From d57f6ab8609134ff114140645f0c423b9ad638eb Mon Sep 17 00:00:00 2001 From: Daniel Arndt Date: Thu, 10 Jun 2021 16:44:32 +0000 Subject: [PATCH 01/13] Replace recursive by iterative implementation of heapify --- .../Tpetra_TestBlockCrsMeshDatabase.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/tpetra/core/example/BlockCrs/Tpetra_TestBlockCrsMeshDatabase.hpp b/packages/tpetra/core/example/BlockCrs/Tpetra_TestBlockCrsMeshDatabase.hpp index 777a0eabe7c9..b722be73a433 100644 --- a/packages/tpetra/core/example/BlockCrs/Tpetra_TestBlockCrsMeshDatabase.hpp +++ b/packages/tpetra/core/example/BlockCrs/Tpetra_TestBlockCrsMeshDatabase.hpp @@ -117,16 +117,18 @@ namespace BlockCrsTest { template KOKKOS_INLINE_FUNCTION static void heapify(T1 *v, T2 n, T2 i) { - T2 largest = i; - T2 l = 2*i + 1; - T2 r = 2*i + 2; - - if (l < n && v[l] > v[largest]) largest = l; - if (r < n && v[r] > v[largest]) largest = r; - if (largest != i) { + while (true) { + T2 largest = i; + T2 l = 2*i + 1; + T2 r = 2*i + 2; + + if (l < n && v[l] > v[largest]) largest = l; + if (r < n && v[r] > v[largest]) largest = r; + if (largest == i) + break; // swap T1 tmp = v[i]; v[i] = v[largest]; v[largest] = tmp; - heapify(v, n, largest); + i = largest; } } From e2359aea1986b54f0878ffdc9543f46f88bb61f1 Mon Sep 17 00:00:00 2001 From: Daniel Arndt Date: Thu, 10 Jun 2021 13:19:54 +0000 Subject: [PATCH 02/13] Tpetra: Enable more SYCL tests --- .../core/ext/TpetraExt_MatrixMatrix_SYCL.hpp | 826 ++++++++++++++++++ .../core/ext/TpetraExt_MatrixMatrix_def.hpp | 1 + .../TpetraExt_TripleMatrixMultiply_def.hpp | 1 + .../ImportExport2/ImportExport2_UnitTests.cpp | 15 +- .../MatrixMatrix/MatrixMatrix_UnitTests.cpp | 13 +- 5 files changed, 851 insertions(+), 5 deletions(-) create mode 100644 packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp diff --git a/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp b/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp new file mode 100644 index 000000000000..d29c8292ea62 --- /dev/null +++ b/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp @@ -0,0 +1,826 @@ +// @HEADER +// *********************************************************************** +// +// Tpetra: Templated Linear Algebra Services Package +// Copyright (2008) Sandia Corporation +// +// Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, +// the U.S. Government retains certain rights in this software. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the Corporation nor the names of the +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Questions? Contact Michael A. Heroux (maherou@sandia.gov) +// +// ************************************************************************ +// @HEADER + +#ifndef TPETRA_MATRIXMATRIX_SYCL_DEF_HPP +#define TPETRA_MATRIXMATRIX_SYCL_DEF_HPP + +#ifdef HAVE_TPETRA_INST_SYCL +namespace Tpetra { +namespace MMdetails { + +/*********************************************************************************************************/ +// MMM KernelWrappers for Partial Specialization to SYCL +template +struct KernelWrappers { + static inline void mult_A_B_newmatrix_kernel_wrapper(CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label = std::string(), + const Teuchos::RCP& params = Teuchos::null); + + + + static inline void mult_A_B_reuse_kernel_wrapper(CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label = std::string(), + const Teuchos::RCP& params = Teuchos::null); + +}; + +// Jacobi KernelWrappers for Partial Specialization to SYCL +template +struct KernelWrappers2 { + static inline void jacobi_A_B_newmatrix_kernel_wrapper(Scalar omega, + const Vector & Dinv, + CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label = std::string(), + const Teuchos::RCP& params = Teuchos::null); + + static inline void jacobi_A_B_reuse_kernel_wrapper(Scalar omega, + const Vector & Dinv, + CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label = std::string(), + const Teuchos::RCP& params = Teuchos::null); + + static inline void jacobi_A_B_newmatrix_KokkosKernels(Scalar omega, + const Vector & Dinv, + CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label = std::string(), + const Teuchos::RCP& params = Teuchos::null); +}; + + +/*********************************************************************************************************/ +// AB NewMatrix Kernel wrappers (KokkosKernels/SYCL Version) +template +void KernelWrappers::mult_A_B_newmatrix_kernel_wrapper(CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label, + const Teuchos::RCP& params) { + + +#ifdef HAVE_TPETRA_MMM_TIMINGS + std::string prefix_mmm = std::string("TpetraExt ") + label + std::string(": "); + using Teuchos::TimeMonitor; + Teuchos::RCP MM = rcp(new TimeMonitor(*(TimeMonitor::getNewTimer(prefix_mmm + std::string("MMM Newmatrix SYCLWrapper"))))); +#endif + // Node-specific code + typedef Kokkos::Compat::KokkosSYCLWrapperNode Node; + std::string nodename("SYCL"); + + // Lots and lots of typedefs + using Teuchos::RCP; + typedef typename Tpetra::CrsMatrix::local_matrix_device_type KCRS; + typedef typename KCRS::device_type device_t; + typedef typename KCRS::StaticCrsGraphType graph_t; + typedef typename graph_t::row_map_type::non_const_type lno_view_t; + typedef typename graph_t::row_map_type::const_type c_lno_view_t; + typedef typename graph_t::entries_type::non_const_type lno_nnz_view_t; + typedef typename KCRS::values_type::non_const_type scalar_view_t; + //typedef typename graph_t::row_map_type::const_type lno_view_t_const; + + // Options + int team_work_size = 16; // Defaults to 16 as per Deveci 12/7/16 - csiefer + std::string myalg("SPGEMM_KK_MEMORY"); + if(!params.is_null()) { + if(params->isParameter("sycl: algorithm")) + myalg = params->get("sycl: algorithm",myalg); + if(params->isParameter("sycl: team work size")) + team_work_size = params->get("sycl: team work size",team_work_size); + } + + // KokkosKernelsHandle + typedef KokkosKernels::Experimental::KokkosKernelsHandle< + typename lno_view_t::const_value_type,typename lno_nnz_view_t::const_value_type, typename scalar_view_t::const_value_type, + typename device_t::execution_space, typename device_t::memory_space,typename device_t::memory_space > KernelHandle; + + // Grab the Kokkos::SparseCrsMatrices + const KCRS & Amat = Aview.origMatrix->getLocalMatrixDevice(); + const KCRS & Bmat = Bview.origMatrix->getLocalMatrixDevice(); + + c_lno_view_t Arowptr = Amat.graph.row_map, + Browptr = Bmat.graph.row_map; + const lno_nnz_view_t Acolind = Amat.graph.entries, + Bcolind = Bmat.graph.entries; + const scalar_view_t Avals = Amat.values, + Bvals = Bmat.values; + + c_lno_view_t Irowptr; + lno_nnz_view_t Icolind; + scalar_view_t Ivals; + if(!Bview.importMatrix.is_null()) { + auto lclB = Bview.importMatrix->getLocalMatrixDevice(); + Irowptr = lclB.graph.row_map; + Icolind = lclB.graph.entries; + Ivals = lclB.values; + } + + + // Get the algorithm mode + std::string alg = nodename+std::string(" algorithm"); + // printf("DEBUG: Using kernel: %s\n",myalg.c_str()); + if(!params.is_null() && params->isParameter(alg)) myalg = params->get(alg,myalg); + KokkosSparse::SPGEMMAlgorithm alg_enum = KokkosSparse::StringToSPGEMMAlgorithm(myalg); + + // Merge the B and Bimport matrices + const KCRS Bmerged = Tpetra::MMdetails::merge_matrices(Aview,Bview,Acol2Brow,Acol2Irow,Bcol2Ccol,Icol2Ccol,C.getColMap()->getNodeNumElements()); + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM = Teuchos::null; MM = rcp(new TimeMonitor (*TimeMonitor::getNewTimer(prefix_mmm + std::string("MMM Newmatrix SYCLCore")))); +#endif + + // Do the multiply on whatever we've got + typename KernelHandle::nnz_lno_t AnumRows = Amat.numRows(); + typename KernelHandle::nnz_lno_t BnumRows = Bmerged.numRows(); + typename KernelHandle::nnz_lno_t BnumCols = Bmerged.numCols(); + + lno_view_t row_mapC (Kokkos::ViewAllocateWithoutInitializing("non_const_lnow_row"), AnumRows + 1); + lno_nnz_view_t entriesC; + scalar_view_t valuesC; + KernelHandle kh; + kh.create_spgemm_handle(alg_enum); + kh.set_team_work_size(team_work_size); + + KokkosSparse::Experimental::spgemm_symbolic(&kh,AnumRows,BnumRows,BnumCols,Amat.graph.row_map,Amat.graph.entries,false,Bmerged.graph.row_map,Bmerged.graph.entries,false,row_mapC); + + size_t c_nnz_size = kh.get_spgemm_handle()->get_c_nnz(); + if (c_nnz_size){ + entriesC = lno_nnz_view_t (Kokkos::ViewAllocateWithoutInitializing("entriesC"), c_nnz_size); + valuesC = scalar_view_t (Kokkos::ViewAllocateWithoutInitializing("valuesC"), c_nnz_size); + } + KokkosSparse::Experimental::spgemm_numeric(&kh,AnumRows,BnumRows,BnumCols,Amat.graph.row_map,Amat.graph.entries,Amat.values,false,Bmerged.graph.row_map,Bmerged.graph.entries,Bmerged.values,false,row_mapC,entriesC,valuesC); + kh.destroy_spgemm_handle(); + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM = Teuchos::null; MM = rcp(new TimeMonitor (*TimeMonitor::getNewTimer(prefix_mmm + std::string("MMM Newmatrix SYCLSort")))); +#endif + + // Sort & set values + if (params.is_null() || params->get("sort entries",true)) + Import_Util::sortCrsEntries(row_mapC, entriesC, valuesC); + C.setAllValues(row_mapC,entriesC,valuesC); + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM = Teuchos::null; MM = rcp(new TimeMonitor (*TimeMonitor::getNewTimer(prefix_mmm + std::string("MMM Newmatrix SYCLESFC")))); +#endif + + // Final Fillcomplete + RCP labelList = rcp(new Teuchos::ParameterList); + labelList->set("Timer Label",label); + if(!params.is_null()) labelList->set("compute global constants",params->get("compute global constants",true)); + RCP > dummyExport; + C.expertStaticFillComplete(Bview.origMatrix->getDomainMap(), Aview.origMatrix->getRangeMap(), Cimport,dummyExport,labelList); +} + + +/*********************************************************************************************************/ +template +void KernelWrappers::mult_A_B_reuse_kernel_wrapper( + CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & targetMapToOrigRow_dev, + const LocalOrdinalViewType & targetMapToImportRow_dev, + const LocalOrdinalViewType & Bcol2Ccol_dev, + const LocalOrdinalViewType & Icol2Ccol_dev, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label, + const Teuchos::RCP& params) { + + // FIXME: Right now, this is a cut-and-paste of the serial kernel + typedef Kokkos::Compat::KokkosSYCLWrapperNode Node; + +#ifdef HAVE_TPETRA_MMM_TIMINGS + std::string prefix_mmm = std::string("TpetraExt ") + label + std::string(": "); + using Teuchos::TimeMonitor; + Teuchos::RCP MM = rcp(new TimeMonitor(*TimeMonitor::getNewTimer(prefix_mmm + std::string("MMM Reuse SerialCore")))); + Teuchos::RCP MM2; +#endif + using Teuchos::RCP; + using Teuchos::rcp; + + + // Lots and lots of typedefs + typedef typename Tpetra::CrsMatrix::local_matrix_host_type KCRS; + typedef typename KCRS::StaticCrsGraphType graph_t; + typedef typename graph_t::row_map_type::const_type c_lno_view_t; + typedef typename graph_t::entries_type::non_const_type lno_nnz_view_t; + typedef typename KCRS::values_type::non_const_type scalar_view_t; + + typedef Scalar SC; + typedef LocalOrdinal LO; + typedef GlobalOrdinal GO; + typedef Node NO; + typedef Map map_type; + const size_t ST_INVALID = Teuchos::OrdinalTraits::invalid(); + const LO LO_INVALID = Teuchos::OrdinalTraits::invalid(); + const SC SC_ZERO = Teuchos::ScalarTraits::zero(); + + // Since this is being run on SYCL, we need to fence because the below code will use UVM + // typename graph_t::execution_space().fence(); + + // KDDKDD UVM Without UVM, need to copy targetMap arrays to host. + // KDDKDD UVM Ideally, this function would run on device and use + // KDDKDD UVM KokkosKernels instead of this host implementation. + auto targetMapToOrigRow = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + targetMapToOrigRow_dev); + auto targetMapToImportRow = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + targetMapToImportRow_dev); + auto Bcol2Ccol = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + Bcol2Ccol_dev); + auto Icol2Ccol = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + Icol2Ccol_dev); + + // Sizes + RCP Ccolmap = C.getColMap(); + size_t m = Aview.origMatrix->getNodeNumRows(); + size_t n = Ccolmap->getNodeNumElements(); + + // Grab the Kokkos::SparseCrsMatrices & inner stuff + const KCRS & Amat = Aview.origMatrix->getLocalMatrixHost(); + const KCRS & Bmat = Bview.origMatrix->getLocalMatrixHost(); + const KCRS & Cmat = C.getLocalMatrixHost(); + + c_lno_view_t Arowptr = Amat.graph.row_map, + Browptr = Bmat.graph.row_map, + Crowptr = Cmat.graph.row_map; + const lno_nnz_view_t Acolind = Amat.graph.entries, + Bcolind = Bmat.graph.entries, + Ccolind = Cmat.graph.entries; + const scalar_view_t Avals = Amat.values, Bvals = Bmat.values; + scalar_view_t Cvals = Cmat.values; + + c_lno_view_t Irowptr; + lno_nnz_view_t Icolind; + scalar_view_t Ivals; + if(!Bview.importMatrix.is_null()) { + auto lclB = Bview.importMatrix->getLocalMatrixHost(); + Irowptr = lclB.graph.row_map; + Icolind = lclB.graph.entries; + Ivals = lclB.values; + } + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM2 = Teuchos::null; MM2 = rcp(new TimeMonitor(*TimeMonitor::getNewTimer(prefix_mmm + std::string("MMM Newmatrix SerialCore - Compare")))); +#endif + + // Classic csr assembly (low memory edition) + // mfh 27 Sep 2016: The c_status array is an implementation detail + // of the local sparse matrix-matrix multiply routine. + + // The status array will contain the index into colind where this entry was last deposited. + // c_status[i] < CSR_ip - not in the row yet + // c_status[i] >= CSR_ip - this is the entry where you can find the data + // We start with this filled with INVALID's indicating that there are no entries yet. + // Sadly, this complicates the code due to the fact that size_t's are unsigned. + std::vector c_status(n, ST_INVALID); + + // For each row of A/C + size_t CSR_ip = 0, OLD_ip = 0; + for (size_t i = 0; i < m; i++) { + // First fill the c_status array w/ locations where we're allowed to + // generate nonzeros for this row + OLD_ip = Crowptr[i]; + CSR_ip = Crowptr[i+1]; + for (size_t k = OLD_ip; k < CSR_ip; k++) { + c_status[Ccolind[k]] = k; + + // Reset values in the row of C + Cvals[k] = SC_ZERO; + } + + for (size_t k = Arowptr[i]; k < Arowptr[i+1]; k++) { + LO Aik = Acolind[k]; + const SC Aval = Avals[k]; + if (Aval == SC_ZERO) + continue; + + if (targetMapToOrigRow[Aik] != LO_INVALID) { + // Local matrix + size_t Bk = Teuchos::as(targetMapToOrigRow[Aik]); + + for (size_t j = Browptr[Bk]; j < Browptr[Bk+1]; ++j) { + LO Bkj = Bcolind[j]; + LO Cij = Bcol2Ccol[Bkj]; + + TEUCHOS_TEST_FOR_EXCEPTION(c_status[Cij] < OLD_ip || c_status[Cij] >= CSR_ip, + std::runtime_error, "Trying to insert a new entry (" << i << "," << Cij << ") into a static graph " << + "(c_status = " << c_status[Cij] << " of [" << OLD_ip << "," << CSR_ip << "))"); + + Cvals[c_status[Cij]] += Aval * Bvals[j]; + } + + } else { + // Remote matrix + size_t Ik = Teuchos::as(targetMapToImportRow[Aik]); + for (size_t j = Irowptr[Ik]; j < Irowptr[Ik+1]; ++j) { + LO Ikj = Icolind[j]; + LO Cij = Icol2Ccol[Ikj]; + + TEUCHOS_TEST_FOR_EXCEPTION(c_status[Cij] < OLD_ip || c_status[Cij] >= CSR_ip, + std::runtime_error, "Trying to insert a new entry (" << i << "," << Cij << ") into a static graph " << + "(c_status = " << c_status[Cij] << " of [" << OLD_ip << "," << CSR_ip << "))"); + + Cvals[c_status[Cij]] += Aval * Ivals[j]; + } + } + } + } + + C.fillComplete(C.getDomainMap(), C.getRangeMap()); +} + +/*********************************************************************************************************/ +template +void KernelWrappers2::jacobi_A_B_newmatrix_kernel_wrapper(Scalar omega, + const Vector & Dinv, + CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label, + const Teuchos::RCP& params) { + +#ifdef HAVE_TPETRA_MMM_TIMINGS + std::string prefix_mmm = std::string("TpetraExt ") + label + std::string(": "); + using Teuchos::TimeMonitor; + Teuchos::RCP MM; +#endif + + // Node-specific code + using Teuchos::RCP; + + // Options + //int team_work_size = 16; // Defaults to 16 as per Deveci 12/7/16 - csiefer // unreferenced + std::string myalg("KK"); + if(!params.is_null()) { + if(params->isParameter("sycl: jacobi algorithm")) + myalg = params->get("sycl: jacobi algorithm",myalg); + } + + if(myalg == "MSAK") { + ::Tpetra::MatrixMatrix::ExtraKernels::jacobi_A_B_newmatrix_MultiplyScaleAddKernel(omega,Dinv,Aview,Bview,Acol2Brow,Acol2Irow,Bcol2Ccol,Icol2Ccol,C,Cimport,label,params); + } + else if(myalg == "KK") { + jacobi_A_B_newmatrix_KokkosKernels(omega,Dinv,Aview,Bview,Acol2Brow,Acol2Irow,Bcol2Ccol,Icol2Ccol,C,Cimport,label,params); + } + else { + throw std::runtime_error("Tpetra::MatrixMatrix::Jacobi newmatrix unknown kernel"); + } + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM = Teuchos::null; MM = rcp(new TimeMonitor (*TimeMonitor::getNewTimer(prefix_mmm + std::string("Jacobi Newmatrix SYCLESFC")))); +#endif + + // Final Fillcomplete + RCP labelList = rcp(new Teuchos::ParameterList); + labelList->set("Timer Label",label); + if(!params.is_null()) labelList->set("compute global constants",params->get("compute global constants",true)); + + // NOTE: MSAK already fillCompletes, so we have to check here + if(!C.isFillComplete()) { + RCP > dummyExport; + C.expertStaticFillComplete(Bview.origMatrix->getDomainMap(), Aview.origMatrix->getRangeMap(), Cimport,dummyExport,labelList); + } + +} + + + +/*********************************************************************************************************/ +template +void KernelWrappers2::jacobi_A_B_reuse_kernel_wrapper(Scalar omega, + const Vector & Dinv, + CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & targetMapToOrigRow_dev, + const LocalOrdinalViewType & targetMapToImportRow_dev, + const LocalOrdinalViewType & Bcol2Ccol_dev, + const LocalOrdinalViewType & Icol2Ccol_dev, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label, + const Teuchos::RCP& params) { + + // FIXME: Right now, this is a cut-and-paste of the serial kernel + typedef Kokkos::Compat::KokkosSYCLWrapperNode Node; + +#ifdef HAVE_TPETRA_MMM_TIMINGS + std::string prefix_mmm = std::string("TpetraExt ") + label + std::string(": "); + using Teuchos::TimeMonitor; + Teuchos::RCP MM = rcp(new TimeMonitor(*TimeMonitor::getNewTimer(prefix_mmm + std::string("Jacobi Reuse SYCLCore")))); + Teuchos::RCP MM2; +#endif + using Teuchos::RCP; + using Teuchos::rcp; + + // Lots and lots of typedefs + typedef typename Tpetra::CrsMatrix::local_matrix_host_type KCRS; + typedef typename KCRS::StaticCrsGraphType graph_t; + typedef typename graph_t::row_map_type::const_type c_lno_view_t; + typedef typename graph_t::entries_type::non_const_type lno_nnz_view_t; + typedef typename KCRS::values_type::non_const_type scalar_view_t; + typedef typename scalar_view_t::memory_space scalar_memory_space; + + typedef Scalar SC; + typedef LocalOrdinal LO; + typedef GlobalOrdinal GO; + typedef Node NO; + typedef Map map_type; + const size_t ST_INVALID = Teuchos::OrdinalTraits::invalid(); + const LO LO_INVALID = Teuchos::OrdinalTraits::invalid(); + const SC SC_ZERO = Teuchos::ScalarTraits::zero(); + + // Since this is being run on SYCL, we need to fence because the below host code will use UVM + // KDDKDD typename graph_t::execution_space().fence(); + + // KDDKDD UVM Without UVM, need to copy targetMap arrays to host. + // KDDKDD UVM Ideally, this function would run on device and use + // KDDKDD UVM KokkosKernels instead of this host implementation. + auto targetMapToOrigRow = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + targetMapToOrigRow_dev); + auto targetMapToImportRow = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + targetMapToImportRow_dev); + auto Bcol2Ccol = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + Bcol2Ccol_dev); + auto Icol2Ccol = + Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), + Icol2Ccol_dev); + + + // Sizes + RCP Ccolmap = C.getColMap(); + size_t m = Aview.origMatrix->getNodeNumRows(); + size_t n = Ccolmap->getNodeNumElements(); + + // Grab the Kokkos::SparseCrsMatrices & inner stuff + const KCRS & Amat = Aview.origMatrix->getLocalMatrixHost(); + const KCRS & Bmat = Bview.origMatrix->getLocalMatrixHost(); + const KCRS & Cmat = C.getLocalMatrixHost(); + + c_lno_view_t Arowptr = Amat.graph.row_map, Browptr = Bmat.graph.row_map, Crowptr = Cmat.graph.row_map; + const lno_nnz_view_t Acolind = Amat.graph.entries, Bcolind = Bmat.graph.entries, Ccolind = Cmat.graph.entries; + const scalar_view_t Avals = Amat.values, Bvals = Bmat.values; + scalar_view_t Cvals = Cmat.values; + + c_lno_view_t Irowptr; + lno_nnz_view_t Icolind; + scalar_view_t Ivals; + if(!Bview.importMatrix.is_null()) { + auto lclB = Bview.importMatrix->getLocalMatrixHost(); + Irowptr = lclB.graph.row_map; + Icolind = lclB.graph.entries; + Ivals = lclB.values; + } + + // Jacobi-specific inner stuff + auto Dvals = + Dinv.template getLocalView(Access::ReadOnly); + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM2 = Teuchos::null; MM2 = rcp(new TimeMonitor(*TimeMonitor::getNewTimer(prefix_mmm + std::string("Jacobi Reuse SYCLCore - Compare")))); +#endif + + // The status array will contain the index into colind where this entry was last deposited. + // c_status[i] < CSR_ip - not in the row yet + // c_status[i] >= CSR_ip - this is the entry where you can find the data + // We start with this filled with INVALID's indicating that there are no entries yet. + // Sadly, this complicates the code due to the fact that size_t's are unsigned. + std::vector c_status(n, ST_INVALID); + + // For each row of A/C + size_t CSR_ip = 0, OLD_ip = 0; + for (size_t i = 0; i < m; i++) { + + // First fill the c_status array w/ locations where we're allowed to + // generate nonzeros for this row + OLD_ip = Crowptr[i]; + CSR_ip = Crowptr[i+1]; + for (size_t k = OLD_ip; k < CSR_ip; k++) { + c_status[Ccolind[k]] = k; + + // Reset values in the row of C + Cvals[k] = SC_ZERO; + } + + SC minusOmegaDval = -omega*Dvals(i,0); + + // Entries of B + for (size_t j = Browptr[i]; j < Browptr[i+1]; j++) { + Scalar Bval = Bvals[j]; + if (Bval == SC_ZERO) + continue; + LO Bij = Bcolind[j]; + LO Cij = Bcol2Ccol[Bij]; + + TEUCHOS_TEST_FOR_EXCEPTION(c_status[Cij] < OLD_ip || c_status[Cij] >= CSR_ip, + std::runtime_error, "Trying to insert a new entry into a static graph"); + + Cvals[c_status[Cij]] = Bvals[j]; + } + + // Entries of -omega * Dinv * A * B + for (size_t k = Arowptr[i]; k < Arowptr[i+1]; k++) { + LO Aik = Acolind[k]; + const SC Aval = Avals[k]; + if (Aval == SC_ZERO) + continue; + + if (targetMapToOrigRow[Aik] != LO_INVALID) { + // Local matrix + size_t Bk = Teuchos::as(targetMapToOrigRow[Aik]); + + for (size_t j = Browptr[Bk]; j < Browptr[Bk+1]; ++j) { + LO Bkj = Bcolind[j]; + LO Cij = Bcol2Ccol[Bkj]; + + TEUCHOS_TEST_FOR_EXCEPTION(c_status[Cij] < OLD_ip || c_status[Cij] >= CSR_ip, + std::runtime_error, "Trying to insert a new entry into a static graph"); + + Cvals[c_status[Cij]] += minusOmegaDval * Aval * Bvals[j]; + } + + } else { + // Remote matrix + size_t Ik = Teuchos::as(targetMapToImportRow[Aik]); + for (size_t j = Irowptr[Ik]; j < Irowptr[Ik+1]; ++j) { + LO Ikj = Icolind[j]; + LO Cij = Icol2Ccol[Ikj]; + + TEUCHOS_TEST_FOR_EXCEPTION(c_status[Cij] < OLD_ip || c_status[Cij] >= CSR_ip, + std::runtime_error, "Trying to insert a new entry into a static graph"); + + Cvals[c_status[Cij]] += minusOmegaDval * Aval * Ivals[j]; + } + } + } + } + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM2= Teuchos::null; + MM = Teuchos::null; MM = rcp(new TimeMonitor (*TimeMonitor::getNewTimer(prefix_mmm + std::string("Jacobi Reuse ESFC")))); +#endif + + C.fillComplete(C.getDomainMap(), C.getRangeMap()); + +} + +/*********************************************************************************************************/ +template +void KernelWrappers2::jacobi_A_B_newmatrix_KokkosKernels(Scalar omega, + const Vector & Dinv, + CrsMatrixStruct& Aview, + CrsMatrixStruct& Bview, + const LocalOrdinalViewType & Acol2Brow, + const LocalOrdinalViewType & Acol2Irow, + const LocalOrdinalViewType & Bcol2Ccol, + const LocalOrdinalViewType & Icol2Ccol, + CrsMatrix& C, + Teuchos::RCP > Cimport, + const std::string& label, + const Teuchos::RCP& params) { + +#ifdef HAVE_TPETRA_MMM_TIMINGS + std::string prefix_mmm = std::string("TpetraExt ") + label + std::string(": "); + using Teuchos::TimeMonitor; + Teuchos::RCP MM; +#endif + + // Check if the diagonal entries exist in debug mode + const bool debug = Tpetra::Details::Behavior::debug(); + if(debug) { + + auto rowMap = Aview.origMatrix->getRowMap(); + Tpetra::Vector diags(rowMap); + Aview.origMatrix->getLocalDiagCopy(diags); + size_t diagLength = rowMap->getNodeNumElements(); + Teuchos::Array diagonal(diagLength); + diags.get1dCopy(diagonal()); + + for(size_t i = 0; i < diagLength; ++i) { + TEUCHOS_TEST_FOR_EXCEPTION(diagonal[i] == Teuchos::ScalarTraits::zero(), + std::runtime_error, + "Matrix A has a zero/missing diagonal: " << diagonal[i] << std::endl << + "KokkosKernels Jacobi-fused SpGEMM requires nonzero diagonal entries in A" << std::endl); + } + } + + // Usings + using device_t = typename Kokkos::Compat::KokkosSYCLWrapperNode::device_type; + using matrix_t = typename Tpetra::CrsMatrix::local_matrix_device_type; + using graph_t = typename matrix_t::StaticCrsGraphType; + using lno_view_t = typename graph_t::row_map_type::non_const_type; + using c_lno_view_t = typename graph_t::row_map_type::const_type; + using lno_nnz_view_t = typename graph_t::entries_type::non_const_type; + using scalar_view_t = typename matrix_t::values_type::non_const_type; + + // KokkosKernels handle + using handle_t = typename KokkosKernels::Experimental::KokkosKernelsHandle< + typename lno_view_t::const_value_type,typename lno_nnz_view_t::const_value_type, typename scalar_view_t::const_value_type, + typename device_t::execution_space, typename device_t::memory_space,typename device_t::memory_space >; + + // Get the rowPtr, colInd and vals of importMatrix + c_lno_view_t Irowptr; + lno_nnz_view_t Icolind; + scalar_view_t Ivals; + if(!Bview.importMatrix.is_null()) { + auto lclB = Bview.importMatrix->getLocalMatrixDevice(); + Irowptr = lclB.graph.row_map; + Icolind = lclB.graph.entries; + Ivals = lclB.values; + } + + // Merge the B and Bimport matrices + const matrix_t Bmerged = Tpetra::MMdetails::merge_matrices(Aview,Bview,Acol2Brow,Acol2Irow,Bcol2Ccol,Icol2Ccol,C.getColMap()->getNodeNumElements()); + + // Get the properties and arrays of input matrices + const matrix_t & Amat = Aview.origMatrix->getLocalMatrixDevice(); + const matrix_t & Bmat = Bview.origMatrix->getLocalMatrixDevice(); + + typename handle_t::nnz_lno_t AnumRows = Amat.numRows(); + typename handle_t::nnz_lno_t BnumRows = Bmerged.numRows(); + typename handle_t::nnz_lno_t BnumCols = Bmerged.numCols(); + + c_lno_view_t Arowptr = Amat.graph.row_map, Browptr = Bmerged.graph.row_map; + const lno_nnz_view_t Acolind = Amat.graph.entries, Bcolind = Bmerged.graph.entries; + const scalar_view_t Avals = Amat.values, Bvals = Bmerged.values; + + // Arrays of the output matrix + lno_view_t row_mapC (Kokkos::ViewAllocateWithoutInitializing("non_const_lnow_row"), AnumRows + 1); + lno_nnz_view_t entriesC; + scalar_view_t valuesC; + + // Options + int team_work_size = 16; + std::string myalg("SPGEMM_KK_MEMORY"); + if(!params.is_null()) { + if(params->isParameter("sycl: algorithm")) + myalg = params->get("sycl: algorithm",myalg); + if(params->isParameter("sycl: team work size")) + team_work_size = params->get("sycl: team work size",team_work_size); + } + + // Get the algorithm mode + std::string nodename("SYCL"); + std::string alg = nodename + std::string(" algorithm"); + if(!params.is_null() && params->isParameter(alg)) myalg = params->get(alg,myalg); + KokkosSparse::SPGEMMAlgorithm alg_enum = KokkosSparse::StringToSPGEMMAlgorithm(myalg); + + + // KokkosKernels call + handle_t kh; + kh.create_spgemm_handle(alg_enum); + kh.set_team_work_size(team_work_size); + + KokkosSparse::Experimental::spgemm_symbolic(&kh, AnumRows, BnumRows, BnumCols, + Arowptr, Acolind, false, + Browptr, Bcolind, false, + row_mapC); + + size_t c_nnz_size = kh.get_spgemm_handle()->get_c_nnz(); + if (c_nnz_size){ + entriesC = lno_nnz_view_t (Kokkos::ViewAllocateWithoutInitializing("entriesC"), c_nnz_size); + valuesC = scalar_view_t (Kokkos::ViewAllocateWithoutInitializing("valuesC"), c_nnz_size); + } + + KokkosSparse::Experimental::spgemm_jacobi(&kh, AnumRows, BnumRows, BnumCols, + Arowptr, Acolind, Avals, false, + Browptr, Bcolind, Bvals, false, + row_mapC, entriesC, valuesC, + omega, Dinv.getLocalViewDevice(Access::ReadOnly)); + kh.destroy_spgemm_handle(); + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM = Teuchos::null; MM = rcp(new TimeMonitor (*TimeMonitor::getNewTimer(prefix_mmm + std::string("Jacobi Newmatrix SYCLSort")))); +#endif + + // Sort & set values + if (params.is_null() || params->get("sort entries",true)) + Import_Util::sortCrsEntries(row_mapC, entriesC, valuesC); + C.setAllValues(row_mapC,entriesC,valuesC); + +#ifdef HAVE_TPETRA_MMM_TIMINGS + MM = Teuchos::null; MM = rcp(new TimeMonitor (*TimeMonitor::getNewTimer(prefix_mmm + std::string("Jacobi Newmatrix SYCLESFC")))); +#endif + + // Final Fillcomplete + Teuchos::RCP labelList = rcp(new Teuchos::ParameterList); + labelList->set("Timer Label",label); + if(!params.is_null()) labelList->set("compute global constants",params->get("compute global constants",true)); + Teuchos::RCP > dummyExport; + C.expertStaticFillComplete(Bview.origMatrix->getDomainMap(), Aview.origMatrix->getRangeMap(), Cimport,dummyExport,labelList); +} + + }//MMdetails +}//Tpetra + +#endif//SYCL + +#endif diff --git a/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_def.hpp b/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_def.hpp index 37101e7a8a29..63048b9b5a2a 100644 --- a/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_def.hpp +++ b/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_def.hpp @@ -82,6 +82,7 @@ #include "TpetraExt_MatrixMatrix_OpenMP.hpp" #include "TpetraExt_MatrixMatrix_Cuda.hpp" #include "TpetraExt_MatrixMatrix_HIP.hpp" +#include "TpetraExt_MatrixMatrix_SYCL.hpp" namespace Tpetra { diff --git a/packages/tpetra/core/ext/TpetraExt_TripleMatrixMultiply_def.hpp b/packages/tpetra/core/ext/TpetraExt_TripleMatrixMultiply_def.hpp index 2a2c082d739f..5df1da47daed 100644 --- a/packages/tpetra/core/ext/TpetraExt_TripleMatrixMultiply_def.hpp +++ b/packages/tpetra/core/ext/TpetraExt_TripleMatrixMultiply_def.hpp @@ -74,6 +74,7 @@ #include "TpetraExt_MatrixMatrix_OpenMP.hpp" #include "TpetraExt_MatrixMatrix_Cuda.hpp" #include "TpetraExt_MatrixMatrix_HIP.hpp" +#include "TpetraExt_MatrixMatrix_SYCL.hpp" namespace Tpetra { diff --git a/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp b/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp index 0451932d45d0..0c5e007a220d 100644 --- a/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp +++ b/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp @@ -2845,11 +2845,20 @@ TEUCHOS_UNIT_TEST_TEMPLATE_2_DECL( Import_Util,GetTwoTransferOwnershipVector, LO TEUCHOS_UNIT_TEST_TEMPLATE_2_INSTANT( Import_Util, GetPids, LO, GO ) \ TEUCHOS_UNIT_TEST_TEMPLATE_2_INSTANT( Import_Util, GetTwoTransferOwnershipVector, LO, GO ) -#define UNIT_TEST_GROUP_SC_LO_GO( SC, LO, GO ) \ +#define UNIT_TEST_GROUP_SC_LO_GO_COMMON( SC, LO, GO ) \ TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( CrsMatrixImportExport, doImport, LO, GO, SC ) \ TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( FusedImportExport, doImport, LO, GO, SC ) \ - TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( Import_Util, UnpackAndCombineWithOwningPIDs, LO, GO, SC ) \ - TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( FusedImportExport, MueLuStyle, LO, GO, SC ) + TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( Import_Util, UnpackAndCombineWithOwningPIDs, LO, GO, SC ) + +// FIXME_SYCL requires quering free device memory +#ifdef HAVE_TPETRA_SYCL + #define UNIT_TEST_GROUP_SC_LO_GO( SC, LO, GO ) \ + UNIT_TEST_GROUP_SC_LO_GO_COMMON( SC, LO, GO ) +#else + #define UNIT_TEST_GROUP_SC_LO_GO( SC, LO, GO ) \ + UNIT_TEST_GROUP_SC_LO_GO_COMMON( SC, LO, GO ) \ + TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( FusedImportExport, MueLuStyle, LO, GO, SC ) +#endif // Note: This test fails. Should fix later. // TEUCHOS_UNIT_TEST_TEMPLATE_2_INSTANT( ReverseImportExport, doImport, ORDINAL, SCALAR ) diff --git a/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp b/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp index 0256eafe3f78..f28e19289b4c 100644 --- a/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp +++ b/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp @@ -2209,8 +2209,7 @@ TEUCHOS_UNIT_TEST_TEMPLATE_4_DECL(Tpetra_MatMatAdd, locally_unsorted, SC, LO, GO }*/ -#define UNIT_TEST_GROUP_SC_LO_GO_NO( SC, LO, GO, NT ) \ - TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMat, operations_test,SC, LO, GO, NT) \ +#define UNIT_TEST_GROUP_SC_LO_GO_NO_COMMON( SC, LO, GO, NT ) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMat, range_row_test, SC, LO, GO, NT) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMat, ATI_range_row_test, SC, LO, GO, NT) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMat, threaded_add_sorted, SC, LO, GO, NT) \ @@ -2222,6 +2221,16 @@ TEUCHOS_UNIT_TEST_TEMPLATE_4_DECL(Tpetra_MatMatAdd, locally_unsorted, SC, LO, GO TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMatAdd, different_col_maps, SC, LO, GO, NT) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMatAdd, different_index_base, SC, LO, GO, NT) +// FIXME_SYCL requires quering free device memory +#ifdef HAVE_TPETRA_SYCL + #define UNIT_TEST_GROUP_SC_LO_GO_NO( SC, LO, GO, NT ) \ + UNIT_TEST_GROUP_SC_LO_GO_NO_COMMON( SC, LO, GO, NT ) +#else + #define UNIT_TEST_GROUP_SC_LO_GO_NO( SC, LO, GO, NT ) \ + TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMat, operations_test,SC, LO, GO, NT) \ + UNIT_TEST_GROUP_SC_LO_GO_NO_COMMON( SC, LO, GO, NT ) +#endif + TPETRA_ETI_MANGLING_TYPEDEFS() TPETRA_INSTANTIATE_SLGN_NO_ORDINAL_SCALAR( UNIT_TEST_GROUP_SC_LO_GO_NO ) From f310e1236c1b3e846a0bcd62a5d3ae90ef52c26f Mon Sep 17 00:00:00 2001 From: Daniel Arndt Date: Thu, 22 Jul 2021 16:08:35 -0400 Subject: [PATCH 03/13] Improve comments --- packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp | 3 +++ .../core/test/ImportExport2/ImportExport2_UnitTests.cpp | 4 +++- .../tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp b/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp index d29c8292ea62..8a76ac813b08 100644 --- a/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp +++ b/packages/tpetra/core/ext/TpetraExt_MatrixMatrix_SYCL.hpp @@ -39,6 +39,9 @@ // ************************************************************************ // @HEADER + +// This is a verbatim copy of the other TpetraExt_MatrixMatrix_*.hpp files +// replacing the execution/memory space by the ones corresponding to SYCL. #ifndef TPETRA_MATRIXMATRIX_SYCL_DEF_HPP #define TPETRA_MATRIXMATRIX_SYCL_DEF_HPP diff --git a/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp b/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp index 0c5e007a220d..decb2583f0ac 100644 --- a/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp +++ b/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp @@ -2845,12 +2845,14 @@ TEUCHOS_UNIT_TEST_TEMPLATE_2_DECL( Import_Util,GetTwoTransferOwnershipVector, LO TEUCHOS_UNIT_TEST_TEMPLATE_2_INSTANT( Import_Util, GetPids, LO, GO ) \ TEUCHOS_UNIT_TEST_TEMPLATE_2_INSTANT( Import_Util, GetTwoTransferOwnershipVector, LO, GO ) +// These are the tests associated to UNIT_TEST_GROUP_SC_LO_GO that work for all backends. #define UNIT_TEST_GROUP_SC_LO_GO_COMMON( SC, LO, GO ) \ TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( CrsMatrixImportExport, doImport, LO, GO, SC ) \ TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( FusedImportExport, doImport, LO, GO, SC ) \ TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( Import_Util, UnpackAndCombineWithOwningPIDs, LO, GO, SC ) -// FIXME_SYCL requires quering free device memory +// FIXME_SYCL requires querying free device memory in KokkosKernels. +// The SYCL specifications don't allow asking for that. #ifdef HAVE_TPETRA_SYCL #define UNIT_TEST_GROUP_SC_LO_GO( SC, LO, GO ) \ UNIT_TEST_GROUP_SC_LO_GO_COMMON( SC, LO, GO ) diff --git a/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp b/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp index f28e19289b4c..e0b5770eb83a 100644 --- a/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp +++ b/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp @@ -2209,6 +2209,7 @@ TEUCHOS_UNIT_TEST_TEMPLATE_4_DECL(Tpetra_MatMatAdd, locally_unsorted, SC, LO, GO }*/ +// These are the tests associated to UNIT_TEST_GROUP_SC_LO_GO_NO that work for all backends. #define UNIT_TEST_GROUP_SC_LO_GO_NO_COMMON( SC, LO, GO, NT ) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMat, range_row_test, SC, LO, GO, NT) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMat, ATI_range_row_test, SC, LO, GO, NT) \ @@ -2221,7 +2222,8 @@ TEUCHOS_UNIT_TEST_TEMPLATE_4_DECL(Tpetra_MatMatAdd, locally_unsorted, SC, LO, GO TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMatAdd, different_col_maps, SC, LO, GO, NT) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMatAdd, different_index_base, SC, LO, GO, NT) -// FIXME_SYCL requires quering free device memory +// FIXME_SYCL requires querying free device memory in KokkosKernels. +// The SYCL specifications don't allow asking for that. #ifdef HAVE_TPETRA_SYCL #define UNIT_TEST_GROUP_SC_LO_GO_NO( SC, LO, GO, NT ) \ UNIT_TEST_GROUP_SC_LO_GO_NO_COMMON( SC, LO, GO, NT ) From 3b4f1164fa086cfefd2fdd61aa0f2edde8820b93 Mon Sep 17 00:00:00 2001 From: Daniel Arndt Date: Thu, 22 Jul 2021 17:42:59 -0400 Subject: [PATCH 04/13] Mention KokkosKernel issue --- .../tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp | 3 ++- .../tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp b/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp index decb2583f0ac..5a5b787b4b95 100644 --- a/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp +++ b/packages/tpetra/core/test/ImportExport2/ImportExport2_UnitTests.cpp @@ -2851,7 +2851,8 @@ TEUCHOS_UNIT_TEST_TEMPLATE_2_DECL( Import_Util,GetTwoTransferOwnershipVector, LO TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( FusedImportExport, doImport, LO, GO, SC ) \ TEUCHOS_UNIT_TEST_TEMPLATE_3_INSTANT( Import_Util, UnpackAndCombineWithOwningPIDs, LO, GO, SC ) -// FIXME_SYCL requires querying free device memory in KokkosKernels. +// FIXME_SYCL requires querying free device memory in KokkosKernels, see +// https://github.com/kokkos/kokkos-kernels/issues/1062. // The SYCL specifications don't allow asking for that. #ifdef HAVE_TPETRA_SYCL #define UNIT_TEST_GROUP_SC_LO_GO( SC, LO, GO ) \ diff --git a/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp b/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp index e0b5770eb83a..63b13fd6a208 100644 --- a/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp +++ b/packages/tpetra/core/test/MatrixMatrix/MatrixMatrix_UnitTests.cpp @@ -2222,7 +2222,8 @@ TEUCHOS_UNIT_TEST_TEMPLATE_4_DECL(Tpetra_MatMatAdd, locally_unsorted, SC, LO, GO TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMatAdd, different_col_maps, SC, LO, GO, NT) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(Tpetra_MatMatAdd, different_index_base, SC, LO, GO, NT) -// FIXME_SYCL requires querying free device memory in KokkosKernels. +// FIXME_SYCL requires querying free device memory in KokkosKernels, see +// https://github.com/kokkos/kokkos-kernels/issues/1062. // The SYCL specifications don't allow asking for that. #ifdef HAVE_TPETRA_SYCL #define UNIT_TEST_GROUP_SC_LO_GO_NO( SC, LO, GO, NT ) \ From b539d1a3200034d8eaa1dcdb83436d7fa92fd340 Mon Sep 17 00:00:00 2001 From: Roger Pawlowski Date: Wed, 6 Oct 2021 12:40:18 -0600 Subject: [PATCH 05/13] Panzer: fix 1D line mesh issue on cuda --- packages/panzer/disc-fe/src/Panzer_IntegrationValues2.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/panzer/disc-fe/src/Panzer_IntegrationValues2.cpp b/packages/panzer/disc-fe/src/Panzer_IntegrationValues2.cpp index cbe5c45a2d3d..77e14cb368bd 100644 --- a/packages/panzer/disc-fe/src/Panzer_IntegrationValues2.cpp +++ b/packages/panzer/disc-fe/src/Panzer_IntegrationValues2.cpp @@ -1717,6 +1717,7 @@ getCubaturePointsRef(const bool cache, Kokkos::parallel_for("copy values",policy,KOKKOS_LAMBDA (const int cell,const int point, const int dim) { aux(cell,point_offset + point,dim) = side_cub_points(point,dim); }); + PHX::Device::execution_space().fence(); } } else { From 54a5a70f77e091a0a93e28230725d17b94942d61 Mon Sep 17 00:00:00 2001 From: Julia Plews Date: Tue, 12 Oct 2021 15:38:56 -0600 Subject: [PATCH 06/13] MiniTensor: fix or at least work around some __host__ __device__ marking problems on CUDA build. Use std:: instead of boost:: in some places? --- .../minitensor/src/MiniTensor_LinearAlgebra.h | 135 +++------ .../src/MiniTensor_LinearAlgebra.i.h | 109 ++++--- .../src/MiniTensor_LinearAlgebra.t.h | 267 +++++++----------- .../minitensor/src/MiniTensor_Mechanics.t.h | 2 +- .../minitensor/src/MiniTensor_Solvers.t.h | 4 +- packages/minitensor/src/MiniTensor_Storage.h | 114 +++----- packages/minitensor/src/MiniTensor_Tensor.i.h | 16 +- .../minitensor/src/MiniTensor_TensorBase.i.h | 7 +- .../minitensor/src/MiniTensor_Utilities.h | 15 +- .../minitensor/src/MiniTensor_Utilities.i.h | 18 +- packages/minitensor/src/MiniTensor_Vector.h | 1 - packages/minitensor/src/MiniTensor_Vector.i.h | 16 +- packages/minitensor/test/test_01.cc | 22 +- 13 files changed, 278 insertions(+), 448 deletions(-) diff --git a/packages/minitensor/src/MiniTensor_LinearAlgebra.h b/packages/minitensor/src/MiniTensor_LinearAlgebra.h index cf373bfd617c..c702a66486df 100644 --- a/packages/minitensor/src/MiniTensor_LinearAlgebra.h +++ b/packages/minitensor/src/MiniTensor_LinearAlgebra.h @@ -183,10 +183,7 @@ I3(Tensor const & A); /// Exponential map. /// \return \f$ \exp A \f$ /// -template -KOKKOS_INLINE_FUNCTION -Tensor -exp(Tensor const & A); +template Tensor exp(Tensor const &A); /// /// Exponential map by Taylor series, radius of convergence is infinity @@ -202,10 +199,7 @@ exp_taylor(Tensor const & A); /// See algorithm 10.20 in Functions of Matrices, N.J. Higham, SIAM, 2008. /// \return \f$ \exp A \f$ /// -template -KOKKOS_INLINE_FUNCTION -Tensor -exp_pade(Tensor const & A); +template Tensor exp_pade(Tensor const &A); /// /// Logarithmic map. @@ -338,20 +332,16 @@ norm_off_diagonal(Tensor const & A); /// that rely on Jacobi-type procedures. /// \return \f$ (p,q) = arg max_{i,j} |a_{ij}| \f$ /// -template -KOKKOS_INLINE_FUNCTION -std::pair -arg_max_abs(Tensor const & A); +template +std::pair arg_max_abs(Tensor const &A); /// /// Arg max off-diagonal. Useful for SVD and other algorithms /// that rely on Jacobi-type procedures. /// \return \f$ (p,q) = arg max_{i \neq j} |a_{ij}| \f$ /// -template -KOKKOS_INLINE_FUNCTION -std::pair -arg_max_off_diagonal(Tensor const & A); +template +std::pair arg_max_off_diagonal(Tensor const &A); /// /// Sort and index. Useful for ordering singular values @@ -360,18 +350,15 @@ arg_max_off_diagonal(Tensor const & A); /// \param u vector to sort /// \return v P sorted vector, permutation matrix such that v = P^T u /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -sort_permutation(Vector const & u); +template +std::pair, Tensor> sort_permutation(Vector const &u); /// /// Singular value decomposition (SVD) /// \return \f$ A = USV^T\f$ /// -template -boost::tuple, Tensor, Tensor> -svd(Tensor const & A); +template +std::tuple, Tensor, Tensor> svd(Tensor const &A); /// /// Project to O(N) (Orthogonal Group) using a Newton-type algorithm. @@ -392,62 +379,54 @@ polar_rotation(Tensor const & A); /// \param A tensor (often a deformation-gradient-like tensor) /// \return \f$ VR = A \f$ with \f$ R \in SO(N) \f$ and \f$ V \in SPD(N) \f$ /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_left(Tensor const & A); +template +std::pair, Tensor> polar_left(Tensor const &A); /// /// Right polar decomposition /// \param A tensor (often a deformation-gradient-like tensor) /// \return \f$ RU = A \f$ with \f$ R \in SO(N) \f$ and \f$ U \in SPD(N) \f$ /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_right(Tensor const & A); +template +std::pair, Tensor> polar_right(Tensor const &A); /// /// Left polar decomposition computed with eigenvalue decomposition /// \param A tensor (often a deformation-gradient-like tensor) /// \return \f$ VR = A \f$ with \f$ R \in SO(N) \f$ and \f$ V \in SPD(N) \f$ /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_left_eig(Tensor const & A); +template +std::pair, Tensor> polar_left_eig(Tensor const &A); /// /// R^3 right polar decomposition /// \param A tensor (often a deformation-gradient-like tensor) /// \return \f$ RU = F \f$ with \f$ R \in SO(N) \f$ and \f$ U \in SPD(N) \f$ /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_right_eig(Tensor const & A); +template +std::pair, Tensor> polar_right_eig(Tensor const &A); /// /// Left polar decomposition with matrix logarithm for V /// \param F tensor (often a deformation-gradient-like tensor) /// \return \f$ VR = F \f$ with \f$ R \in SO(N) \f$ and V SPD, and log V /// -template -boost::tuple, Tensor, Tensor> -polar_left_logV(Tensor const & F); +template +std::tuple, Tensor, Tensor> +polar_left_logV(Tensor const &F); -template -boost::tuple, Tensor, Tensor> -polar_left_logV_eig(Tensor const & F); +template +std::tuple, Tensor, Tensor> +polar_left_logV_eig(Tensor const &F); /// /// Left polar decomposition with matrix logarithm for V using eig_spd_cos /// \param F tensor (often a deformation-gradient-like tensor) /// \return \f$ VR = F \f$ with \f$ R \in SO(N) \f$ and V SPD, and log V /// -template -boost::tuple, Tensor, Tensor> -polar_left_logV_lame(Tensor const & F); +template +std::tuple, Tensor, Tensor> +polar_left_logV_lame(Tensor const &F); /// /// Logarithmic map using BCH expansion (4 terms) @@ -465,37 +444,28 @@ bch(Tensor const & v, Tensor const & r); /// \param \f$ A = [f, g; g, h] \in S(2) \f$ /// \return \f$ c, s \rightarrow [c, -s; s, c]\f$ diagonalizes A$ /// -template -KOKKOS_INLINE_FUNCTION -std::pair -schur_sym(const T f, const T g, const T h); +template +std::pair schur_sym(const T f, const T g, const T h); /// /// Givens rotation. [c, -s; s, c] [a; b] = [r; 0] /// \return c and s /// -template -KOKKOS_INLINE_FUNCTION -std::pair -givens(T const & a, T const & b); +template std::pair givens(T const &a, T const &b); /// /// Eigenvalue decomposition for symmetric 2nd-order tensor /// \return V eigenvectors, D eigenvalues in diagonal Matlab-style /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_sym(Tensor const & A); +template +std::pair, Tensor> eig_sym(Tensor const &A); /// /// Eigenvalue decomposition for SPD 2nd-order tensor /// \return V eigenvectors, D eigenvalues in diagonal Matlab-style /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_spd(Tensor const & A); +template +std::pair, Tensor> eig_spd(Tensor const &A); /// /// Eigenvalue decomposition for SPD 2nd-order tensor @@ -503,10 +473,8 @@ eig_spd(Tensor const & A); /// This algorithm comes from the journal article /// Scherzinger and Dohrmann, CMAME 197 (2008) 4007-4015 /// -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_spd_cos(Tensor const & A); +template +std::pair, Tensor> eig_spd_cos(Tensor const &A); /// /// Cholesky decomposition, rank-1 update algorithm @@ -515,10 +483,8 @@ eig_spd_cos(Tensor const & A); /// \return G Cholesky factor A = GG^T and completed (bool) /// algorithm ran to completion /// -template -KOKKOS_INLINE_FUNCTION -std::pair, bool > -cholesky(Tensor const & A); +template +std::pair, bool> cholesky(Tensor const &A); /// /// Preconditioner types @@ -535,10 +501,9 @@ enum class PreconditionerType /// Compute a preconditioner for improving the conditioning of a /// linear system. /// -template -KOKKOS_INLINE_FUNCTION -std::pair, RHS> -precon(PreconditionerType const pt, Tensor const & A, RHS const & B); +template +std::pair, RHS> precon(PreconditionerType const pt, + Tensor const &A, RHS const &B); /// /// Solve linear system of equations. @@ -551,11 +516,9 @@ precon(PreconditionerType const pt, Tensor const & A, RHS const & B); /// \param b rhs of the system Ax=b /// \return x solution(s) to the system Ax=b /// -template -KOKKOS_INLINE_FUNCTION -RHS -solve(Tensor const & A, RHS const & b, - PreconditionerType const pt = PreconditionerType::IDENTITY); +template +RHS solve(Tensor const &A, RHS const &b, + PreconditionerType const pt = PreconditionerType::IDENTITY); template KOKKOS_INLINE_FUNCTION @@ -565,18 +528,12 @@ solve_full_pivot(Tensor const & A, RHS const & b); /// /// Condition number: ratio of largest to smalest singular values. /// -template -KOKKOS_INLINE_FUNCTION -T -cond(Tensor const & A); +template T cond(Tensor const &A); /// /// Reciprocal condition number: ratio of smallest to largest singular values. /// -template -KOKKOS_INLINE_FUNCTION -T -inv_cond(Tensor const & A); +template T inv_cond(Tensor const &A); } // namespace minitensor diff --git a/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h b/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h index 91e6123822dd..92e4aa540653 100644 --- a/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h +++ b/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h @@ -96,6 +96,8 @@ KOKKOS_INLINE_FUNCTION T norm_1(Tensor const & A) { + using KAT = Kokkos::ArithTraits; + Index const dimension = A.get_dimension(); @@ -107,40 +109,39 @@ norm_1(Tensor const & A) switch (dimension) { - default: + default: - for (Index i = 0; i < dimension; ++i) { - T t = 0.0; - for (Index j = 0; j < dimension; ++j) { - t += std::abs(A(j, i)); - } - v(i) = t; + for (Index i = 0; i < dimension; ++i) { + T t = 0.0; + for (Index j = 0; j < dimension; ++j) { + t += KAT::abs(A(j, i)); } + v(i) = t; + } - for (Index i = 0; i < dimension; ++i) { - s = std::max(s, v(i)); - } - break; - - case 3: - v(0) = std::abs(A(0,0)) + std::abs(A(1,0)) + std::abs(A(2,0)); - v(1) = std::abs(A(0,1)) + std::abs(A(1,1)) + std::abs(A(2,1)); - v(2) = std::abs(A(0,2)) + std::abs(A(1,2)) + std::abs(A(2,2)); + for (Index i = 0; i < dimension; ++i) { + s = max(s, v(i)); + } + break; - s = std::max(std::max(v(0),v(1)),v(2)); - break; + case 3: + v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(1, 0)) + KAT::abs(A(2, 0)); + v(1) = KAT::abs(A(0, 1)) + KAT::abs(A(1, 1)) + KAT::abs(A(2, 1)); + v(2) = KAT::abs(A(0, 2)) + KAT::abs(A(1, 2)) + KAT::abs(A(2, 2)); - case 2: - v(0) = std::abs(A(0,0)) + std::abs(A(1,0)); - v(1) = std::abs(A(0,1)) + std::abs(A(1,1)); + s = max(max(v(0), v(1)), v(2)); + break; - s = std::max(v(0),v(1)); - break; + case 2: + v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(1, 0)); + v(1) = KAT::abs(A(0, 1)) + KAT::abs(A(1, 1)); - case 1: - s = std::abs(A(0,0)); - break; + s = max(v(0), v(1)); + break; + case 1: + s = KAT::abs(A(0, 0)); + break; } return s; @@ -155,6 +156,8 @@ KOKKOS_INLINE_FUNCTION T norm_infinity(Tensor const & A) { + using KAT = Kokkos::ArithTraits; + Index const dimension = A.get_dimension(); @@ -169,33 +172,33 @@ norm_infinity(Tensor const & A) for (Index i = 0; i < dimension; ++i) { T t = 0.0; for (Index j = 0; j < dimension; ++j) { - t += std::abs(A(i, j)); + t += KAT::abs(A(i, j)); } v(i) = t; } for (Index i = 0; i < dimension; ++i) { - s = std::max(s, v(i)); + s = max(s, v(i)); } break; case 3: - v(0) = std::abs(A(0,0)) + std::abs(A(0,1)) + std::abs(A(0,2)); - v(1) = std::abs(A(1,0)) + std::abs(A(1,1)) + std::abs(A(1,2)); - v(2) = std::abs(A(2,0)) + std::abs(A(2,1)) + std::abs(A(2,2)); + v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(0, 1)) + KAT::abs(A(0, 2)); + v(1) = KAT::abs(A(1, 0)) + KAT::abs(A(1, 1)) + KAT::abs(A(1, 2)); + v(2) = KAT::abs(A(2, 0)) + KAT::abs(A(2, 1)) + KAT::abs(A(2, 2)); - s = std::max(std::max(v(0),v(1)),v(2)); + s = max(max(v(0), v(1)), v(2)); break; case 2: - v(0) = std::abs(A(0,0)) + std::abs(A(0,1)); - v(1) = std::abs(A(1,0)) + std::abs(A(1,1)); + v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(0, 1)); + v(1) = KAT::abs(A(1, 0)) + KAT::abs(A(1, 1)); - s = std::max(v(0),v(1)); + s = max(v(0), v(1)); break; case 1: - s = std::abs(A(0,0)); + s = KAT::abs(A(0, 0)); break; } @@ -375,10 +378,11 @@ I2(Tensor const & A) default: #ifdef KOKKOS_ENABLE_CUDA Kokkos::abort("I2 for N > 3 not implemented."); + return T(); #else std::cerr << "I2 for N > 3 not implemented." << std::endl; -#endif exit(1); +#endif break; case 3: @@ -420,10 +424,11 @@ I3(Tensor const & A) default: #ifdef KOKKOS_ENABLE_CUDA Kokkos::abort("I3 for N > 3 not implemented."); -#else + return T(); +#else std::cerr << "I3 for N > 3 not implemented." << std::endl; -#endif exit(1); +#endif break; case 3: @@ -446,16 +451,11 @@ I3(Tensor const & A) // // Condition number. // -template -KOKKOS_INLINE_FUNCTION -T -cond(Tensor const & A) -{ +template T cond(Tensor const &A) { Index const dimension = A.get_dimension(); - Tensor const - S = boost::get<1>(svd(A)); + Tensor const S = std::get<1>(svd(A)); T const k = S(0, 0) / S(dimension - 1, dimension - 1); @@ -466,16 +466,11 @@ cond(Tensor const & A) // // Reciprocal condition number. // -template -KOKKOS_INLINE_FUNCTION -T -inv_cond(Tensor const & A) -{ +template T inv_cond(Tensor const &A) { Index const dimension = A.get_dimension(); - Tensor const - S = boost::get<1>(svd(A)); + Tensor const S = std::get<1>(svd(A)); T const k = S(dimension - 1, dimension - 1) / S(0, 0); @@ -487,11 +482,8 @@ inv_cond(Tensor const & A) // Sort and index in descending order. Useful for ordering singular values // and eigenvalues and corresponding vectors in the respective decompositions. // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -sort_permutation(Vector const & u) -{ +template +std::pair, Tensor> sort_permutation(Vector const &u) { Index const dimension = u.get_dimension(); @@ -517,7 +509,6 @@ sort_permutation(Vector const & u) } return std::make_pair(v, P); - } } // namespace minitensor diff --git a/packages/minitensor/src/MiniTensor_LinearAlgebra.t.h b/packages/minitensor/src/MiniTensor_LinearAlgebra.t.h index 85d14a5e164d..bb6674069e1f 100644 --- a/packages/minitensor/src/MiniTensor_LinearAlgebra.t.h +++ b/packages/minitensor/src/MiniTensor_LinearAlgebra.t.h @@ -39,6 +39,7 @@ // ************************************************************************ // @HEADER +#include "Kokkos_ArithTraits.hpp" #if !defined(MiniTensor_LinearAlgebra_t_h) #define MiniTensor_LinearAlgebra_t_h @@ -289,11 +290,7 @@ subtensor(Tensor const & A, Index const i, Index const j) // // Exponential map // -template -KOKKOS_INLINE_FUNCTION -Tensor -exp(Tensor const & A) -{ +template Tensor exp(Tensor const &A) { return exp_pade(A); } @@ -438,11 +435,9 @@ polynomial_coefficient(Index const order, Index const index) // // Padé approximant polynomial odd and even terms. // -template -KOKKOS_INLINE_FUNCTION +template std::pair, Tensor> -pade_polynomial_terms(Tensor const & A, Index const order) -{ +pade_polynomial_terms(Tensor const &A, Index const order) { Index const dimension = A.get_dimension(); @@ -549,11 +544,7 @@ binary_powering(Tensor const & A, Index const exponent) // \param A tensor // \return \f$ \exp A \f$ // -template -KOKKOS_INLINE_FUNCTION -Tensor -exp_pade(Tensor const & A) -{ +template Tensor exp_pade(Tensor const &A) { Index const dimension = A.get_dimension(); @@ -588,7 +579,7 @@ exp_pade(Tensor const & A) Tensor V; - boost::tie(U, V) = pade_polynomial_terms(A, order); + std::tie(U, V) = pade_polynomial_terms(A, order); B = inverse(V - U) * (U + V); @@ -810,7 +801,7 @@ log_eig_sym(Tensor const & A) Tensor D(dimension); - boost::tie(V, D) = eig_sym(A); + std::tie(V, D) = eig_sym(A); for (Index i = 0; i < dimension; ++i) { D(i, i) = std::log(D(i, i)); @@ -1159,11 +1150,8 @@ norm_off_diagonal(Tensor const & A) // \param A // \return \f$ (p,q) = arg max_{i,j} |a_{ij}| \f$ // -template -//KOKKOS_INLINE_FUNCTION -std::pair -arg_max_abs(Tensor const & A) -{ +template +std::pair arg_max_abs(Tensor const &A) { Index p = 0; Index q = 0; @@ -1193,11 +1181,8 @@ arg_max_abs(Tensor const & A) // \param A // \return \f$ (p,q) = arg max_{i \neq j} |a_{ij}| \f$ // -template -KOKKOS_INLINE_FUNCTION -std::pair -arg_max_off_diagonal(Tensor const & A) -{ +template +std::pair arg_max_off_diagonal(Tensor const &A) { Index p = 0; Index q = 1; @@ -1229,10 +1214,9 @@ namespace { // \param f, g, h where A = [f, g; 0, h] // \return \f$ A = USV^T\f$ // -template -boost::tuple, Tensor, Tensor> -svd_bidiagonal(T f, T g, T h) -{ +template +std::tuple, Tensor, Tensor> svd_bidiagonal(T f, T g, + T h) { T fa = std::abs(f); T ga = std::abs(g); T ha = std::abs(h); @@ -1312,7 +1296,7 @@ svd_bidiagonal(T f, T g, T h) Tensor S(s0, 0.0, 0.0, s1); Tensor V(cv, -sv, sv, cv); - return boost::make_tuple(U, S, V); + return std::make_tuple(U, S, V); } // @@ -1320,16 +1304,15 @@ svd_bidiagonal(T f, T g, T h) // \param A tensor // \return \f$ A = USV^T\f$ // -template -boost::tuple, Tensor, Tensor> -svd_2x2(Tensor const & A) -{ +template +std::tuple, Tensor, Tensor> +svd_2x2(Tensor const &A) { assert(A.get_dimension() == 2); // First compute a givens rotation to eliminate 1,0 entry in tensor T c = 1.0; T s = 0.0; - boost::tie(c, s) = givens(A(0,0), A(1,0)); + std::tie(c, s) = givens(A(0, 0), A(1, 0)); Tensor R(c, -s, s, c); @@ -1341,13 +1324,13 @@ svd_2x2(Tensor const & A) Tensor X(2), S(2), V(2); - boost::tie(X, S, V) = svd_bidiagonal(B(0,0), B(0,1), B(1,1)); + std::tie(X, S, V) = svd_bidiagonal(B(0, 0), B(0, 1), B(1, 1)); // Complete general 2x2 SVD with givens rotation calculated above Tensor U = transpose(R) * X; - return boost::make_tuple(U, S, V); + return std::make_tuple(U, S, V); } // @@ -1355,10 +1338,9 @@ svd_2x2(Tensor const & A) // \param A tensor // \return \f$ A = USV^T\f$ // -template -boost::tuple, Tensor, Tensor> -svd_NxN(Tensor const & A) -{ +template +std::tuple, Tensor, Tensor> +svd_NxN(Tensor const &A) { // Scale first T const norm_a = norm(A); @@ -1399,7 +1381,7 @@ svd_NxN(Tensor const & A) Index q = 0; - boost::tie(p,q) = arg_max_off_diagonal(S); + std::tie(p, q) = arg_max_off_diagonal(S); if (p > q) { std::swap(p, q); @@ -1412,7 +1394,7 @@ svd_NxN(Tensor const & A) Tensor L(2), D(2), R(2); - boost::tie(L, D, R) = svd_2x2(Spq); + std::tie(L, D, R) = svd_2x2(Spq); T const & cl = L(0,0); @@ -1456,12 +1438,12 @@ svd_NxN(Tensor const & A) Vector s(dimension); Tensor P(dimension); - boost::tie(s, P) = sort_permutation(diag(S)); + std::tie(s, P) = sort_permutation(diag(S)); S = scale * diag(s); U = U * P; V = V * P; - return boost::make_tuple(U, diag(diag(S)), transpose(V)); + return std::make_tuple(U, diag(diag(S)), transpose(V)); } } // anonymous namespace @@ -1471,10 +1453,9 @@ svd_NxN(Tensor const & A) // \param A tensor // \return \f$ A = USV^T\f$ // -template -boost::tuple, Tensor, Tensor> -svd(Tensor const & A) -{ +template +std::tuple, Tensor, Tensor> +svd(Tensor const &A) { Index const dimension = A.get_dimension(); @@ -1484,16 +1465,16 @@ svd(Tensor const & A) switch (dimension) { default: - boost::tie(U, S, V) = svd_NxN(A); + std::tie(U, S, V) = svd_NxN(A); break; case 2: - boost::tie(U, S, V) = svd_2x2(A); + std::tie(U, S, V) = svd_2x2(A); break; } - return boost::make_tuple(U, S, V); + return std::make_tuple(U, S, V); } // @@ -1519,8 +1500,8 @@ polar_rotation(Tensor const & A) T const tol_scale = 0.01; - T const - tol_conv = std::sqrt(dimension) * machine_epsilon(); + T const tol_conv = + Kokkos::ArithTraits::sqrt(dimension) * machine_epsilon(); Tensor X = A; @@ -1588,11 +1569,8 @@ polar_rotation(Tensor const & A) // \param A tensor (often a deformation-gradient-like tensor) // \return \f$ VR = A \f$ with \f$ R \in SO(N) \f$ and \f$ V \in SPD(N) \f$ // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_left(Tensor const & A) -{ +template +std::pair, Tensor> polar_left(Tensor const &A) { Tensor R = polar_rotation(A); @@ -1607,11 +1585,8 @@ polar_left(Tensor const & A) // \param A tensor (often a deformation-gradient-like tensor) // \return \f$ RU = A \f$ with \f$ R \in SO(N) \f$ and \f$ U \in SPD(N) \f$ // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_right(Tensor const & A) -{ +template +std::pair, Tensor> polar_right(Tensor const &A) { Tensor R = polar_rotation(A); @@ -1626,11 +1601,8 @@ polar_right(Tensor const & A) // \param F tensor (often a deformation-gradient-like tensor) // \return \f$ VR = F \f$ with \f$ R \in SO(3) \f$ and V SPD(3) // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_left_eig(Tensor const & F) -{ +template +std::pair, Tensor> polar_left_eig(Tensor const &F) { assert(F.get_dimension() == 3); // set up return tensors @@ -1654,7 +1626,7 @@ polar_left_eig(Tensor const & F) Tensor eVec(3); - boost::tie(eVec, eVal) = eig_spd(b); + std::tie(eVec, eVal) = eig_spd(b); // compute sqrt() and inv(sqrt()) of eigenvalues Tensor @@ -1683,11 +1655,8 @@ polar_left_eig(Tensor const & F) // \param F tensor (often a deformation-gradient-like tensor) // \return \f$ RU = F \f$ with \f$ R \in SO(3) \f$ and U SPD(3) // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -polar_right_eig(Tensor const & F) -{ +template +std::pair, Tensor> polar_right_eig(Tensor const &F) { Index const dimension = F.get_dimension(); @@ -1714,7 +1683,7 @@ polar_right_eig(Tensor const & F) Tensor eVec(dimension); - boost::tie(eVec, eVal) = eig_spd(C); + std::tie(eVec, eVal) = eig_spd(C); // compute sqrt() and inv(sqrt()) of eigenvalues Tensor @@ -1744,17 +1713,16 @@ polar_right_eig(Tensor const & F) // \param F tensor (often a deformation-gradient-like tensor) // \return \f$ VR = F \f$ with \f$ R \in SO(N) \f$ and V SPD(N), and log V // -template -boost::tuple, Tensor, Tensor> -polar_left_logV(Tensor const & F) -{ +template +std::tuple, Tensor, Tensor> +polar_left_logV(Tensor const &F) { Index const dimension = F.get_dimension(); Tensor X(dimension), S(dimension), Y(dimension); - boost::tie(X, S, Y) = svd(F); + std::tie(X, S, Y) = svd(F); Tensor R = X * transpose(Y); @@ -1772,13 +1740,12 @@ polar_left_logV(Tensor const & F) Tensor v = X * s * transpose(X); - return boost::make_tuple(V, R, v); + return std::make_tuple(V, R, v); } -template -boost::tuple, Tensor, Tensor> -polar_left_logV_eig(Tensor const & F) -{ +template +std::tuple, Tensor, Tensor> +polar_left_logV_eig(Tensor const &F) { Index const dimension = F.get_dimension(); @@ -1788,7 +1755,7 @@ polar_left_logV_eig(Tensor const & F) Tensor V(dimension), D(dimension); - boost::tie(V, D) = eig_sym(b); + std::tie(V, D) = eig_sym(b); Tensor DQ(dimension, Filler::ZEROS), DI(dimension, Filler::ZEROS), DL(dimension, Filler::ZEROS); @@ -1808,7 +1775,7 @@ polar_left_logV_eig(Tensor const & F) Tensor const x = V * dot_t(DL, V); - return boost::make_tuple(X, R, x); + return std::make_tuple(X, R, x); } // @@ -1816,10 +1783,9 @@ polar_left_logV_eig(Tensor const & F) // \param F tensor (often a deformation-gradient-like tensor) // \return \f$ VR = F \f$ with \f$ R \in SO(N) \f$ and V SPD(N), and log V // -template -boost::tuple, Tensor, Tensor> -polar_left_logV_lame(Tensor const & F) -{ +template +std::tuple, Tensor, Tensor> +polar_left_logV_lame(Tensor const &F) { Index const dimension = F.get_dimension(); @@ -1832,7 +1798,7 @@ polar_left_logV_lame(Tensor const & F) // get eigenvalues/eigenvectors Tensor eVal(dimension); Tensor eVec(dimension); - boost::tie(eVec,eVal) = eig_spd_cos(b); + std::tie(eVec, eVal) = eig_spd_cos(b); // compute sqrt() and inv(sqrt()) of eigenvalues Tensor x = zero(3); @@ -1853,7 +1819,7 @@ polar_left_logV_lame(Tensor const & F) v = eVec*lnx*transpose(eVec); R = Vinv*F; - return boost::make_tuple(V,R,v); + return std::make_tuple(V, R, v); } // @@ -1888,11 +1854,8 @@ bch(Tensor const & x, Tensor const & y) // \param \f$ A = [f, g; g, h] \in S(2) \f$ // \return \f$ c, s \rightarrow [c, -s; s, c]\f diagonalizes A$ // -template -KOKKOS_INLINE_FUNCTION -std::pair -schur_sym(T const f, T const g, T const h) -{ +template +std::pair schur_sym(T const f, T const g, T const h) { T c = 1.0; T s = 0.0; @@ -1916,11 +1879,7 @@ schur_sym(T const f, T const g, T const h) // \param a, b // \return c, s // -template -KOKKOS_INLINE_FUNCTION -std::pair -givens(T const & a, T const & b) -{ +template std::pair givens(T const &a, T const &b) { T c = 1.0; T s = 0.0; @@ -1947,11 +1906,8 @@ namespace { // \return V eigenvectors, D eigenvalues in diagonal Matlab-style // See algorithm 8.4.2 in Matrix Computations, Golub & Van Loan 1996 // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_sym_NxN(Tensor const & A) -{ +template +std::pair, Tensor> eig_sym_NxN(Tensor const &A) { Tensor D = sym(A); @@ -1984,7 +1940,7 @@ eig_sym_NxN(Tensor const & A) Index q = 0; - boost::tie(p,q) = arg_max_off_diagonal(D); + std::tie(p, q) = arg_max_off_diagonal(D); if (p > q) { std::swap(p,q); } @@ -2002,7 +1958,7 @@ eig_sym_NxN(Tensor const & A) T c, s; - boost::tie(c, s) = schur_sym(f, g, h); + std::tie(c, s) = schur_sym(f, g, h); // Apply Givens rotation to matrices // that are converging to eigenvalues and eigenvectors @@ -2018,7 +1974,7 @@ eig_sym_NxN(Tensor const & A) Vector d(dimension); Tensor P(dimension); - boost::tie(d, P) = sort_permutation(diag(D)); + std::tie(d, P) = sort_permutation(diag(D)); D = diag(d); V = V * P; @@ -2030,11 +1986,8 @@ eig_sym_NxN(Tensor const & A) // \param A tensor // \return V eigenvectors, D eigenvalues in diagonal Matlab-style // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_sym_2x2(Tensor const & A) -{ +template +std::pair, Tensor> eig_sym_2x2(Tensor const &A) { assert(A.get_dimension() == 2); T const f = A(0,0); @@ -2093,15 +2046,15 @@ eig_sym_2x2(Tensor const & A) T c, s; - boost::tie(c, s) = schur_sym(f, g, h); + std::tie(c, s) = schur_sym(f, g, h); Tensor V(c, -s, s, c); if (swap_diag == true) { // swap eigenvectors if eigenvalues were swapped - std::swap(V(0,0), V(0,1)); - std::swap(V(1,0), V(1,1)); + std::swap(V(0, 0), V(0, 1)); + std::swap(V(1, 0), V(1, 1)); } return std::make_pair(V, D); @@ -2114,11 +2067,8 @@ eig_sym_2x2(Tensor const & A) // \param A tensor // \return V eigenvectors, D eigenvalues in diagonal Matlab-style // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_sym(Tensor const & A) -{ +template +std::pair, Tensor> eig_sym(Tensor const &A) { Index const dimension = A.get_dimension(); @@ -2128,11 +2078,11 @@ eig_sym(Tensor const & A) switch (dimension) { default: - boost::tie(V, D) = eig_sym_NxN(A); + std::tie(V, D) = eig_sym_NxN(A); break; case 2: - boost::tie(V, D) = eig_sym_2x2(A); + std::tie(V, D) = eig_sym_2x2(A); break; } @@ -2145,11 +2095,8 @@ eig_sym(Tensor const & A) // \param A tensor // \return V eigenvectors, D eigenvalues in diagonal Matlab-style // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_spd(Tensor const & A) -{ +template +std::pair, Tensor> eig_spd(Tensor const &A) { return eig_sym(A); } @@ -2158,11 +2105,8 @@ eig_spd(Tensor const & A) // \param A tensor // \return V eigenvectors, D eigenvalues in diagonal Matlab-style // -template -KOKKOS_INLINE_FUNCTION -std::pair, Tensor> -eig_spd_cos(Tensor const & A) -{ +template +std::pair, Tensor> eig_spd_cos(Tensor const &A) { Index const dimension = A.get_dimension(); @@ -2415,11 +2359,8 @@ eig_spd_cos(Tensor const & A) // \return G Cholesky factor A = GG^T // \return completed (bool) algorithm ran to completion // -template -KOKKOS_INLINE_FUNCTION -std::pair, bool > -cholesky(Tensor const & A) -{ +template +std::pair, bool> cholesky(Tensor const &A) { Tensor G = sym(A); @@ -2465,22 +2406,18 @@ namespace { // // // -template -KOKKOS_INLINE_FUNCTION -std::pair, RHS> -identity_precon(Tensor const & A, RHS const & B) -{ +template +std::pair, RHS> identity_precon(Tensor const &A, + RHS const &B) { return std::make_pair(A, B); } // // // -template -KOKKOS_INLINE_FUNCTION -std::pair, RHS> -diagonal_precon(Tensor const & A, RHS const & B) -{ +template +std::pair, RHS> diagonal_precon(Tensor const &A, + RHS const &B) { Vector const d = diag(A); @@ -2496,11 +2433,8 @@ diagonal_precon(Tensor const & A, RHS const & B) // // // -template -KOKKOS_INLINE_FUNCTION -std::pair, RHS> -maxabsrow_precon(Tensor const & A, RHS & B) -{ +template +std::pair, RHS> maxabsrow_precon(Tensor const &A, RHS &B) { Index const dimension = A.get_dimension(); @@ -2519,11 +2453,9 @@ maxabsrow_precon(Tensor const & A, RHS & B) // // // -template -KOKKOS_INLINE_FUNCTION -std::pair, RHS> -precon(PreconditionerType const pt, Tensor const & A, RHS const & B) -{ +template +std::pair, RHS> precon(PreconditionerType const pt, + Tensor const &A, RHS const &B) { switch (pt) { default: MT_ERROR_EXIT("Unknown preconditioner type."); @@ -2550,18 +2482,15 @@ precon(PreconditionerType const pt, Tensor const & A, RHS const & B) // as it just Gauss-Jordan elimination. It is intended to be used in // conjunction with Kokkos to take advantage of thread parallelism. // -template -KOKKOS_INLINE_FUNCTION -RHS -solve(Tensor const & A, RHS const & b, PreconditionerType const pt) -{ +template +RHS solve(Tensor const &A, RHS const &b, PreconditionerType const pt) { Tensor PA; RHS Pb; - boost::tie(PA, Pb) = precon(pt, A, b); + std::tie(PA, Pb) = precon(pt, A, b); return solve_full_pivot(PA, Pb); } diff --git a/packages/minitensor/src/MiniTensor_Mechanics.t.h b/packages/minitensor/src/MiniTensor_Mechanics.t.h index d9d4ec23d23c..61df4c1950bc 100644 --- a/packages/minitensor/src/MiniTensor_Mechanics.t.h +++ b/packages/minitensor/src/MiniTensor_Mechanics.t.h @@ -663,7 +663,7 @@ check_strong_ellipticity(Tensor4 const & A) Tensor D; - boost::tie(V, D) = eig_sym(Q); + std::tie(V, D) = eig_sym(Q); curr_eigenvalue = D(dimension - 1, dimension - 1); diff --git a/packages/minitensor/src/MiniTensor_Solvers.t.h b/packages/minitensor/src/MiniTensor_Solvers.t.h index 2cf4dd758d2a..ec6a37b34b2b 100644 --- a/packages/minitensor/src/MiniTensor_Solvers.t.h +++ b/packages/minitensor/src/MiniTensor_Solvers.t.h @@ -564,7 +564,7 @@ step(Tensor const & Hessian, Vector const & gradient) bool is_posdef{false}; - boost::tie(L, is_posdef) = cholesky(K); + std::tie(L, is_posdef) = cholesky(K); if (is_posdef == false) { MT_ERROR_EXIT("Trust region subproblem encountered singular Hessian."); @@ -635,7 +635,7 @@ step(Tensor const & Hessian, Vector const & gradient) bool is_posdef{false}; - boost::tie(L, is_posdef) = cholesky(K); + std::tie(L, is_posdef) = cholesky(K); if (is_posdef == false) { MT_ERROR_EXIT("Trust region subproblem encountered singular Hessian."); diff --git a/packages/minitensor/src/MiniTensor_Storage.h b/packages/minitensor/src/MiniTensor_Storage.h index 56b00c903253..5fa993169ec5 100644 --- a/packages/minitensor/src/MiniTensor_Storage.h +++ b/packages/minitensor/src/MiniTensor_Storage.h @@ -39,6 +39,7 @@ // ************************************************************************ // @HEADER +#include "Kokkos_Macros.hpp" #if !defined(MiniTensor_Storage_h) #define MiniTensor_Storage_h @@ -49,12 +50,12 @@ namespace minitensor { /// Set to constant value if not dynamic template struct dimension_const { - static Index const value = C; + static constexpr Index value = C; }; template struct dimension_const { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; /// Validate dimension @@ -64,13 +65,13 @@ struct check_static { #if defined(KOKKOS_ENABLE_CUDA) // Empty #else - static Index const - maximum_dimension = static_cast(std::numeric_limits::digits); + static constexpr Index maximum_dimension = + static_cast(std::numeric_limits::digits); static_assert(D > maximum_dimension, "Dimension is too large"); #endif - static Index const value = D; + static constexpr Index value = D; }; template @@ -91,132 +92,105 @@ check_dynamic(Index const dimension) /// Integer power template restricted to orders defined below template struct dimension_power { - static Index const value = 0; + static constexpr Index value = 0; }; template struct dimension_power { - static Index const value = D; + static constexpr Index value = D; }; template struct dimension_power { - static Index const value = D * D; + static constexpr Index value = D * D; }; template struct dimension_power { - static Index const value = D * D * D; + static constexpr Index value = D * D * D; }; template struct dimension_power { - static Index const value = D * D * D * D; + static constexpr Index value = D * D * D * D; }; /// Integer square for manipulations between 2nd and 4rd-order tensors. template struct dimension_square { - static Index const value = 0; + static constexpr Index value = 0; }; template<> struct dimension_square { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; -template<> -struct dimension_square<1> { - static Index const value = 1; -}; +template <> struct dimension_square<1> { static constexpr Index value = 1; }; -template<> -struct dimension_square<2> { - static Index const value = 4; -}; +template <> struct dimension_square<2> { static constexpr Index value = 4; }; -template<> -struct dimension_square<3> { - static Index const value = 9; -}; +template <> struct dimension_square<3> { static constexpr Index value = 9; }; -template<> -struct dimension_square<4> { - static Index const value = 16; -}; +template <> struct dimension_square<4> { static constexpr Index value = 16; }; /// Integer square root template restricted to dimensions defined below. /// Useful for constructing a 2nd-order tensor from a 4th-order /// tensor with static storage. -template -struct dimension_sqrt { - static Index const value = 0; -}; +template struct dimension_sqrt { static constexpr Index value = 0; }; template<> struct dimension_sqrt { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; -template<> -struct dimension_sqrt<1> { - static Index const value = 1; -}; +template <> struct dimension_sqrt<1> { static constexpr Index value = 1; }; -template<> -struct dimension_sqrt<4> { - static Index const value = 2; -}; +template <> struct dimension_sqrt<4> { static constexpr Index value = 2; }; -template<> -struct dimension_sqrt<9> { - static Index const value = 3; -}; +template <> struct dimension_sqrt<9> { static constexpr Index value = 3; }; -template<> -struct dimension_sqrt<16> { - static Index const value = 4; -}; +template <> struct dimension_sqrt<16> { static constexpr Index value = 4; }; /// Manipulation of static and dynamic dimensions. template struct dimension_add { - static Index const value = N + P; + static constexpr Index value = N + P; }; template struct dimension_add { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; template struct dimension_subtract { - static Index const value = N - P; + static constexpr Index value = N - P; }; template struct dimension_subtract { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; template struct dimension_product { - static Index const value = N * P; + static constexpr Index value = N * P; }; template struct dimension_product { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; template struct dimension_product { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; template<> struct dimension_product { - static Index const value = DYNAMIC; + static constexpr Index value = DYNAMIC; }; /// @@ -240,13 +214,12 @@ class Storage bool IS_DYNAMIC = false; + KOKKOS_INLINE_FUNCTION Storage() { } - explicit - Storage(Index const number_entries) - { + explicit KOKKOS_INLINE_FUNCTION Storage(Index const number_entries) { resize(number_entries); } @@ -255,6 +228,7 @@ class Storage Storage & operator=(Storage const & s) = delete; + KOKKOS_INLINE_FUNCTION ~Storage() { } @@ -316,12 +290,7 @@ class Storage return &storage_[0]; } - static constexpr - Index - static_size() - { - return N; - } + static KOKKOS_INLINE_FUNCTION constexpr Index static_size() { return N; } private: @@ -353,13 +322,12 @@ class Storage bool IS_STATIC = false; + KOKKOS_INLINE_FUNCTION Storage() { } - explicit - Storage(Index const number_entries) - { + explicit KOKKOS_INLINE_FUNCTION Storage(Index const number_entries) { resize(number_entries); } @@ -368,6 +336,7 @@ class Storage Storage & operator=(Storage const & s) = delete; + KOKKOS_INLINE_FUNCTION ~Storage() { clear(); @@ -432,12 +401,7 @@ class Storage return storage_; } - static constexpr - Index - static_size() - { - return 0; - } + static KOKKOS_INLINE_FUNCTION constexpr Index static_size() { return 0; } private: diff --git a/packages/minitensor/src/MiniTensor_Tensor.i.h b/packages/minitensor/src/MiniTensor_Tensor.i.h index e92855003ed8..97e07d3aa085 100644 --- a/packages/minitensor/src/MiniTensor_Tensor.i.h +++ b/packages/minitensor/src/MiniTensor_Tensor.i.h @@ -1646,25 +1646,31 @@ transpose(Tensor const & A) Tensor B = A; + auto my_swap = [&](T &a, T &b) { + T c = a; + a = b; + b = c; + }; + switch (dimension) { default: for (Index i = 0; i < dimension; ++i) { for (Index j = i + 1; j < dimension; ++j) { - std::swap(B(i, j), B(j, i)); + my_swap(B(i, j), B(j, i)); } } break; case 3: - std::swap(B(0, 1), B(1, 0)); - std::swap(B(0, 2), B(2, 0)); + my_swap(B(0, 1), B(1, 0)); + my_swap(B(0, 2), B(2, 0)); - std::swap(B(1, 2), B(2, 1)); + my_swap(B(1, 2), B(2, 1)); break; case 2: - std::swap(B(0, 1), B(1, 0)); + my_swap(B(0, 1), B(1, 0)); break; } diff --git a/packages/minitensor/src/MiniTensor_TensorBase.i.h b/packages/minitensor/src/MiniTensor_TensorBase.i.h index cc2b782cd7d9..572ffd60b702 100644 --- a/packages/minitensor/src/MiniTensor_TensorBase.i.h +++ b/packages/minitensor/src/MiniTensor_TensorBase.i.h @@ -399,6 +399,8 @@ TensorBase::fill(Filler const value) } break; +#ifdef KOKKOS_ACTIVE_EXECUTION_MEMORY_SPACE_HOST + case Filler::RANDOM: for (Index i = 0; i < number_components; ++i) { auto & entry = (*this)[i]; @@ -423,6 +425,8 @@ TensorBase::fill(Filler const value) } break; +#endif + case Filler::NANS: for (Index i = 0; i < number_components; ++i) { auto & entry = (*this)[i]; @@ -432,7 +436,8 @@ TensorBase::fill(Filler const value) break; default: - MT_ERROR_EXIT("Unknown specification of value for filling components."); + MT_ERROR_EXIT("Unknown or undefined (in execution space) specification of " + "value for filling components."); break; } diff --git a/packages/minitensor/src/MiniTensor_Utilities.h b/packages/minitensor/src/MiniTensor_Utilities.h index 565536f1ccaa..7e149ba65a5f 100644 --- a/packages/minitensor/src/MiniTensor_Utilities.h +++ b/packages/minitensor/src/MiniTensor_Utilities.h @@ -141,26 +141,17 @@ tau(); /// Random number generation. Uniform distribution U(-1,1) /// which is the Teuchos default (!). /// -template -KOKKOS_INLINE_FUNCTION -typename Sacado::ScalarType::type -random(); +template typename Sacado::ScalarType::type random(); /// /// Random number generation. Uniform distribution U(0,1). /// -template -KOKKOS_INLINE_FUNCTION -typename Sacado::ScalarType::type -random_uniform(); +template typename Sacado::ScalarType::type random_uniform(); /// /// Random number generation. Normal distribution N(0,1). /// -template -KOKKOS_INLINE_FUNCTION -typename Sacado::ScalarType::type -random_normal(); +template typename Sacado::ScalarType::type random_normal(); /// /// Fill all levels of AD to specified constant. diff --git a/packages/minitensor/src/MiniTensor_Utilities.i.h b/packages/minitensor/src/MiniTensor_Utilities.i.h index 60b7a7b52d1a..dbdbedfe86b3 100644 --- a/packages/minitensor/src/MiniTensor_Utilities.i.h +++ b/packages/minitensor/src/MiniTensor_Utilities.i.h @@ -187,11 +187,7 @@ tau() // // Random number generation. Teuchos [-1,1] // -template -KOKKOS_INLINE_FUNCTION -typename Sacado::ScalarType::type -random() -{ +template typename Sacado::ScalarType::type random() { using S = typename Sacado::ScalarType::type; return Teuchos::ScalarTraits().random(); } @@ -199,11 +195,7 @@ random() // // Uniform [0,1] random number generation. // -template -KOKKOS_INLINE_FUNCTION -typename Sacado::ScalarType::type -random_uniform() -{ +template typename Sacado::ScalarType::type random_uniform() { using S = typename Sacado::ScalarType::type; return static_cast(0.5 * random() + 0.5); } @@ -211,11 +203,7 @@ random_uniform() // // Normal N(0,1) random number generation. // -template -KOKKOS_INLINE_FUNCTION -typename Sacado::ScalarType::type -random_normal() -{ +template typename Sacado::ScalarType::type random_normal() { using S = typename Sacado::ScalarType::type; S const diff --git a/packages/minitensor/src/MiniTensor_Vector.h b/packages/minitensor/src/MiniTensor_Vector.h index 7b2c57ed9aec..9455dc4d46b5 100644 --- a/packages/minitensor/src/MiniTensor_Vector.h +++ b/packages/minitensor/src/MiniTensor_Vector.h @@ -506,7 +506,6 @@ unit(Vector const & u); /// \return v, beta /// template -KOKKOS_INLINE_FUNCTION std::pair, T> house(Vector const & x); diff --git a/packages/minitensor/src/MiniTensor_Vector.i.h b/packages/minitensor/src/MiniTensor_Vector.i.h index 0f90b39c3e5a..ebe5e5545272 100644 --- a/packages/minitensor/src/MiniTensor_Vector.i.h +++ b/packages/minitensor/src/MiniTensor_Vector.i.h @@ -39,6 +39,7 @@ // ************************************************************************ // @HEADER +#include "Kokkos_ArithTraits.hpp" #if !defined(MiniTensor_Vector_i_h) #define MiniTensor_Vector_i_h @@ -802,6 +803,8 @@ KOKKOS_INLINE_FUNCTION T norm_infinity(Vector const & u) { + using KAT = Kokkos::ArithTraits; + Index const dimension = u.get_dimension(); @@ -812,16 +815,16 @@ norm_infinity(Vector const & u) default: for (Index i = 0; i < dimension; ++i) { - s = std::max(s, std::abs(u(i))); + s = max(KAT::abs(u(i)), s); } break; case 3: - s = std::max(std::max(std::abs(u(0)), std::abs(u(1))), std::abs(u(2))); + s = max(max(KAT::abs(u(0)), KAT::abs(u(1))), KAT::abs(u(2))); break; case 2: - s = std::max(std::abs(u(0)), std::abs(u(1))); + s = max(KAT::abs(u(0)), KAT::abs(u(1))); break; } @@ -842,11 +845,8 @@ unit(Vector const & u) // // Compute Householder vector // -template -KOKKOS_INLINE_FUNCTION -std::pair, T> -house(Vector const & x) -{ +template +std::pair, T> house(Vector const &x) { Vector v = x; diff --git a/packages/minitensor/test/test_01.cc b/packages/minitensor/test/test_01.cc index 975d36871f3c..fa0ba3f9701d 100644 --- a/packages/minitensor/test/test_01.cc +++ b/packages/minitensor/test/test_01.cc @@ -901,7 +901,7 @@ TEST(MiniTensor, SymmetricEigen) Tensor V(3); Tensor D(3); - boost::tie(V, D) = eig_sym(A); + std::tie(V, D) = eig_sym(A); ASSERT_LE(std::abs(D(0, 0) - 1.1), machine_epsilon()); ASSERT_LE(std::abs(D(1, 1) - 1.0), machine_epsilon()); @@ -921,7 +921,7 @@ TEST(MiniTensor, LeftPolarDecomposition) Tensor V(3); Tensor R(3); - boost::tie(V, R) = polar_left(F); + std::tie(V, R) = polar_left(F); Real const error_x = norm(V - X) / norm(X); @@ -1031,7 +1031,7 @@ TEST(MiniTensor, PolarLeftLog) Tensor V(3), R(3), v(3); - boost::tie(V, R, v) = polar_left_logV(F); + std::tie(V, R, v) = polar_left_logV(F); Real const error = norm(v - x) / norm(x); @@ -1084,7 +1084,7 @@ TEST(MiniTensor, SVD2x2) Tensor U(2), S(2), V(2); - boost::tie(U, S, V) = svd(A); + std::tie(U, S, V) = svd(A); Tensor B = U * S * transpose(V); @@ -1099,7 +1099,7 @@ TEST(MiniTensor, SVD3x3) Tensor U(3), S(3), V(3); - boost::tie(U, S, V) = svd(A); + std::tie(U, S, V) = svd(A); Tensor const B = U * S * transpose(V); @@ -1115,7 +1115,7 @@ TEST(MiniTensor, SVD3x3Fad) Tensor> U(3), S(3), V(3); - boost::tie(U, S, V) = svd(A); + std::tie(U, S, V) = svd(A); Tensor> const B = U * S * transpose(V); @@ -1183,7 +1183,7 @@ TEST(MiniTensor, SymmetricEigen2x2) Tensor V(2), D(2); - boost::tie(V, D) = eig_sym(A); + std::tie(V, D) = eig_sym(A); Tensor const B = V * D * transpose(V); @@ -1198,7 +1198,7 @@ TEST(MiniTensor, SymmetricEigen3x3) Tensor V(3), D(3); - boost::tie(V, D) = eig_sym(A); + std::tie(V, D) = eig_sym(A); Tensor const B = V * D * transpose(V); @@ -1213,11 +1213,11 @@ TEST(MiniTensor, Polar3x3) Tensor R(3), U(3); - boost::tie(R, U) = polar_right(A); + std::tie(R, U) = polar_right(A); Tensor X(3), D(3), Y(3); - boost::tie(X, D, Y) = svd(A); + std::tie(X, D, Y) = svd(A); Tensor const B = R - X * transpose(Y) + U - Y * D * transpose(Y); @@ -1234,7 +1234,7 @@ TEST(MiniTensor, Cholesky) bool is_spd; - boost::tie(G, is_spd) = cholesky(A); + std::tie(G, is_spd) = cholesky(A); Tensor const B(1.0, 0.0, 0.0, 1.0, 2.0, 0.0, 1.0, 1.0, 1.0); From 189fe360cfb17acd95eed6013f1cdb895fbcd4fd Mon Sep 17 00:00:00 2001 From: Chris Siefert Date: Fri, 15 Oct 2021 10:57:58 -0600 Subject: [PATCH 07/13] TrilinosCouplings: Adding 2D anisotropic diffusion --- .../examples/scaling/CMakeLists.txt | 1 + ...ouplings_IntrepidPoissonExampleHelpers.cpp | 16 +++++++ ...ouplings_IntrepidPoissonExampleHelpers.hpp | 5 +++ .../scaling/example_Poisson2D_pn_tpetra.cpp | 44 ++++++++++++++++--- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/packages/trilinoscouplings/examples/scaling/CMakeLists.txt b/packages/trilinoscouplings/examples/scaling/CMakeLists.txt index 0145d4f9df78..d579fca201ad 100644 --- a/packages/trilinoscouplings/examples/scaling/CMakeLists.txt +++ b/packages/trilinoscouplings/examples/scaling/CMakeLists.txt @@ -172,6 +172,7 @@ IF(${PACKAGE_NAME}_ENABLE_Epetra AND ${PACKAGE_NAME}_ENABLE_EpetraExt AND Example_Poisson2D_pn_tpetra SOURCES example_Poisson2D_pn_tpetra.cpp TrilinosCouplings_Pamgen_Utils.cpp + TrilinosCouplings_IntrepidPoissonExampleHelpers.cpp COMM mpi ) diff --git a/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.cpp b/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.cpp index 7cec51bec895..dcab796e5b84 100644 --- a/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.cpp +++ b/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.cpp @@ -113,6 +113,8 @@ double materialTensorOffDiagonalValue_; Matrix3 rotation_, strength_,diff_total_; +std::vector matrix2D_; + bool useDiffusionMatrix() { return use_diffusion_; } @@ -136,6 +138,20 @@ const std::vector& getDiffusionMatrix() { +const std::vector& getDiffusionMatrix2D() { + // Gets the x/y sub-matrix + if(!use_diffusion_) + throw std::runtime_error("setDiffusionRotationStrength has not been called"); + matrix2D_.resize(4); + matrix2D_[0] = diff_total_(0,0); + matrix2D_[1] = diff_total_(0,1); + matrix2D_[2] = diff_total_(1,0); + matrix2D_[3] = diff_total_(1,1); + + return matrix2D_; +} + + void setDiffusionRotationAndStrength(const std::vector& theta, const std::vector& diagonal) { rotation_ = Z_Rotation(theta[2]*M_PI/180.0) * Y_Rotation(theta[1]*M_PI/180.0) * X_Rotation(theta[0]*M_PI/180.0); diff --git a/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.hpp b/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.hpp index 35f7e680cab8..5daf4cda0bd5 100644 --- a/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.hpp +++ b/packages/trilinoscouplings/examples/scaling/TrilinosCouplings_IntrepidPoissonExampleHelpers.hpp @@ -110,6 +110,11 @@ void setDiffusionRotationAndStrength(const std::vector& diff_rotation_an /// \brief Gets the diffusion Tensor const std::vector & getDiffusionMatrix(); + +/// \brief Gets the 2D diffusion Tensor (for 2D problems) +const std::vector & getDiffusionMatrix2D(); + + /// \brief Use the diffusion tensor rather than the off-diagonal values bool useDiffusionMatrix(); diff --git a/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp b/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp index 3eefdc36f7ca..f25806937d62 100644 --- a/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp +++ b/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp @@ -87,6 +87,8 @@ // TrilinosCouplings includes #include "TrilinosCouplings_config.h" #include "TrilinosCouplings_Pamgen_Utils.hpp" +#include "TrilinosCouplings_IntrepidPoissonExampleHelpers.hpp" + // Intrepid includes #include "Intrepid_FunctionSpaceTools.hpp" @@ -469,6 +471,25 @@ int main(int argc, char *argv[]) { clp.setOption ("seed", &randomSeed, "Random Seed."); + // Material diffusion strength and rotation (in 3D) + // We'll get the 2D version of this later + std::vector diff_rotation_angle {0.0, 0.0, 0.0}; + std::vector diff_strength {1.0, 1.0, 1.0}; + for(int i=0; i<3; i++) { + char letter[4] = "xyz"; + char str1[80], str2[80]; + // Rotation + sprintf(str1,"rot_%c_angle",letter[i]); + sprintf(str2,"Rotation around %c axis, in degrees",letter[i]); + clp.setOption(str1,&diff_rotation_angle[i],str2); + + // Strength + sprintf(str1,"strength_%c",letter[i]); + sprintf(str2,"Strength of pre-rotation %c-diffusion",letter[i]); + clp.setOption(str1,&diff_strength[i],str2); + } + + switch (clp.parse(argc, argv)) { case Teuchos::CommandLineProcessor::PARSE_HELP_PRINTED: return EXIT_SUCCESS; case Teuchos::CommandLineProcessor::PARSE_ERROR: @@ -499,6 +520,15 @@ int main(int argc, char *argv[]) { } + // Diffusion Tensor + ::TrilinosCouplings::IntrepidPoissonExample::setDiffusionRotationAndStrength(diff_rotation_angle, diff_strength); + if(MyPID == 0) { + const std::vector & A = ::TrilinosCouplings::IntrepidPoissonExample::getDiffusionMatrix2D(); + std::cout<<"[ "< diffusion_tensor; + template void materialTensor(Scalar material[][2], const Scalar& x, const Scalar& y) { - - material[0][0] = 1.; - material[0][1] = 0.; + if(diffusion_tensor.size() == 0) + diffusion_tensor = ::TrilinosCouplings::IntrepidPoissonExample::getDiffusionMatrix2D(); + material[0][0] = (Scalar)diffusion_tensor[0]; + material[0][1] = (Scalar)diffusion_tensor[1]; // - material[1][0] = 0.; - material[1][1] = 1.; + material[1][0] = (Scalar)diffusion_tensor[2]; + material[1][1] = (Scalar)diffusion_tensor[3]; } /**********************************************************************************/ From fd27eb3418b60fc65ab91dabb1317cd600971a8a Mon Sep 17 00:00:00 2001 From: Chris Siefert Date: Mon, 18 Oct 2021 20:57:19 -0600 Subject: [PATCH 08/13] MueLu: Adding signed SA style dropping option --- .../MueLu_CoalesceDropFactory_def.hpp | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp b/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp index c4b903035125..9af45faff43f 100644 --- a/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp +++ b/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp @@ -132,7 +132,8 @@ namespace MueLu { { typedef Teuchos::StringToIntegralParameterEntryValidator validatorType; - validParamList->getEntry("aggregation: drop scheme").setValidator(rcp(new validatorType(Teuchos::tuple("classical", "distance laplacian","signed classical","block diagonal","block diagonal classical","block diagonal distance laplacian","block diagonal signed classical","block diagonal colored signed classical"), "aggregation: drop scheme"))); + // "signed classical" is the Ruge-Stuben style (relative to max off-diagonal), "sign classical sa" is the signed version of the sa criterion (relative to the diagonal values) + validParamList->getEntry("aggregation: drop scheme").setValidator(rcp(new validatorType(Teuchos::tuple("signed classical sa","classical", "distance laplacian","signed classical","block diagonal","block diagonal classical","block diagonal distance laplacian","block diagonal signed classical","block diagonal colored signed classical"), "aggregation: drop scheme"))); } SET_VALID_ENTRY("aggregation: distance laplacian algo"); @@ -174,7 +175,7 @@ namespace MueLu { void CoalesceDropFactory::Build(Level ¤tLevel) const { FactoryMonitor m(*this, "Build", currentLevel); - + typedef Teuchos::ScalarTraits STS; typedef typename STS::magnitudeType real_type; typedef Xpetra::MultiVector RealValuedMultiVector; @@ -196,7 +197,8 @@ namespace MueLu { bool use_block_algorithm=false; LO interleaved_blocksize = as(pL.get("aggregation: block diagonal: interleaved blocksize")); - bool useSignedClassical = false; + bool useSignedClassicalRS = false; + bool useSignedClassicalSA = false; bool generateColoringGraph = false; // NOTE: If we're doing blockDiagonal, we'll not want to do rowSum twice (we'll do it @@ -210,8 +212,13 @@ namespace MueLu { Coords = Get< RCP >(currentLevel, "Coordinates"); A = realA; } + else if(algo == "signed classical sa") { + useSignedClassicalSA = true; + algo = "classical"; + A = realA; + } else if(algo == "signed classical" || algo == "block diagonal colored signed classical" || algo == "block diagonal signed classical") { - useSignedClassical = true; + useSignedClassicalRS = true; // if(realA->GetFixedBlockSize() > 1) { RCP BlockNumber = Get >(currentLevel, "BlockNumber"); // Ghost the column block numbers if we need to @@ -378,12 +385,14 @@ namespace MueLu { const typename STS::magnitudeType dirichletThreshold = STS::magnitude(as(pL.get("aggregation: Dirichlet threshold"))); - // NOTE: We don't support signed classical with cut drop at present - TEUCHOS_TEST_FOR_EXCEPTION(useSignedClassical && classicalAlgo != defaultAlgo, Exceptions::RuntimeError, "\"aggregation: classical algo\" != default is not supported for scalled classical aggregation"); - + // NOTE: We don't support signed classical RS or SA with cut drop at present + TEUCHOS_TEST_FOR_EXCEPTION(useSignedClassicalRS && classicalAlgo != defaultAlgo, Exceptions::RuntimeError, "\"aggregation: classical algo\" != default is not supported for scalled classical aggregation"); + TEUCHOS_TEST_FOR_EXCEPTION(useSignedClassicalSA && classicalAlgo != defaultAlgo, Exceptions::RuntimeError, "\"aggregation: classical algo\" != default is not supported for scalled classical sa aggregation"); GO numDropped = 0, numTotal = 0; std::string graphType = "unamalgamated"; //for description purposes only + + /************************** RS or SA-style Classical Dropping (and variants) **************************/ if (algo == "classical") { if (predrop_ == null) { // ap: this is a hack: had to declare predrop_ as mutable @@ -404,7 +413,7 @@ namespace MueLu { // At this points we either have // (predrop_ != null) // Therefore, it is sufficient to check only threshold - if (A->GetFixedBlockSize() == 1 && threshold == STS::zero() && !useSignedClassical && A->hasCrsGraph()) { + if (A->GetFixedBlockSize() == 1 && threshold == STS::zero() && !useSignedClassicalRS && !useSignedClassicalSA && A->hasCrsGraph()) { // Case 1: scalar problem, no dropping => just use matrix graph RCP graph = rcp(new Graph(A->getCrsGraph(), "graph of A")); // Detect and record rows that correspond to Dirichlet boundary conditions @@ -431,7 +440,8 @@ namespace MueLu { } else if ( (A->GetFixedBlockSize() == 1 && threshold != STS::zero()) || (A->GetFixedBlockSize() == 1 && threshold == STS::zero() && !A->hasCrsGraph()) || - (A->GetFixedBlockSize() == 1 && useSignedClassical) ) { + (A->GetFixedBlockSize() == 1 && useSignedClassicalRS) || + (A->GetFixedBlockSize() == 1 && useSignedClassicalSA) ) { // Case 2: scalar problem with dropping => record the column indices of undropped entries, but still use original // graph's map information, e.g., whether index is local // OR a matrix without a CrsGraph @@ -444,7 +454,8 @@ namespace MueLu { RCP ghostedDiag; ArrayRCP ghostedDiagVals; ArrayRCP negMaxOffDiagonal; - if(useSignedClassical) { + // RS style needs the max negative off-diagonal, SA style needs the diagonal + if(useSignedClassicalRS) { if(ghostedBlockNumber.is_null()) { negMaxOffDiagonal = MueLu::Utilities::GetMatrixMaxMinusOffDiagonal(*A); if (GetVerbLevel() & Statistics1) @@ -489,12 +500,12 @@ namespace MueLu { //FIXME For now, hardwiring the dropping in here LO rownnz = 0; - if(useSignedClassical) { - // Signed classical + if(useSignedClassicalRS) { + // Signed classical RS style for (LO colID = 0; colID < Teuchos::as(nnz); colID++) { LO col = indices[colID]; MT max_neg_aik = realThreshold * STS::real(negMaxOffDiagonal[row]); - MT neg_aij = - STS::real(vals[colID]); + MT neg_aij = - STS::real(vals[colID]); /* if(row==1326) printf("A(%d,%d) = %6.4e, block = (%d,%d) neg_aij = %6.4e max_neg_aik = %6.4e\n",row,col,vals[colID], g_block_id.is_null() ? -1 : g_block_id[row], g_block_id.is_null() ? -1 : g_block_id[col], @@ -507,6 +518,27 @@ namespace MueLu { } rows[row+1] = realnnz; } + else if(useSignedClassicalSA) { + // Signed classical SA style + for (LO colID = 0; colID < Teuchos::as(nnz); colID++) { + LO col = indices[colID]; + + bool is_nonpositive = STS::real(vals[colID]) <= 0; + MT aiiajj = STS::magnitude(threshold*threshold * ghostedDiagVals[col]*ghostedDiagVals[row]); // eps^2*|a_ii|*|a_jj| + MT aij = is_nonpositive ? STS::magnitude(vals[colID]*vals[colID]) : (-STS::magnitude(vals[colID]*vals[colID])); // + |a_ij|^2, if a_ij < 0, - |a_ij|^2 if a_ij >=0 + + if(row==1326) printf("A(%d,%d) = %6.4e, raw_aij = %6.4e aij = %6.4e aiiajj = %6.4e\n",row,col,vals[colID], + vals[colID],aij, aiiajj); + + + if ((!rowIsDirichlet && aij > aiiajj) || row == col) { + columns[realnnz++] = col; + rownnz++; + } else + numDropped++; + } + rows[row+1] = realnnz; + } else { // Standard abs classical for (LO colID = 0; colID < Teuchos::as(nnz); colID++) { From 13007a8aa2bc4e7795b82c31f6f29076e5e75bb9 Mon Sep 17 00:00:00 2001 From: Chris Siefert Date: Mon, 18 Oct 2021 21:09:25 -0600 Subject: [PATCH 09/13] MueLu: Adding signed SA style dropping option --- .../MueLu_CoalesceDropFactory_def.hpp | 4 +- .../test/unit_tests/CoalesceDropFactory.cpp | 44 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp b/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp index 9af45faff43f..ded2947f9a3a 100644 --- a/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp +++ b/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp @@ -164,7 +164,9 @@ namespace MueLu { if (algo == "distance laplacian" || algo == "block diagonal distance laplacian") { Input(currentLevel, "Coordinates"); } - if (algo.find("block diagonal") != std::string::npos || algo.find("signed classical") != std::string::npos) { + if(algo == "signed classical sa") + ; + else if (algo.find("block diagonal") != std::string::npos || algo.find("signed classical") != std::string::npos) { Input(currentLevel, "BlockNumber"); } } diff --git a/packages/muelu/test/unit_tests/CoalesceDropFactory.cpp b/packages/muelu/test/unit_tests/CoalesceDropFactory.cpp index c730e165e812..63883e9521c8 100644 --- a/packages/muelu/test/unit_tests/CoalesceDropFactory.cpp +++ b/packages/muelu/test/unit_tests/CoalesceDropFactory.cpp @@ -1620,6 +1620,47 @@ namespace MueLuTests { + TEUCHOS_UNIT_TEST_TEMPLATE_4_DECL(CoalesceDropFactory, SignedClassicalSA, Scalar, LocalOrdinal, GlobalOrdinal, Node) { +# include + typedef Teuchos::ScalarTraits STS; + typedef typename STS::magnitudeType real_type; + + MUELU_TESTING_SET_OSTREAM; + MUELU_TESTING_LIMIT_SCOPE(Scalar,GlobalOrdinal,Node); + out << "version: " << MueLu::Version() << std::endl; + + RCP > comm = Parameters::getDefaultComm(); + Xpetra::UnderlyingLib lib = TestHelpers::Parameters::getLib(); + + GO nx = 10*comm->getSize(); + Teuchos::ParameterList matrixList; + matrixList.set("nx",nx); + matrixList.set("ny",(GO)10); + matrixList.set("nz",(GO)10); + matrixList.set("matrixType","Laplace3D"); + RCP A =TestHelpers::TestFactory::BuildMatrix(matrixList,lib); + + Level fineLevel; + fineLevel.Set("A", A); + + + RCP amalgFact = rcp(new AmalgamationFactory()); + CoalesceDropFactory coalesceDropFact; + coalesceDropFact.SetFactory("UnAmalgamationInfo",amalgFact); + coalesceDropFact.SetParameter("aggregation: drop tol",Teuchos::ParameterEntry(0.0)); + coalesceDropFact.SetParameter("aggregation: drop scheme",Teuchos::ParameterEntry(std::string("signed classical sa"))); + fineLevel.Request("Graph",&coalesceDropFact); + fineLevel.Request("DofsPerNode", &coalesceDropFact); + + coalesceDropFact.Build(fineLevel); + + RCP graph = fineLevel.Get >("Graph", &coalesceDropFact); + LO myDofsPerNode = fineLevel.Get("DofsPerNode", &coalesceDropFact); + TEST_EQUALITY(Teuchos::as(myDofsPerNode) == 1, true); + } + + + #define MUELU_ETI_GROUP(SC,LO,GO,Node) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,Constructor,SC,LO,GO,Node) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,Build,SC,LO,GO,Node) \ @@ -1642,7 +1683,8 @@ namespace MueLuTests { TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,BlockDiagonalDistanceLaplacian,SC,LO,GO,Node) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,BlockDiagonalDistanceLaplacianWeighted,SC,LO,GO,Node) \ TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,DistanceLaplacianWeighted,SC,LO,GO,Node) \ - TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,AggresiveDroppingIsMarkedAsBoundary,SC,LO,GO,Node) + TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,AggresiveDroppingIsMarkedAsBoundary,SC,LO,GO,Node) \ + TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT(CoalesceDropFactory,SignedClassicalSA,SC,LO,GO,Node) \ #include From 2d80622c1cb6d8021145f5421cd1f406290d2b96 Mon Sep 17 00:00:00 2001 From: Chris Siefert Date: Mon, 18 Oct 2021 21:14:38 -0600 Subject: [PATCH 10/13] TrilinosCouplings: Output mods --- .../examples/scaling/example_Poisson2D_pn_tpetra.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp b/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp index f25806937d62..caea27002639 100644 --- a/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp +++ b/packages/trilinoscouplings/examples/scaling/example_Poisson2D_pn_tpetra.cpp @@ -2106,9 +2106,10 @@ int TestMultiLevelPreconditionerLaplace(char ProblemType[], ParameterList belosList; belosList.set("Maximum Iterations", maxIts); // Maximum number of iterations allowed belosList.set("Convergence Tolerance", tol); // Relative convergence tolerance requested - belosList.set("Verbosity", Belos::Errors + Belos::Warnings + Belos::StatusTestDetails); + // belosList.set("Verbosity", Belos::Errors + Belos::Warnings + Belos::StatusTestDetails); belosList.set("Output Frequency", 1); - belosList.set("Output Style", Belos::Brief); + belosList.set("Output Style", 1); + belosList.set ("Verbosity", 33); bool scaleResidualHist = true; if (!scaleResidualHist) belosList.set("Implicit Residual Scaling", "None"); From 49eda37d4872d091642d42863e04ef3a6cae016e Mon Sep 17 00:00:00 2001 From: Chris Siefert Date: Tue, 19 Oct 2021 08:34:11 -0600 Subject: [PATCH 11/13] MueLu: Removing printf --- .../MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp b/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp index ded2947f9a3a..60b76d6dc427 100644 --- a/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp +++ b/packages/muelu/src/Graph/MatrixTransformation/MueLu_CoalesceDropFactory_def.hpp @@ -528,10 +528,10 @@ namespace MueLu { bool is_nonpositive = STS::real(vals[colID]) <= 0; MT aiiajj = STS::magnitude(threshold*threshold * ghostedDiagVals[col]*ghostedDiagVals[row]); // eps^2*|a_ii|*|a_jj| MT aij = is_nonpositive ? STS::magnitude(vals[colID]*vals[colID]) : (-STS::magnitude(vals[colID]*vals[colID])); // + |a_ij|^2, if a_ij < 0, - |a_ij|^2 if a_ij >=0 - + /* if(row==1326) printf("A(%d,%d) = %6.4e, raw_aij = %6.4e aij = %6.4e aiiajj = %6.4e\n",row,col,vals[colID], vals[colID],aij, aiiajj); - + */ if ((!rowIsDirichlet && aij > aiiajj) || row == col) { columns[realnnz++] = col; From 48770fec0596930b137559801c55f897a6b47997 Mon Sep 17 00:00:00 2001 From: Alejandro Mota Date: Tue, 19 Oct 2021 19:30:29 -0700 Subject: [PATCH 12/13] Changes to fix errors in LCM --- .../src/MiniTensor_LinearAlgebra.i.h | 32 ++++++++----------- packages/minitensor/src/MiniTensor_Tensor.i.h | 19 +++-------- .../minitensor/src/MiniTensor_Utilities.h | 12 +++++-- .../minitensor/src/MiniTensor_Utilities.i.h | 24 +++++++++----- packages/minitensor/src/MiniTensor_Vector.i.h | 8 ++--- 5 files changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h b/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h index 92e4aa540653..9b57ef699f94 100644 --- a/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h +++ b/packages/minitensor/src/MiniTensor_LinearAlgebra.i.h @@ -96,8 +96,6 @@ KOKKOS_INLINE_FUNCTION T norm_1(Tensor const & A) { - using KAT = Kokkos::ArithTraits; - Index const dimension = A.get_dimension(); @@ -114,7 +112,7 @@ norm_1(Tensor const & A) for (Index i = 0; i < dimension; ++i) { T t = 0.0; for (Index j = 0; j < dimension; ++j) { - t += KAT::abs(A(j, i)); + t += minitensor::abs(A(j, i)); } v(i) = t; } @@ -125,22 +123,22 @@ norm_1(Tensor const & A) break; case 3: - v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(1, 0)) + KAT::abs(A(2, 0)); - v(1) = KAT::abs(A(0, 1)) + KAT::abs(A(1, 1)) + KAT::abs(A(2, 1)); - v(2) = KAT::abs(A(0, 2)) + KAT::abs(A(1, 2)) + KAT::abs(A(2, 2)); + v(0) = minitensor::abs(A(0, 0)) + minitensor::abs(A(1, 0)) + minitensor::abs(A(2, 0)); + v(1) = minitensor::abs(A(0, 1)) + minitensor::abs(A(1, 1)) + minitensor::abs(A(2, 1)); + v(2) = minitensor::abs(A(0, 2)) + minitensor::abs(A(1, 2)) + minitensor::abs(A(2, 2)); s = max(max(v(0), v(1)), v(2)); break; case 2: - v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(1, 0)); - v(1) = KAT::abs(A(0, 1)) + KAT::abs(A(1, 1)); + v(0) = minitensor::abs(A(0, 0)) + minitensor::abs(A(1, 0)); + v(1) = minitensor::abs(A(0, 1)) + minitensor::abs(A(1, 1)); s = max(v(0), v(1)); break; case 1: - s = KAT::abs(A(0, 0)); + s = minitensor::abs(A(0, 0)); break; } @@ -156,8 +154,6 @@ KOKKOS_INLINE_FUNCTION T norm_infinity(Tensor const & A) { - using KAT = Kokkos::ArithTraits; - Index const dimension = A.get_dimension(); @@ -172,7 +168,7 @@ norm_infinity(Tensor const & A) for (Index i = 0; i < dimension; ++i) { T t = 0.0; for (Index j = 0; j < dimension; ++j) { - t += KAT::abs(A(i, j)); + t += minitensor::abs(A(i, j)); } v(i) = t; } @@ -183,22 +179,22 @@ norm_infinity(Tensor const & A) break; case 3: - v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(0, 1)) + KAT::abs(A(0, 2)); - v(1) = KAT::abs(A(1, 0)) + KAT::abs(A(1, 1)) + KAT::abs(A(1, 2)); - v(2) = KAT::abs(A(2, 0)) + KAT::abs(A(2, 1)) + KAT::abs(A(2, 2)); + v(0) = minitensor::abs(A(0, 0)) + minitensor::abs(A(0, 1)) + minitensor::abs(A(0, 2)); + v(1) = minitensor::abs(A(1, 0)) + minitensor::abs(A(1, 1)) + minitensor::abs(A(1, 2)); + v(2) = minitensor::abs(A(2, 0)) + minitensor::abs(A(2, 1)) + minitensor::abs(A(2, 2)); s = max(max(v(0), v(1)), v(2)); break; case 2: - v(0) = KAT::abs(A(0, 0)) + KAT::abs(A(0, 1)); - v(1) = KAT::abs(A(1, 0)) + KAT::abs(A(1, 1)); + v(0) = minitensor::abs(A(0, 0)) + minitensor::abs(A(0, 1)); + v(1) = minitensor::abs(A(1, 0)) + minitensor::abs(A(1, 1)); s = max(v(0), v(1)); break; case 1: - s = KAT::abs(A(0, 0)); + s = minitensor::abs(A(0, 0)); break; } diff --git a/packages/minitensor/src/MiniTensor_Tensor.i.h b/packages/minitensor/src/MiniTensor_Tensor.i.h index 97e07d3aa085..7bde2f3b1302 100644 --- a/packages/minitensor/src/MiniTensor_Tensor.i.h +++ b/packages/minitensor/src/MiniTensor_Tensor.i.h @@ -1646,32 +1646,23 @@ transpose(Tensor const & A) Tensor B = A; - auto my_swap = [&](T &a, T &b) { - T c = a; - a = b; - b = c; - }; - switch (dimension) { default: for (Index i = 0; i < dimension; ++i) { for (Index j = i + 1; j < dimension; ++j) { - my_swap(B(i, j), B(j, i)); + minitensor::swap(B(i, j), B(j, i)); } } break; case 3: - my_swap(B(0, 1), B(1, 0)); - my_swap(B(0, 2), B(2, 0)); - - my_swap(B(1, 2), B(2, 1)); - + minitensor::swap(B(0, 1), B(1, 0)); + minitensor::swap(B(0, 2), B(2, 0)); + minitensor::swap(B(1, 2), B(2, 1)); break; case 2: - my_swap(B(0, 1), B(1, 0)); - + minitensor::swap(B(0, 1), B(1, 0)); break; } diff --git a/packages/minitensor/src/MiniTensor_Utilities.h b/packages/minitensor/src/MiniTensor_Utilities.h index 7e149ba65a5f..2e6091fee574 100644 --- a/packages/minitensor/src/MiniTensor_Utilities.h +++ b/packages/minitensor/src/MiniTensor_Utilities.h @@ -49,6 +49,14 @@ namespace minitensor { +// +// abs function +// +template +KOKKOS_INLINE_FUNCTION +T +abs(T const & a); + // //swap function // @@ -63,7 +71,7 @@ swap(T & a, T & b); template KOKKOS_INLINE_FUNCTION T -max(const T & a,const T & b); +max(T const & a, T const & b); // // max function @@ -71,7 +79,7 @@ max(const T & a,const T & b); template KOKKOS_INLINE_FUNCTION T -min(const T & a,const T & b); +min(T const & a,T const & b); /// /// Sign function diff --git a/packages/minitensor/src/MiniTensor_Utilities.i.h b/packages/minitensor/src/MiniTensor_Utilities.i.h index dbdbedfe86b3..9e19785e7a5c 100644 --- a/packages/minitensor/src/MiniTensor_Utilities.i.h +++ b/packages/minitensor/src/MiniTensor_Utilities.i.h @@ -50,6 +50,17 @@ namespace minitensor { +// +// +// +template +KOKKOS_INLINE_FUNCTION +T +abs(T const & a) +{ + return a < T(0) ? -a : a; +} + // // // @@ -61,12 +72,9 @@ swap(T & a, T & b) // Guard against the same memory location. if (&a == &b) return; - // XOR algorithm - a ^= b; - b ^= a; - a ^= b; - - return; + auto const c = a; + a = b; + b = c; } // @@ -75,7 +83,7 @@ swap(T & a, T & b) template KOKKOS_INLINE_FUNCTION T -max(const T & a, const T & b) +max(T const & a, T const & b) { return a > b ? a : b; } @@ -86,7 +94,7 @@ max(const T & a, const T & b) template KOKKOS_INLINE_FUNCTION T -min(const T & a, const T & b) +min(T const & a, T const & b) { return a < b ? a : b; } diff --git a/packages/minitensor/src/MiniTensor_Vector.i.h b/packages/minitensor/src/MiniTensor_Vector.i.h index ebe5e5545272..6c0ebfac6536 100644 --- a/packages/minitensor/src/MiniTensor_Vector.i.h +++ b/packages/minitensor/src/MiniTensor_Vector.i.h @@ -803,8 +803,6 @@ KOKKOS_INLINE_FUNCTION T norm_infinity(Vector const & u) { - using KAT = Kokkos::ArithTraits; - Index const dimension = u.get_dimension(); @@ -815,16 +813,16 @@ norm_infinity(Vector const & u) default: for (Index i = 0; i < dimension; ++i) { - s = max(KAT::abs(u(i)), s); + s = max(minitensor::abs(u(i)), s); } break; case 3: - s = max(max(KAT::abs(u(0)), KAT::abs(u(1))), KAT::abs(u(2))); + s = max(max(minitensor::abs(u(0)), minitensor::abs(u(1))), minitensor::abs(u(2))); break; case 2: - s = max(KAT::abs(u(0)), KAT::abs(u(1))); + s = max(minitensor::abs(u(0)), minitensor::abs(u(1))); break; } From 186a40ba6549ff196909ef8d069342ffb13a8f65 Mon Sep 17 00:00:00 2001 From: drnobleabq <44879870+drnobleabq@users.noreply.github.com> Date: Wed, 20 Oct 2021 13:22:30 -0600 Subject: [PATCH 13/13] First krino commit (#9825) * First krino commit * Floating point comparison fix and clang cleanup --- PackagesList.cmake | 1 + packages/krino/CMakeLists.txt | 13 + packages/krino/LICENSE | 30 + packages/krino/cmake/Dependencies.cmake | 8 + .../build_sierra_krino_as_Trilinos_package.sh | 108 + .../krino/cmake_install_test/load_gcc_modules | 13 + .../krino/cmake_install_test/run_cmake_krino | 52 + .../Akri_DeleteSmallElementsMain.cpp | 187 + .../delete_small_elements/CMakeLists.txt | 12 + packages/krino/krino/Apps_krino.cpp | 47 + packages/krino/krino/CMakeLists.txt | 17 + .../Akri_AdaptivityInterface.cpp | 679 +++ .../Akri_AdaptivityInterface.hpp | 36 + .../krino/adaptivity_interface/CMakeLists.txt | 19 + .../Akri_InterfaceGeometry.hpp | 79 + .../Akri_InterfaceGeometry_dummy.cpp | 3 + .../CMakeLists.txt | 18 + .../krino_lib/Akri_AdaptivityHelpers.cpp | 138 + .../krino_lib/Akri_AdaptivityHelpers.hpp | 41 + .../krino/krino_lib/Akri_AnalyticSurf.cpp | 323 ++ .../krino/krino_lib/Akri_AnalyticSurf.hpp | 186 + .../Akri_AnalyticSurfaceInterfaceGeometry.cpp | 203 + .../Akri_AnalyticSurfaceInterfaceGeometry.hpp | 111 + .../krino/krino_lib/Akri_AuxMetaData.cpp | 396 ++ .../krino/krino_lib/Akri_AuxMetaData.hpp | 180 + .../krino/krino_lib/Akri_BoundingBox.cpp | 214 + .../krino/krino_lib/Akri_BoundingBox.hpp | 212 + .../krino/krino_lib/Akri_BoundingBoxMesh.cpp | 697 ++++ .../krino/krino_lib/Akri_BoundingBoxMesh.hpp | 157 + .../krino_lib/Akri_CDFEM_Parent_Edge.cpp | 636 +++ .../krino_lib/Akri_CDFEM_Parent_Edge.hpp | 137 + .../krino_lib/Akri_CDFEM_Parent_Edges.cpp | 492 +++ .../krino_lib/Akri_CDFEM_Parent_Edges.hpp | 86 + .../krino/krino_lib/Akri_CDFEM_Snapper.hpp | 31 + .../krino/krino_lib/Akri_CDFEM_Support.cpp | 364 ++ .../krino/krino_lib/Akri_CDFEM_Support.hpp | 259 ++ .../krino/krino/krino_lib/Akri_CDMesh.cpp | 3689 +++++++++++++++++ .../krino/krino/krino_lib/Akri_CDMesh.hpp | 333 ++ .../krino/krino_lib/Akri_CDMesh_Debug.cpp | 229 + .../krino/krino_lib/Akri_CDMesh_Debug.hpp | 20 + .../krino_lib/Akri_CDMesh_Refinement.cpp | 367 ++ .../krino_lib/Akri_CDMesh_Refinement.hpp | 31 + .../krino/krino_lib/Akri_CDMesh_Utils.cpp | 143 + .../krino/krino_lib/Akri_CDMesh_Utils.hpp | 34 + .../krino_lib/Akri_Composite_Surface.cpp | 108 + .../krino_lib/Akri_Composite_Surface.hpp | 64 + .../Akri_Compute_Surface_Distance.cpp | 122 + .../Akri_Compute_Surface_Distance.hpp | 42 + .../krino/krino_lib/Akri_ContourElement.cpp | 556 +++ .../krino/krino_lib/Akri_ContourElement.hpp | 130 + .../krino_lib/Akri_ContourSubElement.cpp | 2565 ++++++++++++ .../krino_lib/Akri_ContourSubElement.hpp | 311 ++ .../krino/krino_lib/Akri_Cutting_Surface.cpp | 170 + .../krino/krino_lib/Akri_Cutting_Surface.hpp | 81 + .../Akri_DecompositionHasChanged.cpp | 302 ++ .../Akri_DecompositionHasChanged.hpp | 25 + .../krino/krino/krino_lib/Akri_DiagWriter.cpp | 54 + .../krino/krino/krino_lib/Akri_DiagWriter.hpp | 63 + .../krino/krino_lib/Akri_DiagWriter_fwd.hpp | 37 + .../krino/krino_lib/Akri_DistanceSweeper.cpp | 177 + .../krino/krino_lib/Akri_DistanceSweeper.hpp | 22 + .../krino/krino/krino_lib/Akri_Element.cpp | 1077 +++++ .../krino/krino/krino_lib/Akri_Element.hpp | 207 + .../krino_lib/Akri_ElementCutterUtils.cpp | 184 + .../krino_lib/Akri_ElementCutterUtils.hpp | 37 + .../krino/krino_lib/Akri_Element_Cutter.cpp | 1064 +++++ .../krino/krino_lib/Akri_Element_Cutter.hpp | 132 + .../krino_lib/Akri_Element_Intersections.cpp | 28 + .../krino_lib/Akri_Element_Intersections.hpp | 34 + .../krino/krino_lib/Akri_EntityIdPool.cpp | 92 + .../krino/krino_lib/Akri_EntityIdPool.hpp | 39 + packages/krino/krino/krino_lib/Akri_Facet.cpp | 197 + packages/krino/krino/krino_lib/Akri_Facet.hpp | 158 + .../krino/krino_lib/Akri_Faceted_Surface.cpp | 494 +++ .../krino/krino_lib/Akri_Faceted_Surface.hpp | 69 + .../krino/krino_lib/Akri_Fast_Marching.cpp | 726 ++++ .../krino/krino_lib/Akri_Fast_Marching.hpp | 104 + .../krino/krino/krino_lib/Akri_FieldRef.cpp | 54 + .../krino/krino/krino_lib/Akri_FieldRef.hpp | 168 + .../krino/krino/krino_lib/Akri_IC_Alg.cpp | 225 + .../krino/krino/krino_lib/Akri_IC_Alg.hpp | 55 + .../krino/krino_lib/Akri_IC_Calculator.cpp | 100 + .../krino/krino_lib/Akri_IC_Calculator.hpp | 53 + .../krino/krino/krino_lib/Akri_IO_Helpers.cpp | 101 + .../krino/krino/krino_lib/Akri_IO_Helpers.hpp | 66 + .../krino/krino_lib/Akri_InterfaceID.hpp | 98 + .../Akri_Interface_Name_Generator.hpp | 71 + .../krino_lib/Akri_Intersection_Points.cpp | 267 ++ .../krino_lib/Akri_Intersection_Points.hpp | 75 + .../krino/krino/krino_lib/Akri_LevelSet.cpp | 1822 ++++++++ .../krino/krino/krino_lib/Akri_LevelSet.hpp | 339 ++ .../Akri_LevelSetInterfaceGeometry.cpp | 931 +++++ .../Akri_LevelSetInterfaceGeometry.hpp | 122 + .../krino_lib/Akri_LevelSet_Identifier.hpp | 37 + .../krino/krino_lib/Akri_LowerEnvelope.cpp | 317 ++ .../krino/krino_lib/Akri_LowerEnvelope.hpp | 120 + .../Akri_MasterElementDeterminer.cpp | 105 + .../Akri_MasterElementDeterminer.hpp | 30 + .../krino/krino/krino_lib/Akri_MathUtil.cpp | 233 ++ .../krino/krino/krino_lib/Akri_MathUtil.hpp | 35 + .../krino/krino/krino_lib/Akri_MeshClone.cpp | 629 +++ .../krino/krino/krino_lib/Akri_MeshClone.hpp | 67 + .../krino/krino_lib/Akri_MeshDiagnostics.cpp | 70 + .../krino/krino_lib/Akri_MeshDiagnostics.hpp | 23 + .../krino/krino_lib/Akri_MeshHelpers.cpp | 2560 ++++++++++++ .../krino/krino_lib/Akri_MeshHelpers.hpp | 182 + .../krino/krino_lib/Akri_MeshInputOptions.cpp | 52 + .../krino/krino_lib/Akri_MeshInputOptions.hpp | 112 + .../krino/krino_lib/Akri_MeshSurface.cpp | 1019 +++++ .../krino/krino_lib/Akri_MeshSurface.hpp | 177 + .../krino/krino_lib/Akri_MortonIndex.hpp | 58 + .../krino_lib/Akri_NodeToCapturedDomains.cpp | 121 + .../krino_lib/Akri_NodeToCapturedDomains.hpp | 31 + .../krino/krino_lib/Akri_OrderedIdPair.hpp | 50 + .../krino_lib/Akri_ParallelCommHelpers.hpp | 77 + .../krino_lib/Akri_ParallelErrorMessage.cpp | 30 + .../krino_lib/Akri_ParallelErrorMessage.hpp | 43 + .../krino_lib/Akri_ParentsToChildMapper.cpp | 188 + .../krino_lib/Akri_ParentsToChildMapper.hpp | 78 + .../krino/krino/krino_lib/Akri_PhaseTag.cpp | 112 + .../krino/krino/krino_lib/Akri_PhaseTag.hpp | 171 + .../krino/krino_lib/Akri_Phase_Support.cpp | 961 +++++ .../krino/krino_lib/Akri_Phase_Support.hpp | 212 + packages/krino/krino/krino_lib/Akri_Plane.hpp | 20 + .../krino_lib/Akri_Plane_Intersections.cpp | 144 + .../krino_lib/Akri_Plane_Intersections.hpp | 31 + .../krino/krino_lib/Akri_ProlongationData.cpp | 659 +++ .../krino/krino_lib/Akri_ProlongationData.hpp | 139 + .../krino/krino_lib/Akri_QualityMetric.cpp | 127 + .../krino/krino_lib/Akri_QualityMetric.hpp | 76 + .../krino/krino_lib/Akri_RegionInterface.cpp | 15 + .../krino/krino_lib/Akri_RegionInterface.hpp | 65 + .../krino/krino_lib/Akri_ReportHandler.hpp | 21 + .../krino/krino/krino_lib/Akri_SearchTree.cpp | 257 ++ .../krino/krino/krino_lib/Akri_SearchTree.hpp | 119 + .../krino/krino/krino_lib/Akri_Segment.hpp | 83 + packages/krino/krino/krino_lib/Akri_Snap.cpp | 633 +++ packages/krino/krino/krino_lib/Akri_Snap.hpp | 35 + .../Akri_SnapIndependentSetFinder.hpp | 34 + .../krino/krino/krino_lib/Akri_SnapInfo.cpp | 71 + .../krino/krino/krino_lib/Akri_SnapInfo.hpp | 87 + .../krino/krino/krino_lib/Akri_SnapToNode.cpp | 59 + .../krino/krino/krino_lib/Akri_SnapToNode.hpp | 26 + .../krino/krino/krino_lib/Akri_SubElement.cpp | 2833 +++++++++++++ .../krino/krino/krino_lib/Akri_SubElement.hpp | 431 ++ .../Akri_SubElementChildNodeAncestry.cpp | 246 ++ .../Akri_SubElementChildNodeAncestry.hpp | 49 + .../krino_lib/Akri_SubElementNodeAncestry.cpp | 32 + .../krino_lib/Akri_SubElementNodeAncestry.hpp | 65 + .../krino/krino/krino_lib/Akri_Surface.cpp | 28 + .../krino/krino/krino_lib/Akri_Surface.hpp | 98 + .../krino/krino_lib/Akri_Transformation.cpp | 112 + .../krino/krino_lib/Akri_Transformation.hpp | 65 + .../krino/krino/krino_lib/Akri_Triangle.hpp | 169 + .../krino/krino/krino_lib/Akri_TypeDefs.hpp | 27 + .../krino/krino/krino_lib/Akri_Utility.hpp | 66 + packages/krino/krino/krino_lib/Akri_Vec.cpp | 38 + packages/krino/krino/krino_lib/Akri_Vec.hpp | 26 + packages/krino/krino/krino_lib/CMakeLists.txt | 19 + .../master_element/Akri_MasterElement.hpp | 21 + .../Akri_MasterElementBasis.hpp | 701 ++++ .../master_element/Akri_MasterElementCalc.cpp | 456 ++ .../master_element/Akri_MasterElementCalc.hpp | 122 + .../Akri_MasterElementHybrid.cpp | 181 + .../Akri_MasterElementHybrid.hpp | 137 + .../Akri_MasterElementIntrepid.cpp | 287 ++ .../Akri_MasterElementIntrepid.hpp | 135 + .../krino/krino/master_element/CMakeLists.txt | 19 + .../parser/Akri_CDFEM_Options_Parser.cpp | 139 + .../parser/Akri_CDFEM_Options_Parser.hpp | 21 + .../krino/krino/parser/Akri_IC_Parser.cpp | 457 ++ .../krino/krino/parser/Akri_IC_Parser.hpp | 21 + .../krino/parser/Akri_LevelSet_Parser.cpp | 167 + .../krino/parser/Akri_LevelSet_Parser.hpp | 21 + .../krino/parser/Akri_MeshInput_Parser.cpp | 181 + .../krino/parser/Akri_MeshInput_Parser.hpp | 22 + .../krino/krino/parser/Akri_Phase_Parser.cpp | 101 + .../krino/krino/parser/Akri_Phase_Parser.hpp | 22 + .../krino/krino/parser/Akri_Region_Parser.cpp | 84 + .../krino/krino/parser/Akri_Region_Parser.hpp | 21 + .../parser/Akri_ResultsOutput_Parser.cpp | 72 + .../parser/Akri_ResultsOutput_Parser.hpp | 21 + .../krino/parser/Akri_Simulation_Parser.cpp | 54 + .../krino/parser/Akri_Simulation_Parser.hpp | 20 + packages/krino/krino/parser/Akri_YAML.hpp | 48 + .../krino/krino/parser/Akri_YAML_Parser.cpp | 235 ++ .../krino/krino/parser/Akri_YAML_Parser.hpp | 60 + packages/krino/krino/parser/CMakeLists.txt | 19 + .../rebalance_utils/Akri_RebalanceUtils.cpp | 230 + .../rebalance_utils/Akri_RebalanceUtils.hpp | 46 + .../Akri_RebalanceUtils_Impl.cpp | 348 ++ .../Akri_RebalanceUtils_Impl.hpp | 44 + .../krino/rebalance_utils/CMakeLists.txt | 19 + packages/krino/krino/region/Akri_Region.cpp | 664 +++ packages/krino/krino/region/Akri_Region.hpp | 81 + .../krino/region/Akri_RegisterProduct.cpp | 30 + .../krino/region/Akri_RegisterProduct.hpp | 20 + .../region/Akri_ResultsOutputOptions.hpp | 88 + .../krino/krino/region/Akri_Simulation.cpp | 179 + .../krino/krino/region/Akri_Simulation.hpp | 68 + packages/krino/krino/region/Akri_Startup.cpp | 306 ++ packages/krino/krino/region/Akri_Startup.hpp | 36 + packages/krino/krino/region/CMakeLists.txt | 19 + .../krino/unit_tests/Akri_UnitMathUtils.cpp | 64 + .../krino/unit_tests/Akri_UnitTestUtils.cpp | 23 + .../krino/unit_tests/Akri_UnitTestUtils.hpp | 19 + .../unit_tests/Akri_Unit_Analytic_CDMesh.cpp | 276 ++ .../Akri_Unit_CDFEM_Parent_Edge.cpp | 782 ++++ .../krino/unit_tests/Akri_Unit_CDMesh.cpp | 3092 ++++++++++++++ .../unit_tests/Akri_Unit_ContourElement.cpp | 83 + .../krino/unit_tests/Akri_Unit_Element.cpp | 718 ++++ .../unit_tests/Akri_Unit_Element_Cutter.cpp | 188 + .../Akri_Unit_Explicit_Hamilton_Jacobi.cpp | 903 ++++ .../krino/unit_tests/Akri_Unit_Geometry.cpp | 444 ++ .../unit_tests/Akri_Unit_LogRedirecter.cpp | 27 + .../unit_tests/Akri_Unit_LogRedirecter.hpp | 32 + .../unit_tests/Akri_Unit_LowerEnvelope.cpp | 260 ++ .../unit_tests/Akri_Unit_MeshHelpers.cpp | 278 ++ .../unit_tests/Akri_Unit_MeshHelpers.hpp | 24 + .../unit_tests/Akri_Unit_MortonIndex.cpp | 52 + .../Akri_Unit_ParallelErrorMessage.cpp | 42 + .../Akri_Unit_Part_Decomposition_Fixture.cpp | 197 + .../Akri_Unit_Part_Decomposition_Fixture.hpp | 75 + .../unit_tests/Akri_Unit_Phase_Support.cpp | 231 ++ .../unit_tests/Akri_Unit_RebalanceUtils.cpp | 446 ++ .../Akri_Unit_Single_Element_Fixtures.cpp | 136 + .../Akri_Unit_Single_Element_Fixtures.hpp | 79 + .../krino/krino/unit_tests/Akri_Unit_main.cpp | 32 + .../krino/krino/unit_tests/CMakeLists.txt | 14 + .../stk_emend/independent_set/CMakeLists.txt | 10 + .../independent_set/IndependentSetDummy.cpp | 3 + 231 files changed, 54507 insertions(+) create mode 100644 packages/krino/CMakeLists.txt create mode 100644 packages/krino/LICENSE create mode 100644 packages/krino/cmake/Dependencies.cmake create mode 100755 packages/krino/cmake_install_test/build_sierra_krino_as_Trilinos_package.sh create mode 100644 packages/krino/cmake_install_test/load_gcc_modules create mode 100755 packages/krino/cmake_install_test/run_cmake_krino create mode 100644 packages/krino/delete_small_elements/Akri_DeleteSmallElementsMain.cpp create mode 100644 packages/krino/delete_small_elements/CMakeLists.txt create mode 100644 packages/krino/krino/Apps_krino.cpp create mode 100644 packages/krino/krino/CMakeLists.txt create mode 100644 packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.cpp create mode 100644 packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.hpp create mode 100644 packages/krino/krino/adaptivity_interface/CMakeLists.txt create mode 100644 packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry.hpp create mode 100644 packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry_dummy.cpp create mode 100644 packages/krino/krino/interface_geometry_interface/CMakeLists.txt create mode 100644 packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_AnalyticSurf.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_AnalyticSurf.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_AuxMetaData.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_AuxMetaData.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_BoundingBox.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_BoundingBox.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDFEM_Snapper.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDFEM_Support.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDFEM_Support.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh_Debug.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh_Debug.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh_Utils.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_CDMesh_Utils.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Composite_Surface.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Composite_Surface.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ContourElement.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_ContourElement.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ContourSubElement.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_ContourSubElement.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Cutting_Surface.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Cutting_Surface.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_DiagWriter.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_DiagWriter.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_DiagWriter_fwd.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_DistanceSweeper.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_DistanceSweeper.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Element.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Element.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ElementCutterUtils.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_ElementCutterUtils.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Element_Cutter.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Element_Cutter.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Element_Intersections.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Element_Intersections.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_EntityIdPool.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_EntityIdPool.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Facet.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Facet.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Faceted_Surface.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Faceted_Surface.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Fast_Marching.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Fast_Marching.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_FieldRef.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_FieldRef.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_IC_Alg.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_IC_Alg.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_IC_Calculator.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_IC_Calculator.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_IO_Helpers.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_IO_Helpers.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_InterfaceID.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Interface_Name_Generator.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Intersection_Points.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Intersection_Points.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_LevelSet.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_LevelSet.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_LevelSet_Identifier.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_LowerEnvelope.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_LowerEnvelope.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MathUtil.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_MathUtil.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshClone.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshClone.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshDiagnostics.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshDiagnostics.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshHelpers.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshHelpers.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshInputOptions.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshInputOptions.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshSurface.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_MeshSurface.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_MortonIndex.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_OrderedIdPair.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ParallelCommHelpers.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ParentsToChildMapper.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_ParentsToChildMapper.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_PhaseTag.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_PhaseTag.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Phase_Support.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Phase_Support.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Plane.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Plane_Intersections.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Plane_Intersections.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ProlongationData.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_ProlongationData.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_QualityMetric.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_QualityMetric.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_RegionInterface.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_RegionInterface.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_ReportHandler.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_SearchTree.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_SearchTree.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Segment.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Snap.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Snap.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_SnapIndependentSetFinder.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_SnapInfo.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_SnapInfo.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_SnapToNode.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_SnapToNode.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_SubElement.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_SubElement.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Surface.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Surface.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Transformation.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Transformation.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Triangle.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_TypeDefs.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Utility.hpp create mode 100644 packages/krino/krino/krino_lib/Akri_Vec.cpp create mode 100644 packages/krino/krino/krino_lib/Akri_Vec.hpp create mode 100644 packages/krino/krino/krino_lib/CMakeLists.txt create mode 100644 packages/krino/krino/master_element/Akri_MasterElement.hpp create mode 100644 packages/krino/krino/master_element/Akri_MasterElementBasis.hpp create mode 100644 packages/krino/krino/master_element/Akri_MasterElementCalc.cpp create mode 100644 packages/krino/krino/master_element/Akri_MasterElementCalc.hpp create mode 100644 packages/krino/krino/master_element/Akri_MasterElementHybrid.cpp create mode 100644 packages/krino/krino/master_element/Akri_MasterElementHybrid.hpp create mode 100644 packages/krino/krino/master_element/Akri_MasterElementIntrepid.cpp create mode 100644 packages/krino/krino/master_element/Akri_MasterElementIntrepid.hpp create mode 100644 packages/krino/krino/master_element/CMakeLists.txt create mode 100644 packages/krino/krino/parser/Akri_CDFEM_Options_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_CDFEM_Options_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_IC_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_IC_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_LevelSet_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_LevelSet_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_MeshInput_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_MeshInput_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_Phase_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_Phase_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_Region_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_Region_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_ResultsOutput_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_ResultsOutput_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_Simulation_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_Simulation_Parser.hpp create mode 100644 packages/krino/krino/parser/Akri_YAML.hpp create mode 100644 packages/krino/krino/parser/Akri_YAML_Parser.cpp create mode 100644 packages/krino/krino/parser/Akri_YAML_Parser.hpp create mode 100644 packages/krino/krino/parser/CMakeLists.txt create mode 100644 packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.cpp create mode 100644 packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.hpp create mode 100644 packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.cpp create mode 100644 packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.hpp create mode 100644 packages/krino/krino/rebalance_utils/CMakeLists.txt create mode 100644 packages/krino/krino/region/Akri_Region.cpp create mode 100644 packages/krino/krino/region/Akri_Region.hpp create mode 100644 packages/krino/krino/region/Akri_RegisterProduct.cpp create mode 100644 packages/krino/krino/region/Akri_RegisterProduct.hpp create mode 100644 packages/krino/krino/region/Akri_ResultsOutputOptions.hpp create mode 100644 packages/krino/krino/region/Akri_Simulation.cpp create mode 100644 packages/krino/krino/region/Akri_Simulation.hpp create mode 100644 packages/krino/krino/region/Akri_Startup.cpp create mode 100644 packages/krino/krino/region/Akri_Startup.hpp create mode 100644 packages/krino/krino/region/CMakeLists.txt create mode 100644 packages/krino/krino/unit_tests/Akri_UnitMathUtils.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_UnitTestUtils.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_UnitTestUtils.hpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Analytic_CDMesh.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_CDFEM_Parent_Edge.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_CDMesh.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_ContourElement.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Element.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Element_Cutter.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Explicit_Hamilton_Jacobi.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Geometry.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.hpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_LowerEnvelope.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.hpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_MortonIndex.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_ParallelErrorMessage.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.hpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Phase_Support.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_RebalanceUtils.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.cpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.hpp create mode 100644 packages/krino/krino/unit_tests/Akri_Unit_main.cpp create mode 100644 packages/krino/krino/unit_tests/CMakeLists.txt create mode 100644 packages/stk/stk_emend/stk_emend/independent_set/IndependentSetDummy.cpp diff --git a/PackagesList.cmake b/PackagesList.cmake index 4a44aafd4eb9..08dc43e67ae0 100644 --- a/PackagesList.cmake +++ b/PackagesList.cmake @@ -112,6 +112,7 @@ TRIBITS_REPOSITORY_DEFINE_PACKAGES( Compadre packages/compadre ST STK packages/stk PT # Depends on boost Percept packages/percept PT # Depends on boost + Krino packages/krino PT # Depends on boost SCORECapf_zoltan SCOREC/zoltan ST SCORECapf_stk SCOREC/stk ST SCORECma SCOREC/ma ST diff --git a/packages/krino/CMakeLists.txt b/packages/krino/CMakeLists.txt new file mode 100644 index 000000000000..2f2c2ab9c734 --- /dev/null +++ b/packages/krino/CMakeLists.txt @@ -0,0 +1,13 @@ + +message("Building Krino as a Trilinos package") +TRIBITS_PACKAGE(Krino) + +TRIBITS_ADD_DEBUG_OPTION() +TRIBITS_ADD_SHOW_DEPRECATED_WARNINGS_OPTION() + +if (${Trilinos_ENABLE_Krino}) + add_subdirectory(krino) + add_subdirectory(delete_small_elements) +endif() + +TRIBITS_PACKAGE_POSTPROCESS() diff --git a/packages/krino/LICENSE b/packages/krino/LICENSE new file mode 100644 index 000000000000..33d63e2fe002 --- /dev/null +++ b/packages/krino/LICENSE @@ -0,0 +1,30 @@ +Copyright 2002 - 2008, 2010, 2011 National Technology & Engineering +Solutions of Sandia, LLC (NTESS). + +Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government +retains certain rights in this software. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/krino/cmake/Dependencies.cmake b/packages/krino/cmake/Dependencies.cmake new file mode 100644 index 000000000000..76d77c60821f --- /dev/null +++ b/packages/krino/cmake/Dependencies.cmake @@ -0,0 +1,8 @@ +SET(LIB_REQUIRED_DEP_PACKAGES SEACASIoss SEACASExodus STKBalance STKMath STKIO STKSearch STKTopology STKUtil STKTools STKEmend Percept Intrepid) +SET(LIB_OPTIONAL_DEP_PACKAGES) +SET(TEST_REQUIRED_DEP_PACKAGES Gtest STKUnit_test_utils) +SET(TEST_OPTIONAL_DEP_PACKAGES) +SET(LIB_REQUIRED_DEP_TPLS Boost) +SET(LIB_OPTIONAL_DEP_TPLS MPI yaml-cpp) +SET(TEST_REQUIRED_DEP_TPLS) +SET(TEST_OPTIONAL_DEP_TPLS) diff --git a/packages/krino/cmake_install_test/build_sierra_krino_as_Trilinos_package.sh b/packages/krino/cmake_install_test/build_sierra_krino_as_Trilinos_package.sh new file mode 100755 index 000000000000..650cc502eca4 --- /dev/null +++ b/packages/krino/cmake_install_test/build_sierra_krino_as_Trilinos_package.sh @@ -0,0 +1,108 @@ +#!/bin/sh + +function execute() { + stdbuf -o0 -e0 echo "% $@" ; + eval "$@" ; + if [ $? -ne 0 ] ; then + echo "'$@' failed."; + exit 1; + fi +} + +function cd_to_new_dir() +{ + execute rm -rf $1 + execute mkdir $1 + execute cd $1 +} + +function exit_with_message() +{ + echo $1 + exit 1 +} + +function make_and_install() +{ + make -j 16 || exit_with_message "Failed building $1" + make install || exit_with_message "Failed installing $1" +} + +function build_yaml() +{ + productName=yaml + + cd_to_new_dir ${output_dir}/${productName} + execute tar -xzf ${sierra_proj}/TPLs_src/spack/spack_tpls/yaml-cpp/*.tar.gz + cd_to_new_dir ${productName}_build + + export CC=gcc + export CXX=g++ + execute cmake -DCMAKE_BUILD_TYPE=${build_type^^} -DYAML_CPP_BUILD_TESTS=false -DCMAKE_INSTALL_PREFIX=../${productName}_install ../yaml-cpp + make_and_install $productName + unset CC + unset CXX +} + +function build_trilinos_with_krino() +{ + productName=trilinos + + trilinos_dir=${output_dir}/${productName} + if [ ! -d ${trilinos_dir} ] ; then + execute mkdir ${trilinos_dir} + fi + execute cd ${trilinos_dir} + + if [ ! -d Trilinos ] ; then + execute git clone -b develop https://github.com/trilinos/Trilinos.git Trilinos + #else + #execute cd Trilinos + #execute git checkout develop + #execute git reset --hard origin/develop + #execute git pull + #execute cd .. + fi + + if [ -d Trilinos/packages/krino ] ; then + execute rm -rf Trilinos/packages/krino; + fi + if [ ! -L Trilinos/packages/krino ] ; then + execute ln -s ${sierra_proj}/krino Trilinos/packages + fi + + rm -rf ${productName}_install + cd_to_new_dir ${productName}_build + + export TRILINOS_INSTALL_DIR=../${productName}_install + $sierra_proj/krino/cmake_install_test/run_cmake_krino + make_and_install $productName +} + +function runTests() +{ + cd $1 + ctest -j 16 || exit_with_message "$2 tests failed" + cd ../.. +} + +sierra_proj=${SIERRA_PROJ:-${PWD}} +output_dir=${OUTPUT_DIR:-${PWD}/../krino-cmake-testing} + +cuda_on_or_off=${CUDA:-OFF} +build_type=${CMAKE_BUILD_TYPE:-release} +date_suffix=`date +%F_%H-%M-%S` + +source $sierra_proj/krino/cmake_install_test/load_gcc_modules + +if [ ! -d ${output_dir} ] ; then + execute mkdir ${output_dir} +fi + +build_yaml +build_trilinos_with_krino + +#runTests morph/morph_build "Morph" +#runTests morph_and_sgm/morph_and_sgm_build "Morph and SGM" +#runTests morphWithExe/morphWithExe_build "MorphWithExe" + diff --git a/packages/krino/cmake_install_test/load_gcc_modules b/packages/krino/cmake_install_test/load_gcc_modules new file mode 100644 index 000000000000..7b57261e312e --- /dev/null +++ b/packages/krino/cmake_install_test/load_gcc_modules @@ -0,0 +1,13 @@ +#!/bin/bash + +module load cde/v2/cmake/3.19.2 +module load cde/v2/compiler/gcc/7.2.0 +module load cde/v2/gcc/7.2.0/openmpi/4.0.5 +module load cde/v2/gcc/7.2.0/netlib-lapack/3.8.0 +module load cde/v2/gcc/7.2.0/boost/1.73.0 +module load cde/v2/gcc/7.2.0/hdf5/1.10.6 +module load cde/v2/gcc/7.2.0/netcdf-c/4.7.3 +module load cde/v2/gcc/7.2.0/parallel-netcdf/1.12.1 +module load cde/v2/gcc/7.2.0/metis/5.1.0 +module load cde/v2/gcc/7.2.0/parmetis/4.0.3 + diff --git a/packages/krino/cmake_install_test/run_cmake_krino b/packages/krino/cmake_install_test/run_cmake_krino new file mode 100755 index 000000000000..9f052313e1f0 --- /dev/null +++ b/packages/krino/cmake_install_test/run_cmake_krino @@ -0,0 +1,52 @@ +#!/bin/bash + +trilinos_src_dir=${TRILINOS_DIR:-${PWD}/../Trilinos} +build_dir=${BUILD_DIR:-${PWD}} +build_type=${CMAKE_BUILD_TYPE:-release} +trilinos_install_dir=${TRILINOS_INSTALL_DIR:-${PWD}/../trilinos_install_dir} + +yaml_install_dir=${YAML_INSTALL_DIR:-${build_dir}/../../yaml/yaml_install} + +printf "\nTRILINOS_DIR=${trilinos_src_dir}\n"; +printf "BUILD_DIR=${build_dir}\n"; +printf "CMAKE_BUILD_TYPE=${build_type}\n"; +printf "TRILINOS_INSTALL_DIR=${trilinos_install_dir}\n"; +printf "YAML_INSTALL_DIR=${yaml_install_dir}\n"; +printf "\nTo change these vars, set as env vars or pass to this script like 'VAR=value run_cmake_stk'\n\n"; + +if [ ! -d ${trilinos_src_dir}/packages/seacas ] && [ ! -L ${trilinos_src_dir}/packages/seacas ] ; then + echo "Trilinos dir (${trilinos_src_dir}) doesn't have packages/seacas directory. If using a Sierra project, make a soft-link to Sierra's seacas directory."; + exit 1; +fi +if [ ! -d ${trilinos_src_dir}/packages/stk ] && [ ! -L ${trilinos_src_dir}/packages/stk ]; then + echo "Trilinos dir (${trilinos_src_dir}) doesn't have packages/stk directory. If using a Sierra project, make a soft-link to Sierra's stk directory."; + exit 1; +fi +if [ ! -d ${trilinos_src_dir}/packages/krino ] && [ ! -L ${trilinos_src_dir}/packages/krino ]; then + echo "Trilinos dir (${trilinos_src_dir}) doesn't have packages/krino directory. If using a Sierra project, make a soft-link to Sierra's krino directory."; + exit 1; +fi + +mkdir -p $trilinos_install_dir +mkdir -p $build_dir + +cd ${build_dir} + +# Cleanup old cache before we configure +rm -rf CMakeFiles CMakeCache.txt + +cmake \ +-DCMAKE_INSTALL_PREFIX=$trilinos_install_dir \ +-DCMAKE_BUILD_TYPE=${build_type^^} \ +-DKrino_ENABLE_TESTS:BOOL=ON \ +-DTrilinos_ENABLE_Krino:BOOL=ON \ +-DTrilinos_ENABLE_Fortran:BOOL=ON \ +-DTPL_ENABLE_yaml-cpp=ON \ +-Dyaml-cpp_INCLUDE_DIRS=${yaml_install_dir}/include \ +-Dyaml-cpp_LIBRARY_DIRS=${yaml_install_dir}/lib \ +-DTPL_ENABLE_Boost:BOOL=ON \ +-DTPL_ENABLE_MPI=ON \ +-DTPL_ENABLE_Pnetcdf:BOOL=ON \ +-DTPL_ENABLE_HDF5:BOOL=ON \ +${trilinos_src_dir}/ + diff --git a/packages/krino/delete_small_elements/Akri_DeleteSmallElementsMain.cpp b/packages/krino/delete_small_elements/Akri_DeleteSmallElementsMain.cpp new file mode 100644 index 000000000000..1a5fd03eaabc --- /dev/null +++ b/packages/krino/delete_small_elements/Akri_DeleteSmallElementsMain.cpp @@ -0,0 +1,187 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +struct DeleteSmallElementsInputData +{ + static constexpr const char * mDefaultString{"None"}; + std::string meshIn{mDefaultString}; + std::string meshOut{mDefaultString}; + bool blockNameSpecified{false}; + std::string blockName{mDefaultString}; + double minNodalVolume{0.0}; + bool minNodalVolumeSpecified{false}; + double minRelativeNodalVolume{-1.0}; + bool minRelativeNodalVolumeSpecified{false}; +}; + +bool read_command_line( int argc, char *argv[], DeleteSmallElementsInputData & inputData, stk::ParallelMachine comm) +{ + DeleteSmallElementsInputData defaultValues; + + const stk::CommandLineOption inmeshOption{"inmesh", "i", "Filename of input genesis mesh."}; + const stk::CommandLineOption outmeshOption{"outmesh", "o", "Filename of output genesis mesh to write."}; + + const stk::CommandLineOption minNodalVolumeOption{"min_nodal_volume", "m", "Remove all nodes and attached entities smaller than this size. Either --min_nodal_volume or --min_relative_nodal_volume can be specified, but not both."}; + const stk::CommandLineOption minRelativeNodalVolumeOption{"min_relative_nodal_volume", "r", "Remove all all nodes and attached entities smaller than this factor times the maximum element volume in the input mesh. Either --min_nodal_volume or --min_relative_nodal_volume can be specified, but not both"}; + const stk::CommandLineOption blockNameOption{"block", "b", "Confine consideration to the specified block."}; + + stk::CommandLineParserParallel commandLine(comm); + commandLine.disallow_unrecognized(); + + commandLine.add_required(inmeshOption); + commandLine.add_required(outmeshOption); + commandLine.add_optional(blockNameOption, inputData.blockName); + commandLine.add_optional(minNodalVolumeOption, inputData.minNodalVolume); + commandLine.add_optional(minRelativeNodalVolumeOption, inputData.minRelativeNodalVolume); + + + stk::CommandLineParser::ParseState state = commandLine.parse(argc, const_cast(argv)); + if(state == stk::CommandLineParser::ParseComplete) + { + inputData.meshIn = commandLine.get_option_value(inmeshOption.name); + inputData.meshOut = commandLine.get_option_value(outmeshOption.name); + + inputData.blockNameSpecified = commandLine.is_option_parsed(blockNameOption.name); + inputData.minNodalVolumeSpecified = commandLine.is_option_parsed(minNodalVolumeOption.name); + inputData.minRelativeNodalVolumeSpecified = commandLine.is_option_parsed(minRelativeNodalVolumeOption.name); + + if(inputData.blockNameSpecified) + { + inputData.blockName = commandLine.get_option_value(blockNameOption.name); + } + + if(inputData.minNodalVolumeSpecified && inputData.minRelativeNodalVolumeSpecified) + { + sierra::Env::outputP0() << "ERROR: You cannot specify both --"+minNodalVolumeOption.name+" and --"+minRelativeNodalVolumeOption.name+"." << std::endl; + state = stk::CommandLineParser::ParseError; + } + + if(inputData.minNodalVolumeSpecified) + { + inputData.minNodalVolume = commandLine.get_option_value(minNodalVolumeOption.name); + if (inputData.minNodalVolume < 0.0) + { + sierra::Env::outputP0() << "ERROR: size specified via --"+minNodalVolumeOption.name+" must be >= 0." << std::endl; + state = stk::CommandLineParser::ParseError; + } + } + + if(inputData.minRelativeNodalVolumeSpecified) + { + inputData.minRelativeNodalVolume = commandLine.get_option_value(minRelativeNodalVolumeOption.name); + if (inputData.minRelativeNodalVolume < 0.0) + { + sierra::Env::outputP0() << "ERROR: edge length ratio specified via --"+minRelativeNodalVolumeOption.name+" must be >= 0." << std::endl; + state = stk::CommandLineParser::ParseError; + } + } + } + + if(state != stk::CommandLineParser::ParseComplete) + sierra::Env::outputP0() << commandLine.get_usage() << std::endl; + + return state == stk::CommandLineParser::ParseComplete; +} + +static bool delete_small_elements(const DeleteSmallElementsInputData& inputData, + const stk::ParallelMachine comm) +{ + stk::mesh::MetaData meta; + stk::mesh::BulkData bulk(meta, comm); + + stk::io::fill_mesh_with_auto_decomp(inputData.meshIn, bulk); + + // delete infinitesimal elements + double minEdgeLength, maxEdgeLength, minElementVolume, maxElementVolume; + compute_element_quality(bulk, minEdgeLength, maxEdgeLength, minElementVolume, maxElementVolume); + sierra::Env::outputP0() << "Overall mesh size results: minEdgeLength=" << minEdgeLength << ", maxEdgeLength=" << maxEdgeLength << ", minElementVolume=" << minElementVolume << ", maxElementVolume=" << maxElementVolume << std::endl; + + stk::mesh::Selector blockSelector = meta.universal_part(); + if (inputData.blockNameSpecified) + { + stk::mesh::Part * block = meta.get_part(inputData.blockName); + if (!block) + { + sierra::Env::outputP0() << "Did not find part with name " << inputData.blockName << "." << std::endl; + return false; + } + blockSelector = *block; + } + + const double minRetainedElementVolume = inputData.minNodalVolumeSpecified ? inputData.minNodalVolume : (inputData.minRelativeNodalVolume*maxElementVolume); + if (minElementVolume < minRetainedElementVolume) + delete_all_entities_using_nodes_with_nodal_volume_below_threshold(bulk, blockSelector, minRetainedElementVolume); + else + sierra::Env::outputP0() << "All nodes already have nodal volume larger than " << minRetainedElementVolume << "." << std::endl; + + stk::io::StkMeshIoBroker io_broker; + io_broker.set_bulk_data(bulk); + size_t resultFileIndex = io_broker.create_output_mesh(inputData.meshOut, stk::io::WRITE_RESULTS); + io_broker.write_output_mesh(resultFileIndex); + + return true; +} + +static bool run_delete_small_elements(int argc, char **argv, stk::ParallelMachine comm) +{ + int proc = stk::parallel_machine_rank(comm); + if(proc != 0) + stk::EnvData::instance().m_outputP0 = &stk::EnvData::instance().m_outputNull; + + DeleteSmallElementsInputData inputData; + bool didParseOk = read_command_line(argc, argv, inputData, comm); + + bool successfullyRun{false}; + + if (didParseOk) + { + successfullyRun = delete_small_elements(inputData, comm); + + stk::parallel_machine_barrier(comm); + } + + return successfullyRun; +} + +} + +int main(int argc, char **argv) +{ + stk::ParallelMachine comm{stk::parallel_machine_init(&argc, &argv)}; + + bool successfullyRun{false}; + + try + { + successfullyRun = krino::run_delete_small_elements(argc, argv, comm); + } + catch(std::exception &e) + { + std::cerr << "Proc " << stk::parallel_machine_rank(comm) << ": " << e.what() << std::endl; + const int errorCode{-1}; + MPI_Abort(comm, errorCode); + } + + stk::parallel_machine_finalize(); + + return successfullyRun ? 0 : 1; +} diff --git a/packages/krino/delete_small_elements/CMakeLists.txt b/packages/krino/delete_small_elements/CMakeLists.txt new file mode 100644 index 000000000000..864b989c454e --- /dev/null +++ b/packages/krino/delete_small_elements/CMakeLists.txt @@ -0,0 +1,12 @@ +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +SET(SOURCES_MAIN Akri_DeleteSmallElementsMain.cpp) + +set(EXE_NAME delete_small_elements) +TRIBITS_ADD_EXECUTABLE( + ${EXE_NAME} + SOURCES ${SOURCES_MAIN} + NOEXEPREFIX INSTALLABLE + ) diff --git a/packages/krino/krino/Apps_krino.cpp b/packages/krino/krino/Apps_krino.cpp new file mode 100644 index 000000000000..dd24cd296feb --- /dev/null +++ b/packages/krino/krino/Apps_krino.cpp @@ -0,0 +1,47 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +int main( int argc, char ** argv ) +{ + krino::Startup startup(argc, argv); + + if (startup.exit_early()) { + return 0; + } + + bool is_parsing = true; + + try { + krino::YAML_Parser::parse(); + + is_parsing = false; + krino::Simulation & simulation = krino::Simulation::get(); + simulation.commit(); + simulation.initialize(); + simulation.execute(); + } + catch (std::exception &x) { + stk::diag::Trace::Preserve preserve__; + startup.handle_exception(x.what(),is_parsing); + } + catch (...) { + stk::diag::Trace::Preserve preserve__; + startup.handle_exception("Unknown",is_parsing); + } + + // call Simulation::reset() manually to make sure the associated static objects are destroyed before MPI_FINALIZE is called by ~Startup + krino::Simulation::reset(); + + // all done + return 0; +} diff --git a/packages/krino/krino/CMakeLists.txt b/packages/krino/krino/CMakeLists.txt new file mode 100644 index 000000000000..4e8c7ece3d53 --- /dev/null +++ b/packages/krino/krino/CMakeLists.txt @@ -0,0 +1,17 @@ +add_subdirectory(interface_geometry_interface) +add_subdirectory(master_element) +add_subdirectory(krino_lib) +add_subdirectory(adaptivity_interface) +add_subdirectory(region) +add_subdirectory(rebalance_utils) +add_subdirectory(parser) +add_subdirectory(unit_tests) + +SET(SOURCES_MAIN Apps_krino.cpp) + +set(EXE_NAME krino) +TRIBITS_ADD_EXECUTABLE( + ${EXE_NAME} + SOURCES ${SOURCES_MAIN} + NOEXEPREFIX NOEXESUFFIX INSTALLABLE + ) diff --git a/packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.cpp b/packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.cpp new file mode 100644 index 000000000000..47ea5ae3d3ed --- /dev/null +++ b/packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.cpp @@ -0,0 +1,679 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +#ifdef __INTEL_COMPILER +#include // for ElementRefinePredicate +#include +#include +#include // for UniformRefiner +#include +#include +#include +#include +#include +#else +// this disables the checking of shadowed variables on GCC only +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#include // for ElementRefinePredicate +#include +#include +#include // for UniformRefiner +#include +#include +#include +#include +#include +#pragma GCC diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino +{ + +namespace +{ + +class HAdaptImpl +{ +public: + HAdaptImpl(stk::mesh::MetaData & meta) + : my_meta(meta), + my_active_part(nullptr), + my_root_timer(nullptr) + { + } + void setup(stk::mesh::Part & active_part, stk::diag::Timer & root_timer); + void do_adaptive_refinement(const std::string & marker_field_name); + void do_initial_uniform_refinement(const int num_levels); + +private: + void get_counts(const stk::mesh::FieldBase & marker_field, + unsigned & refinement_count, + unsigned & unrefinement_count) const; + void check_supported_element_types() const; + + stk::mesh::MetaData & my_meta; + std::unique_ptr my_pMesh; + std::unique_ptr my_adaptive_refinement_pattern; + std::unique_ptr my_element_refine_predicate; + std::unique_ptr> my_breaker; + stk::mesh::Selector my_selector; + stk::mesh::Part * my_active_part; + stk::diag::Timer * my_root_timer; +}; + +HAdaptImpl & get(stk::mesh::MetaData & meta) +{ + HAdaptImpl * hadapt = const_cast(meta.get_attribute()); + if (nullptr == hadapt) + { + hadapt = new HAdaptImpl(meta); + meta.declare_attribute_with_delete(hadapt); + } + return *hadapt; +} + +void HAdaptImpl::check_supported_element_types() const +{ + const stk::mesh::PartVector & all_parts = my_meta.get_parts(); + for (size_t i = 0; i < all_parts.size(); ++i) + { + const stk::mesh::Part & part = *all_parts[i]; + if (stk::io::is_part_io_part(part) && part.primary_entity_rank() == stk::topology::ELEMENT_RANK) + { + if ((2 == my_meta.spatial_dimension() && + part.topology() == stk::topology::TRIANGLE_3_2D) || + (3 == my_meta.spatial_dimension() && + part.topology() == stk::topology::TETRAHEDRON_4) || + part.topology() == stk::topology::PARTICLE) + { + continue; + } + ThrowErrorMsg("Elements in block " + << part.name() << " have topology " << part.topology().name() + << " which is currently not supported for adaptive refinement."); + } + } +} + +void HAdaptImpl::get_counts(const stk::mesh::FieldBase & marker_field, + unsigned & refinement_count, + unsigned & unrefinement_count) const +{ + stk::mesh::BulkData & mesh = marker_field.get_mesh(); + stk::mesh::Part & active_part = *my_active_part; + stk::mesh::Selector field_selector = stk::mesh::selectField(marker_field) & active_part & mesh.mesh_meta_data().locally_owned_part(); + + unsigned local_refinement_count = 0; + unsigned local_unrefinement_count = 0; + + const stk::mesh::BucketVector & buckets = + mesh.get_buckets(stk::topology::ELEMENT_RANK, field_selector); + + for (stk::mesh::BucketVector::const_iterator ib = buckets.begin(); ib != buckets.end(); ++ib) + { + const stk::mesh::Bucket & b = **ib; + const int length = b.size(); + + for (int i = 0; i < length; ++i) + { + stk::mesh::Entity elem = b[i]; + + const int & marker = *((int *)stk::mesh::field_data(marker_field, elem)); + if (marker < 0) + { + ++local_unrefinement_count; + } + else if (marker > 0) + { + ++local_refinement_count; + } + } + } + refinement_count = 0; + unrefinement_count = 0; + stk::all_reduce_sum( + mesh.parallel(), (int *)&local_refinement_count, (int *)&refinement_count, 1); + stk::all_reduce_sum(mesh.parallel(), + (int *)&local_unrefinement_count, + (int *)&unrefinement_count, + 1); +} + +void HAdaptImpl::setup(stk::mesh::Part & active_part, stk::diag::Timer & root_timer) +{ + /* %TRACE[ON]% */ Trace trace__( + "void HAdapt::setup(stk::mesh::Part & active_part)"); /* %TRACE% */ + + my_active_part = &active_part; + my_root_timer = &root_timer; + + static stk::diag::Timer timerAdapt_("Adapt", *my_root_timer); + static stk::diag::Timer timerSetup_("Setup", timerAdapt_); + stk::diag::TimeBlock tbTimerSetup_(timerSetup_); + + const unsigned nDim = my_meta.spatial_dimension(); + + my_pMesh = std::make_unique(&my_meta, nullptr, false); + + typedef stk::mesh::Field PerceptVector; + const stk::mesh::FieldBase & coordinates_base = *my_meta.coordinate_field(); + PerceptVector & typed_coordinates = + reinterpret_cast(const_cast(coordinates_base)); + my_pMesh->setCoordinatesField(&typed_coordinates); + + my_pMesh->register_and_set_refine_fields(); + my_pMesh->add_field_int("refine_field", stk::topology::NODE_RANK, 0); + + if (2 == nDim) + { + my_adaptive_refinement_pattern = std::make_unique(*my_pMesh); + } + else + { + my_adaptive_refinement_pattern = std::make_unique(*my_pMesh); + } + + my_pMesh->output_active_children_only(true); + my_pMesh->setProperty("Refiner_skip_side_part_fixes", "true"); +} + +namespace +{ +void limit_coarsening_to_child_elements_with_all_siblings_marked_for_coarsening_and_no_children( + percept::PerceptMesh & eMesh, + const stk::mesh::FieldBase & marker_field) +{ + const stk::mesh::BulkData & mesh = *eMesh.get_bulk_data(); + const auto & local_buckets = + mesh.get_buckets(stk::topology::ELEMENT_RANK, mesh.mesh_meta_data().locally_owned_part()); + + std::vector children; + std::vector childrenOfChild; + + // Only mark element for COARSEN if all children are marked COARSEN + for (auto && b_ptr : local_buckets) + { + for (auto && elem : *b_ptr) + { + eMesh.getChildren(elem, children, false, false); + if (!children.empty()) + { + bool all_children_marked_COARSEN = true; + for (auto && child : children) + { + int & marker = (*((int *)stk::mesh::field_data( marker_field, child ))); + + if (marker == -1) + eMesh.getChildren(child, childrenOfChild, false, false); + if (marker != -1 || !childrenOfChild.empty()) + { + all_children_marked_COARSEN = false; + break; + } + } + if (!all_children_marked_COARSEN) + { + for (auto && child : children) + { + int & marker = (*((int *)stk::mesh::field_data( marker_field, child ))); + if (marker == -1) + { + marker = 0; + } + } + } + } + } + } +} + + +void fill_percept_refine_field(percept::PerceptMesh & eMesh, + const stk::mesh::FieldBase & marker_field) +{ + limit_coarsening_to_child_elements_with_all_siblings_marked_for_coarsening_and_no_children(eMesh, marker_field); + + stk::mesh::BulkData & mesh = *eMesh.get_bulk_data(); + stk::mesh::communicate_field_data(mesh, {&marker_field}); + + stk::mesh::Part &parent_part = *mesh.mesh_meta_data().get_part("refine_inactive_elements_part_3"); + stk::mesh::FieldBase & refine_field = *eMesh.m_refine_field; + + for (auto && b_ptr : mesh.get_buckets(stk::topology::ELEMENT_RANK, stk::mesh::selectField(marker_field))) + { + const stk::mesh::Bucket & b = *b_ptr; + const bool is_parent = b.member(parent_part); + const int length = b.size(); + const unsigned marker_field_length = stk::mesh::field_scalars_per_entity(marker_field, b); + ThrowRequire(1 == stk::mesh::field_scalars_per_entity(refine_field, b)); + + int * marker = (int *)stk::mesh::field_data(marker_field, b); + int * refine = (int *)stk::mesh::field_data(refine_field, b); + if (is_parent) + { + for (int i = 0; i < length; ++i) + { + marker[i*marker_field_length] = 0; + } + } + + for (int i = 0; i < length; ++i) + { + refine[i] = marker[i*marker_field_length]; + } + } +} + +void delete_partless_faces_and_edges(stk::mesh::BulkData & mesh) +{ + std::vector entities; + std::vector relatives; + std::vector relative_ordinals; + + mesh.modification_begin(); + + for (stk::mesh::EntityRank entity_rank : {stk::topology::FACE_RANK, stk::topology::EDGE_RANK}) + { + stk::mesh::get_entities(mesh, entity_rank, entities); + + for (auto && entity : entities) + { + stk::mesh::Bucket & bucket = mesh.bucket(entity); + + const stk::mesh::PartVector & bucket_parts = bucket.supersets(); + bool bucket_has_same_rank_part = + std::find_if(bucket_parts.begin(), + bucket_parts.end(), + [&](const stk::mesh::Part * bucket_part) + { + return (bucket_part->primary_entity_rank() == bucket.entity_rank() && + !stk::mesh::is_auto_declared_part(*bucket_part)); + }) != bucket_parts.end(); + + if (!bucket_has_same_rank_part && + mesh.num_connectivity(entity, stk::topology::CONSTRAINT_RANK) == 0) + { + for (stk::mesh::EntityRank irank = stk::topology::ELEMENT_RANK; irank != entity_rank; + --irank) + { + // Previously this attempted to delete forward or backward and still the list got + // corrupted, + // so just copy into vector and delete from there. + relatives.assign(mesh.begin(entity, irank), mesh.end(entity, irank)); + relative_ordinals.assign( + mesh.begin_ordinals(entity, irank), mesh.end_ordinals(entity, irank)); + + for (size_t irel = 0; irel < relatives.size(); ++irel) + { + ThrowRequireMsg(mesh.destroy_relation(relatives[irel], entity, relative_ordinals[irel]), + "Could not destroy relation between " << mesh.entity_key(relatives[irel]) << " and " + << mesh.entity_key(entity)); + } + } + ThrowRequireMsg( + mesh.destroy_entity(entity), "Could not destroy entity " << mesh.entity_key(entity)); + } + } + } + + mesh.modification_end(); +} + +void update_active_inactive_entities(stk::mesh::BulkData & mesh, + stk::mesh::Part & active_part) +{ + const auto & meta = mesh.mesh_meta_data(); + stk::mesh::PartVector active_part_vec(1, &active_part); + stk::mesh::PartVector inactive_part_vec; + stk::mesh::Selector select_locally_owned(meta.locally_owned_part()); + stk::mesh::Selector percept_active_selector = get_refinement_active_part(meta, stk::topology::ELEMENT_RANK); + + mesh.modification_begin(); + std::vector entities; + for (stk::mesh::EntityRank entity_rank = stk::topology::NODE_RANK; + entity_rank <= stk::topology::ELEMENT_RANK; + ++entity_rank) + { + stk::mesh::get_selected_entities(select_locally_owned, mesh.buckets(entity_rank), entities); + for (auto && entity : entities) + { + if (percept_active_selector(mesh.bucket(entity))) + mesh.change_entity_parts(entity, active_part_vec, inactive_part_vec); + else + mesh.change_entity_parts(entity, inactive_part_vec, active_part_vec); + } + } + mesh.modification_end(); +} + +void +fixup_side_permutation(stk::mesh::BulkData & mesh) +{ + mesh.modification_begin(); + stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + + const stk::mesh::BucketVector & buckets = mesh.buckets(stk::topology::ELEMENT_RANK); + + for (auto&& bucket_ptr : buckets) + { + for (auto&& elem : *bucket_ptr) + { + const stk::mesh::Entity* elem_sides = mesh.begin(elem, meta.side_rank()); + const unsigned num_elem_sides = mesh.num_connectivity(elem, meta.side_rank()); + + for (size_t it_side = 0; it_side < num_elem_sides; ++it_side) + { + auto relationship = determine_ordinal_and_permutation(mesh, elem, elem_sides[it_side]); + set_relation_permutation(mesh, elem, elem_sides[it_side], relationship.first, relationship.second); + } + } + } + mesh.modification_end(); +} + +} + +void HAdaptImpl::do_adaptive_refinement(const std::string & marker_field_name) +{ + /* %TRACE[ON]% */ Trace trace__( + "void HAdapt::do_adaptive_refinement(const std::string &marker_field)"); /* %TRACE% */ + + ThrowAssertMsg(my_root_timer != nullptr, "HAdapt::setup() not called."); + static stk::diag::Timer timerAdapt_("Adapt", *my_root_timer); + static stk::diag::Timer timerSetup_("Setup", timerAdapt_); + static stk::diag::Timer timerFmwkUpdating_("Update Active Part", timerAdapt_); + static stk::diag::Timer timerRefine_("Refine", timerAdapt_); + static stk::diag::Timer timerUnrefine_("Unrefine", timerAdapt_); + + stk::diag::TimeBlock tbTimerAdapt_(timerAdapt_); + + constexpr bool debug = false; + + check_supported_element_types(); + + stk::mesh::BulkData & bulk = my_meta.mesh_bulk_data(); + if (!my_pMesh->get_bulk_data()) + { + my_pMesh->set_bulk_data(&bulk); + } + + const stk::mesh::FieldBase * element_marker_field = + my_meta.get_field(stk::topology::ELEMENT_RANK, marker_field_name); + ThrowRequire(element_marker_field); + my_selector = stk::mesh::selectField(*element_marker_field); + + if (!my_breaker) + { + const double tolerance = 0.0; // not used + stk::mesh::FieldBase * refine_field = my_pMesh->m_refine_field; + my_element_refine_predicate = std::make_unique( + *my_pMesh, &my_selector, refine_field, tolerance); // Note that this stores pointer to selector so temporary is not ok + my_breaker = std::make_unique>( + *my_element_refine_predicate, *my_pMesh, *my_adaptive_refinement_pattern, nullptr, debug); + my_breaker->setRemoveOldElements(false); + my_breaker->setAlwaysInitializeNodeRegistry(false); + } + + my_breaker->initializeRefine(); + + unsigned refinement_count = 0; + unsigned unrefinement_count = 0; + + fill_percept_refine_field(*my_pMesh, *element_marker_field); + + get_counts(*element_marker_field, refinement_count, unrefinement_count); + krinolog << "Number of elements marked for refinement = " << refinement_count << "\n"; + krinolog << "Number of elements marked for unrefinement = " << unrefinement_count << stk::diag::dendl; + + const unsigned total_marked_count = refinement_count + unrefinement_count; + if (0 == total_marked_count) + { + krinolog << "Adapt: No elements marked for refinement or unrefinement. " + "Skipping adaptivity" << stk::diag::dendl; + return; + } + + delete_partless_faces_and_edges(bulk); + + { + stk::diag::TimeBlock tbTimerRefine_(timerRefine_); + my_breaker->setAlternateRootTimer(&timerRefine_); + my_breaker->setModBegEndRootTimer(&timerAdapt_); + + std::vector counts; + stk::mesh::comm_mesh_counts(bulk, counts); + + krinolog << "Adapt: before refine, mesh has " << counts[0] << " nodes, " << counts[1] + << " edges, " << counts[2] << " faces, " << counts[3] << " elements" << stk::diag::dendl; + + my_breaker->refine(); + + stk::mesh::comm_mesh_counts(bulk, counts); + + krinolog << "Adapt: after refine, mesh has " << counts[0] << " nodes, " << counts[1] + << " edges, " << counts[2] << " faces, " << counts[3] << " elements" << stk::diag::dendl; + + my_breaker->setAlternateRootTimer(0); + my_breaker->setModBegEndRootTimer(0); + } + + // update refine field to include newly created children + fill_percept_refine_field(*my_pMesh, *element_marker_field); + + { + stk::diag::TimeBlock tbTimerUnrefine_(timerUnrefine_); + my_breaker->setAlternateRootTimer(&timerUnrefine_); + my_breaker->setModBegEndRootTimer(&timerAdapt_); + my_breaker->unrefine(); + my_breaker->setAlternateRootTimer(0); + my_breaker->setModBegEndRootTimer(0); + } + + { + stk::diag::TimeBlock tbTimerRebuilding_(timerFmwkUpdating_); + // The Encore-Percept interface also called induce_nodal_unranked_superset_parts + // and topology nodeset inducer but I don't believe those are needed here. + ThrowRequireMsg(my_active_part != nullptr, "Active part not set for krino::HAdapt"); + update_active_inactive_entities(bulk, *my_active_part); + fixup_side_permutation(bulk); + } +} + +void HAdaptImpl::do_initial_uniform_refinement(const int num_levels) +{ + /* %TRACE[ON]% */ Trace trace__( + "void HAdapt::do_uniform_refinement()"); /* %TRACE% */ + + ThrowAssertMsg(my_root_timer != nullptr, "HAdapt::setup() not called."); + static stk::diag::Timer timerAdapt_("Adapt", *my_root_timer); + static stk::diag::Timer timerSetup_("Setup", timerAdapt_); + static stk::diag::Timer timerFmwkUpdating_("Update Active Part", timerAdapt_); + static stk::diag::Timer timerRefine_("Refine", timerAdapt_); + + stk::diag::TimeBlock tbTimerAdapt_(timerAdapt_); + + check_supported_element_types(); + + stk::mesh::BulkData & bulk = my_meta.mesh_bulk_data(); + if (!my_pMesh->get_bulk_data()) + { + my_pMesh->set_bulk_data(&bulk); + } + + percept::UniformRefiner urefine(*my_pMesh, *my_adaptive_refinement_pattern, nullptr); + urefine.setAlternateRootTimer(&timerAdapt_); + + { + stk::diag::TimeBlock tbTimerRefine_(timerRefine_); + + std::vector counts; + stk::mesh::comm_mesh_counts(bulk, counts); + + krinolog << "Adapt: before refine, mesh has " << counts[0] << " nodes, " << counts[1] + << " edges, " << counts[2] << " faces, " << counts[3] << " elements" << stk::diag::dendl; + + for (int level=0; level counts; + stk::mesh::comm_mesh_counts(bulk, counts); + num_initial_elements = counts[3]; + } + + const auto & meta = bulk.mesh_meta_data(); + percept::MarkerInfo markerInfo; + markerInfo.errorIndicator_ = meta.get_field(stk::topology::ELEMENT_RANK, indicator_field_name); + markerInfo.refineField_ = meta.get_field(stk::topology::ELEMENT_RANK, marker_field_name); + markerInfo.refineFieldOrig_ = meta.get_field(stk::topology::ELEMENT_RANK, "refine_field_orig"); + markerInfo.refineLevelField_ = meta.get_field(stk::topology::ELEMENT_RANK, "refine_level"); + /* + markerInfo.useMarker_ = true; + + markerInfo.numInitialElements_ = num_initial_elements; + markerInfo.maxRefinementLevel_ = max_refinement_level; + markerInfo.maxRefinementNumberOfElementsFraction_ = max_element_growth_factor; + markerInfo.debug_ = false; + + markerInfo.refineFraction_ = refine_fraction; + markerInfo.unrefineFraction_ = unrefine_fraction; + + percept::MarkerUsingErrIndFraction marker(const_cast(bulk), markerInfo); + marker.mark();*/ + + const auto & percept_parent_part = get_refinement_inactive_part(meta, stk::topology::ELEMENT_RANK); + const auto & active_buckets = + bulk.get_buckets(stk::topology::ELEMENT_RANK, + stk::mesh::selectField(*markerInfo.errorIndicator_) & + !percept_parent_part & + meta.locally_owned_part()); + const int buckets_to_sample = std::min(1000ul, active_buckets.size()); + std::vector sample_values; + sample_values.reserve(512*buckets_to_sample); + int j=0; + for(int i=0; i < buckets_to_sample; ++i) + { + const double * ind_data = field_data(*markerInfo.errorIndicator_, *active_buckets[i]); + const int size = active_buckets[i]->size(); + j += size; + sample_values.insert(sample_values.end(), ind_data, ind_data + size); + } + + // This is not scalable + std::vector global_vals; + stk::parallel_vector_concat(bulk.parallel(), sample_values, global_vals); + std::sort(global_vals.begin(), global_vals.end()); + + const auto current_active_elements = global_vals.size(); + + const double remaining_levels = max_refinement_level - current_refinement_level; + const double fraction_to_refine = + current_active_elements >= target_elem_count ? 0: + 1./12. * ( + std::pow( + static_cast(target_elem_count) / static_cast(current_active_elements), + 1./remaining_levels) + - 1.); + + const int global_fraction_index = (1.-fraction_to_refine) * current_active_elements; + const double threshold_val = global_vals[global_fraction_index]; + + for(auto && b_ptr : active_buckets) + { + const auto & bucket = *b_ptr; + const double * ind_data = field_data(*markerInfo.errorIndicator_, bucket); + int * marker_data = field_data(*markerInfo.refineField_, bucket); + const int size = bucket.size(); + for(int i=0; i < size; ++i) + { + marker_data[i] = ind_data[i] > threshold_val ? 1 : 0; + } + } +} + +} +} // namespace krino diff --git a/packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.hpp b/packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.hpp new file mode 100644 index 000000000000..84c5b21c340d --- /dev/null +++ b/packages/krino/krino/adaptivity_interface/Akri_AdaptivityInterface.hpp @@ -0,0 +1,36 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_ADAPTIVITYINTERFACE_H_ +#define KRINO_INCLUDE_AKRI_ADAPTIVITYINTERFACE_H_ + +#include + +namespace stk { namespace mesh { class BulkData; } } +namespace stk { namespace mesh { class MetaData; } } +namespace stk { namespace mesh { class Part; } } +namespace stk { namespace diag { class Timer; } } + +namespace krino { + +namespace HAdapt +{ + void setup(stk::mesh::MetaData & meta, stk::mesh::Part & active_part, stk::diag::Timer & root_timer); + void do_adaptive_refinement(stk::mesh::MetaData & meta, const std::string & marker_field_name); + void do_uniform_refinement(stk::mesh::MetaData & meta, const int num_levels); + void mark_based_on_indicator_field(const stk::mesh::BulkData & bulk, + const std::string & marker_field_name, + const std::string & indicator_field_name, + const int max_refinement_level, + const int current_refinement_level, + const uint64_t target_elem_count); +} + +} // namespace krino + +#endif /* KRINO_INCLUDE_AKRI_ADAPTIVITYINTERFACE_H_ */ diff --git a/packages/krino/krino/adaptivity_interface/CMakeLists.txt b/packages/krino/krino/adaptivity_interface/CMakeLists.txt new file mode 100644 index 000000000000..6c8d3a739a32 --- /dev/null +++ b/packages/krino/krino/adaptivity_interface/CMakeLists.txt @@ -0,0 +1,19 @@ +SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + krino_adaptivity_interface_lib + HEADERS ${HEADERS} + SOURCES ${SOURCES} + DEPLIBS krino_lib) + +INSTALL(FILES ${HEADERS} DESTINATION + ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/krino_adaptivity_interface_lib) + diff --git a/packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry.hpp b/packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry.hpp new file mode 100644 index 000000000000..2f2a3a32d69d --- /dev/null +++ b/packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry.hpp @@ -0,0 +1,79 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_INTERFACEGEOMETRY_H_ +#define AKRI_INTERFACEGEOMETRY_H_ +#include +#include +#include +#include +#include +#include + +namespace krino { + +class SnapInfo; + +class ElementCutter +{ +public: + virtual ~ElementCutter() {} + virtual std::vector get_sorted_cutting_interfaces() const = 0; + virtual std::vector get_interface_signs_based_on_crossings(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains) const = 0; + virtual void fill_tetrahedron_face_interior_intersections(const std::array & faceNodes, + const InterfaceID & interface1, + const InterfaceID & interface2, + const ElementIntersectionPointFilter & intersectionPointFilter, + std::vector & intersections) const = 0; + virtual bool might_have_interior_or_face_intersections() const = 0; + virtual void fill_interior_intersections(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const = 0; + virtual std::string visualize(const stk::mesh::BulkData & mesh) const = 0; + virtual bool have_crossing(const InterfaceID interface, const Segment3d & edge) const = 0; + virtual double interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const = 0; + virtual int sign_at_position(const InterfaceID interface, const Vector3d & paramCoords) const = 0; + virtual int get_starting_phase_for_cutting_surfaces() const = 0; +}; + +class InterfaceGeometry { +public: + InterfaceGeometry() {} + + virtual ~InterfaceGeometry() {} + virtual void prepare_to_process_elements(const stk::mesh::BulkData & mesh, const NodeToCapturedDomainsMap & nodesToCapturedDomains) const = 0; + virtual void prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const std::vector & elementsToIntersect, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const = 0; + + virtual std::vector get_edge_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const = 0; + + virtual void append_element_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + const std::vector & elementsToIntersect, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) const = 0; + + // FIXME: Temporary methods + virtual void store_phase_for_uncut_elements(const stk::mesh::BulkData & mesh) const = 0; + virtual void store_phase_for_elements_that_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const std::vector & intersectionPoints, + const std::vector & independentSnapInfos, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const = 0; + + virtual const ElementToDomainMap & get_phase_for_uncut_elements() const = 0; + + virtual std::unique_ptr build_element_cutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const std::function &)> & intersectingPlanesDiagonalPicker) const = 0; + + virtual PhaseTag get_starting_phase(const ElementCutter * cutter) const = 0; +}; +} + +#endif // AKRI_INTERFACEGEOMETRY_H_ diff --git a/packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry_dummy.cpp b/packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry_dummy.cpp new file mode 100644 index 000000000000..5879b01607a9 --- /dev/null +++ b/packages/krino/krino/interface_geometry_interface/Akri_InterfaceGeometry_dummy.cpp @@ -0,0 +1,3 @@ +namespace krino { +void InterfaceGeometryDummy() {} +} diff --git a/packages/krino/krino/interface_geometry_interface/CMakeLists.txt b/packages/krino/krino/interface_geometry_interface/CMakeLists.txt new file mode 100644 index 000000000000..917ba031808d --- /dev/null +++ b/packages/krino/krino/interface_geometry_interface/CMakeLists.txt @@ -0,0 +1,18 @@ +SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + interface_geometry_interface_lib + HEADERS ${HEADERS} + SOURCES ${SOURCES}) + +INSTALL(FILES ${HEADERS} DESTINATION + ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/interface_geometry_interface_lib) + diff --git a/packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.cpp b/packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.cpp new file mode 100644 index 000000000000..cd9709bd0f33 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.cpp @@ -0,0 +1,138 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino +{ + +stk::mesh::Part & get_refinement_active_part(const stk::mesh::MetaData & meta, stk::mesh::EntityRank rank) +{ + const std::string active_part_name = "refine_active_elements_part_"+std::to_string((int)rank); + stk::mesh::Part* active_part = meta.get_part(active_part_name); + ThrowRequireMsg(nullptr != active_part, "Active part not found: " << active_part_name); + return *active_part; +} +stk::mesh::Part & get_refinement_inactive_part(const stk::mesh::MetaData & meta, stk::mesh::EntityRank rank) +{ + const std::string inactive_part_name = "refine_inactive_elements_part_"+std::to_string((int)rank); + stk::mesh::Part* inactive_part = meta.get_part(inactive_part_name); + ThrowRequireMsg(nullptr != inactive_part, "Inactive part not found: " << inactive_part_name); + return *inactive_part; +} + +void filter_refinement_marker(const stk::mesh::BulkData & mesh, FieldRef elem_marker, const stk::mesh::Selector & do_not_refine_or_unrefine_selector) +{ + const auto & perceptParentPart = get_refinement_inactive_part(mesh.mesh_meta_data(), stk::topology::ELEMENT_RANK); + + for (auto && bucketPtr : mesh.get_buckets(stk::topology::ELEMENT_RANK, mesh.mesh_meta_data().locally_owned_part())) + { + int * markers = field_data(elem_marker, *bucketPtr); + const int size = bucketPtr->size(); + if (do_not_refine_or_unrefine_selector(*bucketPtr)) + { + for (int i = 0; i < size; ++i) + if (markers[i] == Refinement_Marker::REFINE || markers[i] == Refinement_Marker::COARSEN) + markers[i] = Refinement_Marker::NOTHING; + } + else if (bucketPtr->member(perceptParentPart)) + { + for (int i = 0; i < size; ++i) + if (markers[i] == Refinement_Marker::REFINE) + markers[i] = Refinement_Marker::NOTHING; + } + } +} + +stk::mesh::Selector cdfem_do_not_refine_or_unrefine_selector(const CDFEM_Support & cdfem_support) +{ + const stk::mesh::Selector parent_or_child_selector = + cdfem_support.get_child_part() | cdfem_support.get_parent_part(); + const stk::mesh::Selector decomposed_blocks_selector = + krino::Phase_Support::get(cdfem_support.get_mesh_meta()).get_all_decomposed_blocks_selector(); + const stk::mesh::Selector do_not_refine_selector = (!decomposed_blocks_selector) | parent_or_child_selector; + return do_not_refine_selector; +} + + +void perform_multilevel_adaptivity(stk::mesh::BulkData & mesh, + const std::string & marker_field_name, + const std::function & marker_function, + const std::function & adapt_function, + const stk::mesh::Selector & do_not_refine_selector) +{ + Tracespec trace__("perform_multilevel_adaptivity()"); + + const auto & aux_meta = AuxMetaData::get(mesh.mesh_meta_data()); + + const FieldRef elem_marker = aux_meta.get_field( + stk::topology::ELEMENT_RANK, marker_field_name, stk::mesh::StateNew); + + const stk::mesh::Selector active_selector = aux_meta.active_part(); + const stk::mesh::Selector locally_owned_selector = mesh.mesh_meta_data().locally_owned_part(); + + std::vector children; + + bool done = false; + int num_refinements = 0; + while (!done) + { + marker_function(marker_field_name, num_refinements); + filter_refinement_marker(mesh, elem_marker, do_not_refine_selector); + + const auto & local_buckets = mesh.get_buckets(stk::topology::ELEMENT_RANK, locally_owned_selector); + unsigned num_marked_refine = 0; + for (auto && b_ptr : local_buckets) + { + int * markers = field_data(elem_marker, *b_ptr); + for (size_t i = 0; i < b_ptr->size(); ++i) + { + if (markers[i] == Refinement_Marker::REFINE) ++num_marked_refine; + } + } + + // Only worth cost of adaptive refinement if any elements are marked for refinement + unsigned global_num_marked_refine = 0; + stk::all_reduce_sum(mesh.parallel(), &num_marked_refine, &global_num_marked_refine, 1); + if (global_num_marked_refine > 0) + { + const int debug_level = 0; + adapt_function(marker_field_name, debug_level); + + ++num_refinements; + } + else + { + done = true; + } + } + + // This probably should not be needed. + CDMesh::fixup_adapted_element_parts(mesh); + + // This probably should not be needed. + attach_sides_to_elements(mesh); +} +} diff --git a/packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.hpp b/packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.hpp new file mode 100644 index 000000000000..17a29e7ccafb --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AdaptivityHelpers.hpp @@ -0,0 +1,41 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_ADAPTIVITYHELPERS_H_ +#define KRINO_INCLUDE_AKRI_ADAPTIVITYHELPERS_H_ + +#include +#include +#include +#include + +namespace krino { + +class CDFEM_Support; + +stk::mesh::Part & get_refinement_active_part(const stk::mesh::MetaData & meta, stk::mesh::EntityRank rank); +stk::mesh::Part & get_refinement_inactive_part(const stk::mesh::MetaData & meta, stk::mesh::EntityRank rank); +stk::mesh::Selector cdfem_do_not_refine_or_unrefine_selector(const CDFEM_Support & cdfem_support); + +enum Refinement_Marker + { + COARSEN = -1, + NOTHING = 0, + REFINE = 1 + }; + +void +perform_multilevel_adaptivity(stk::mesh::BulkData & mesh, + const std::string & marker_field_name, + const std::function & marker_function, + const std::function & adapt_function, + const stk::mesh::Selector & do_not_refine_selector = stk::mesh::Selector()); + +} + +#endif /* KRINO_INCLUDE_AKRI_ADAPTIVITYHELPERS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_AnalyticSurf.cpp b/packages/krino/krino/krino_lib/Akri_AnalyticSurf.cpp new file mode 100644 index 000000000000..374396fb7bbf --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AnalyticSurf.cpp @@ -0,0 +1,323 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +#include + +namespace krino{ + +Cylinder::Cylinder(const std::string & n, // surface name + const double e1[3], // first endpoint of axis + const double e2[3], // second endpoint of axis + const double r, // radius of cylinder + const int sign) + : SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign(), + dist_sign(sign) +{ + // initialize internal description of cylinder + radius = r; + + p1 = Vector3d(e1); + p2 = Vector3d(e2); + + xi = p2 - p1; + + length = xi.length(); + + xi.unitize(); +} + +void +Cylinder::prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) +{ + if (NULL == my_transformation) + { + return; + } + + my_transformation->update(time); + my_transformation->apply(p1); + my_transformation->apply(p2); + + xi = p2 - p1; + + length = xi.length(); + + xi.unitize(); +} + +BoundingBox +Cylinder::get_bounding_box() +{ + BoundingBox bbox; + bbox.accommodate(p1); + bbox.accommodate(p2); + bbox.pad(radius); + return bbox; +} + +double +Cylinder::point_signed_distance(const Vector3d &x) const +{ + double D = std::numeric_limits::max(); + + // convert x to cylindrical coordinates + + // v is the vector from p1 to x. + Vector3d v = x - p1; + + // Xix is the projection of v along the axis of the cylinder + const double Xix = Dot(v,xi); + + // u is the associated vector + Vector3d u = Xix*xi; + + // Rx is the distance from the axis of the cylinder to x + + const double Rx = (v - u).length(); + + if ( Xix <= 0 ) + { + // x is behind the cylinder. + // The closest point on the cylinder lies on the rear circular face. + + if ( Rx <= radius ) + { + D = -Xix; + } + else + { + D = std::sqrt(Xix*Xix + (Rx-radius)*(Rx-radius)); + } + } + else if( Xix > 0 && Xix < length) + { + // x is not in front of or behind the cylinder: it is alongside the cylinder. + // the signed distance is given by Rx - radius unless this point is closer + // to one of the circular faces + + if ( Rx >= radius ) + { + D = Rx - radius; + } + else + { + D = std::max( std::max(Rx - radius, -Xix), Xix-length ); + } + } + else + { + // x is in front of the cylinder. + // The closest point on the cylinder lies on the front circular face. + + if ( Rx <= radius ) + { + D = Xix-length; + } + else + { + D = std::sqrt((Xix-length)*(Xix-length) + (Rx-radius)*(Rx-radius)); + } + } + + return dist_sign*D; +} + +Point::Point(const std::string & n, // surface name + const Vector3d & coords) + : SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign(), + my_coords(coords) +{ +} + +double +Point::point_signed_distance(const Vector3d &x) const +{ + return (x-my_coords).length_squared(); +} + +BoundingBox +Point::get_bounding_box() +{ + BoundingBox bbox; + bbox.accommodate(my_coords); + return bbox; +} + +Sphere::Sphere(const std::string & n, // surface name + const Vector3d & center, + const double radius, + const int sign) + : SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign(), + myDistSign(sign), + myCenter(center), + myRadius(radius) +{ +} + +void +Sphere::prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) +{ + if (nullptr != my_transformation) + { + my_transformation->update(time); + my_transformation->apply(myCenter); + } +} + +BoundingBox +Sphere::get_bounding_box() +{ + return BoundingBox( + Vector3d(myCenter[0]-myRadius, myCenter[1]-myRadius, myCenter[2]-myRadius), + Vector3d(myCenter[0]+myRadius, myCenter[1]+myRadius, myCenter[2]+myRadius) + ); +} + +double +Sphere::point_signed_distance(const Vector3d &x) const +{ + return (myDistSign*((x-myCenter).length() - myRadius)); +} + +Ellipsoid::Ellipsoid( + const std::string & name, // surface name + const std::vector & center, + const std::vector & semiAxes, + const std::vector & rotationVec, + const int sign) +: SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign(), + mySign(sign) +{ + ThrowAssert(center.size() == 3 && semiAxes.size() == 3); + mySemiAxesNorm = 0.0; + for(unsigned i = 0; i < 3; ++i) { + myCenter[i] = center[i]; + mySemiAxes[i] = semiAxes[i]; + mySemiAxesNorm += mySemiAxes[i]*mySemiAxes[i]; + } + mySemiAxesNorm = std::sqrt(mySemiAxesNorm); + + if (!rotationVec.empty()) + { + ThrowAssert(rotationVec.size() == 3); + Vector3d pointRotation(-rotationVec[0], -rotationVec[1], -rotationVec[2]); // myRotation is the rotation used for the query point locations, which is the opposite of the rotation of the ellipsoid + myRotation = std::make_unique(); + myRotation->set_from_rotation_vector(pointRotation); + } +} + +BoundingBox +Ellipsoid::get_bounding_box() +{ + if (!myRotation) + return BoundingBox(myCenter-mySemiAxes, myCenter+mySemiAxes); + + const Vector3d min = myCenter-mySemiAxes; + const Vector3d max = myCenter+mySemiAxes; + BoundingBox rotatedBbox; + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(min[0],min[1],min[2]))); + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(min[0],min[1],max[2]))); + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(min[0],max[1],min[2]))); + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(min[0],max[1],max[2]))); + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(max[0],min[1],min[2]))); + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(max[0],min[1],max[2]))); + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(max[0],max[1],min[2]))); + rotatedBbox.accommodate(myRotation->reverse_rotate_3d_vector(Vector3d(max[0],max[1],max[2]))); + + return rotatedBbox; +} + +double +Ellipsoid::point_signed_distance(const Vector3d &x) const +{ + Vector3d delta = x-myCenter; + if (myRotation) + delta = myRotation->rotate_3d_vector(delta); + // Not an exact distance except in the case of a sphere + const double dx = delta[0]/mySemiAxes[0]; + const double dy = delta[1]/mySemiAxes[1]; + const double dz = delta[2]/mySemiAxes[2]; + return mySign*(mySemiAxesNorm*std::sqrt(dx*dx+dy*dy+dz*dz)-mySemiAxesNorm); +} + +Plane::Plane(const std::string & n, // surface name + const double normal[3], + const double offset, + const double multiplier) + : SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign(), + myMultiplier(multiplier), + myNormal(normal), + myOffset(offset) +{ + myNormal.unitize(); +} + +double +Plane::point_signed_distance(const Vector3d &x) const +{ + return myMultiplier*(Dot(myNormal, x) + myOffset); +} + +BoundingBox +Plane::get_bounding_box() +{ + //bounding box is entire domain + return BoundingBox(Vector3d(-std::numeric_limits::max(), -std::numeric_limits::max(), -std::numeric_limits::max()), + Vector3d(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); +} + +Random::Random(const unsigned long seed) + : SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign(), + my_amplitude(1.0) +{ + my_srand(seed); +} + +double +Random::point_signed_distance(const Vector3d &x) const +{ + // generate random number between -my_amplitude and my_amplitude + return my_amplitude * (-1.0 + 2.0 * my_rand()); +} + +BoundingBox +Random::get_bounding_box() +{ + //bounding box is entire domain + return BoundingBox(Vector3d(-std::numeric_limits::max(), -std::numeric_limits::max(), -std::numeric_limits::max()), + Vector3d(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max())); +} + +Analytic_Isosurface::Analytic_Isosurface() + : SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign() +{ +} + +BoundingBox +Analytic_Isosurface::get_bounding_box() +{ + return BoundingBox( + Vector3d(-1.,-1.,-1.), + Vector3d(1.,1.,1.) + ); +} + +double +Analytic_Isosurface::point_signed_distance(const Vector3d &coord) const +{ + const double x = coord[0]; + const double y = coord[1]; + const double z = coord[2]; + return 2.*y*(y*y-3.*x*x)*(1.-z*z) + std::pow(x*x+y*y,2) - (9.*z*z-1.)*(1.-z*z); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_AnalyticSurf.hpp b/packages/krino/krino/krino_lib/Akri_AnalyticSurf.hpp new file mode 100644 index 000000000000..18f70a5bb0e1 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AnalyticSurf.hpp @@ -0,0 +1,186 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_AnalyticSurf_h +#define Akri_AnalyticSurf_h + +#include +#include +#include + +#include +#include + +#include + +namespace stk { namespace mesh { class BulkData; } } +namespace stk { namespace mesh { class Entity; } } +namespace stk { namespace mesh { class MetaData; } } +namespace stk { namespace mesh { class Selector; } } + +namespace krino { + +class Cylinder: public SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign { +public: + Cylinder(const std::string & n, // surface name + const double e1[3], // first endpoint of axis + const double e2[3], // second endpoint of axis + const double r, // radius of cylinder + const int sign); + + virtual ~Cylinder() {} + + virtual Surface_Type type() const override { return CYLINDER; } + virtual size_t storage_size() const override { return sizeof(Cylinder); } + + virtual void prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) override; + virtual double point_signed_distance(const Vector3d &x) const override; + virtual BoundingBox get_bounding_box() override; + +private: + int dist_sign; + double radius; + double length; + + // two end points + Vector3d p1, p2; + + // unit vector in direction of axis. + Vector3d xi; +}; + +class Sphere: public SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign { +public: + Sphere(const std::string & n, // surface name + const Vector3d & center, + const double radius, + const int sign = 1); + + virtual ~Sphere() {} + + virtual Surface_Type type() const override { return SPHERE; } + virtual size_t storage_size() const override { return sizeof(Sphere); } + + virtual void prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) override; + virtual double point_signed_distance(const Vector3d &x) const override; + virtual BoundingBox get_bounding_box() override; + +private: + int myDistSign; + Vector3d myCenter; + double myRadius; +}; + +class Ellipsoid : public SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign { +public: + Ellipsoid( + const std::string & name, // surface name + const std::vector & center, + const std::vector & semiAxes, + const std::vector & rotationVec, + const int sign); + + virtual ~Ellipsoid() {} + + virtual Surface_Type type() const override { return ELLIPSOID; } + virtual size_t storage_size() const override { return sizeof(Ellipsoid); } + + virtual double point_signed_distance(const Vector3d &x) const override; + virtual BoundingBox get_bounding_box() override; + +private: + int mySign; + + //center of ellipsoid + Vector3d myCenter; + + Vector3d mySemiAxes; + double mySemiAxesNorm; + std::unique_ptr myRotation; +}; + +class Plane: public SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign { +public: + Plane(const std::string & n, // surface name + const double normal[3], + const double offset, + const double multiplier); + + virtual ~Plane() {} + + virtual Surface_Type type() const override { return PLANE; } + virtual size_t storage_size() const override { return sizeof(Plane); } + + virtual double point_signed_distance(const Vector3d &x) const override; + virtual BoundingBox get_bounding_box() override; + +private: + double myMultiplier; + + Vector3d myNormal; //unit normal to plane + double myOffset; //eq of plane is 0 = offset + normal \dot x +}; + +class Point: public SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign { +public: + Point(const std::string & n, // surface name + const Vector3d & coords); + + virtual ~Point() {} + + virtual Surface_Type type() const override { return POINT; } + virtual size_t storage_size() const override { return sizeof(Point); } + + virtual double point_signed_distance(const Vector3d &x) const override; + virtual BoundingBox get_bounding_box() override; + +private: + Vector3d my_coords; +}; + +class Random : public SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign { +public: + Random(const unsigned long seed); + + virtual ~Random() {} + + virtual Surface_Type type() const override { return RANDOM; } + virtual size_t storage_size() const override { return sizeof(Random); } + virtual void prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) override + { + if (truncation_length > 0.0) my_amplitude = truncation_length; + } + + virtual double point_signed_distance(const Vector3d &x) const override; + virtual BoundingBox get_bounding_box() override; + +private: + mutable unsigned long iseed; + double my_amplitude; + + double my_rand() const { iseed = (1664525L*iseed + 1013904223L) & 0xffffffff; + return( ((double) iseed) / ((double) 0xffffffff) ); } + void my_srand(unsigned int seed) const {iseed = seed;} +}; + +class Analytic_Isosurface: public SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign { +public: + Analytic_Isosurface(); + + virtual ~Analytic_Isosurface() {} + + virtual Surface_Type type() const override { return SPHERE; } + virtual size_t storage_size() const override { return sizeof(Analytic_Isosurface); } + + virtual double point_signed_distance(const Vector3d &x) const override; + virtual BoundingBox get_bounding_box() override; +}; + +} // namespace krino + +#endif // Akri_AnalyticSurf_h diff --git a/packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.cpp b/packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.cpp new file mode 100644 index 000000000000..5062a28d0c4b --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.cpp @@ -0,0 +1,203 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +static int surface_sign_at_position(const Surface & surface, const Vector3d & pt) +{ + const double phi = surface.point_signed_distance(pt); + return ( (phi < 0.) ? -1 : 1 ); // GOMA sign convention +} + +static std::function build_edge_distance_function(const Surface & surface, const Segment3d & edge) +{ + std::function distanceFunction = + [&surface, &edge](const double x) + { + return surface.point_signed_distance((1.-x)*edge.GetNode(0) + x*edge.GetNode(1)); + }; + return distanceFunction; +} + +static double find_crossing_position(const Surface & surface, const Segment3d & edge) +{ + const double phi0 = surface.point_signed_distance(edge.GetNode(0)); + const double phi1 = surface.point_signed_distance(edge.GetNode(1)); + const auto result = find_root(build_edge_distance_function(surface, edge), 0., 1., phi0, phi1); + ThrowRequire(result.first); + return result.second; +} + +SurfaceElementCutter::SurfaceElementCutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const Surface & surface) +: myMasterElem(MasterElementDeterminer::getMasterElement(mesh.bucket(element).topology())), + mySurface(surface) +{ + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + fill_element_node_coordinates(mesh, element, coordsField, myElementNodeCoords); +} + +bool SurfaceElementCutter::have_crossing(const InterfaceID interface, const Segment3d & edge) const +{ + return surface_sign_at_position(mySurface, parametric_to_global_coordinates(edge.GetNode(0))) != + surface_sign_at_position(mySurface, parametric_to_global_coordinates(edge.GetNode(1))); +} + +double SurfaceElementCutter::interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const +{ + const Segment3d globalEdge(parametric_to_global_coordinates(edge.GetNode(0)), parametric_to_global_coordinates(edge.GetNode(1))); + return find_crossing_position(mySurface, globalEdge); +} + +int SurfaceElementCutter::sign_at_position(const InterfaceID interface, const Vector3d & paramCoords) const +{ + return surface_sign_at_position(mySurface, parametric_to_global_coordinates(paramCoords)); +} + +Vector3d SurfaceElementCutter::parametric_to_global_coordinates(const Vector3d & pCoords) const +{ + std::vector nodalShapeFunctions(myMasterElem.num_nodes()); + myMasterElem.shape_fcn(1, pCoords.data(), nodalShapeFunctions.data()); + Vector3d pt(Vector3d::ZERO); + for (unsigned n=0; n & elementsToIntersect, + const Surface & surface, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) +{ + const bool intersectionPointIsOwned = true; + std::vector intersectionPointSortedDomains; + const int dim = mesh.mesh_meta_data().spatial_dimension(); + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + std::set> edgesAlreadyChecked; + for (auto && elem : elementsToIntersect) + { + const stk::topology topology = mesh.bucket(elem).topology(); + const stk::mesh::Entity* elem_nodes = mesh.begin_nodes(elem); + const unsigned numEdges = topology.num_edges(); + + for (unsigned iedge = 0; iedge < numEdges; ++iedge) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(topology, iedge); + const stk::mesh::Entity node0 = elem_nodes[edge_node_ordinals[0]]; + const stk::mesh::Entity node1 = elem_nodes[edge_node_ordinals[1]]; + const stk::mesh::EntityId node0Id = mesh.identifier(node0); + const stk::mesh::EntityId node1Id = mesh.identifier(node1); + const std::array edgeNodeIds = (node0Id < node1Id) ? std::array{node0Id, node1Id} : std::array{node1Id, node0Id}; + auto iter = edgesAlreadyChecked.lower_bound(edgeNodeIds); + if (iter == edgesAlreadyChecked.end() || edgeNodeIds != *iter) + { + edgesAlreadyChecked.insert(iter, edgeNodeIds); + const Vector3d node0Coords(field_data(coordsField, node0), dim); + const Vector3d node1Coords(field_data(coordsField, node1), dim); + const double phi0 = surface.point_signed_distance(node0Coords); + const double phi1 = surface.point_signed_distance(node1Coords); + const bool haveCrossing = (phi0 < 0.) ? (phi1 >= 0.) : (phi1 < 0.); + if (haveCrossing) + { + const InterfaceID interface(0,0); + const double location = find_crossing_position(surface, Segment3d(node0Coords, node1Coords)); + interface.fill_sorted_domains(intersectionPointSortedDomains); + const std::vector intersectionPointNodes{node0,node1}; + if (intersectionPointFilter(intersectionPointNodes, intersectionPointSortedDomains)) + intersectionPoints.emplace_back(intersectionPointIsOwned, intersectionPointNodes, std::vector{1.-location, location}, intersectionPointSortedDomains); + } + } + } + } +} + +static BoundingBox compute_nodal_bounding_box(const stk::mesh::BulkData & mesh) +{ + const int nDim = mesh.mesh_meta_data().spatial_dimension(); + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + + BoundingBox nodeBbox; + for ( auto && bucket : mesh.buckets(stk::topology::NODE_RANK) ) + { + double *coord = field_data(coordsField, *bucket); + for (size_t n = 0; n < bucket->size(); ++n) + nodeBbox.accommodate( Vector3d(coord+n*nDim, nDim) ); + } + + return nodeBbox; +} + +static void prepare_to_compute_with_surface(const stk::mesh::BulkData & mesh, const Surface & surface) +{ + const BoundingBox nodeBbox = compute_nodal_bounding_box(mesh); + Surface & nonConstSurface = const_cast(surface); + nonConstSurface.prepare_to_compute(0.0, nodeBbox, 0.); // Setup including communication of facets that are within this processors narrow band +} + +void AnalyticSurfaceInterfaceGeometry::prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + myElementsToIntersect = get_owned_parent_elements(mesh, myActivePart, myCdfemSupport, myPhaseSupport); + prepare_to_compute_with_surface(mesh, mySurface); +} + +void AnalyticSurfaceInterfaceGeometry::prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const std::vector & elementsToIntersect, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + myElementsToIntersect = elementsToIntersect; + prepare_to_compute_with_surface(mesh, mySurface); +} + +std::vector AnalyticSurfaceInterfaceGeometry::get_edge_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + const IntersectionPointFilter intersectionPointFilter = keep_all_intersection_points_filter(); + std::vector intersectionPoints; + append_surface_edge_intersection_points(mesh, myElementsToIntersect, mySurface, intersectionPointFilter, intersectionPoints); + return intersectionPoints; +} + +void AnalyticSurfaceInterfaceGeometry::append_element_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + const std::vector & elementsToIntersect, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) const +{ + prepare_to_process_elements(mesh, elementsToIntersect, nodesToCapturedDomains); + append_surface_edge_intersection_points(mesh, myElementsToIntersect, mySurface, intersectionPointFilter, intersectionPoints); +} + +std::unique_ptr AnalyticSurfaceInterfaceGeometry::build_element_cutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const std::function &)> & intersectingPlanesDiagonalPicker) const +{ + std::unique_ptr cutter; + cutter.reset( new SurfaceElementCutter(mesh, element, mySurface) ); + return cutter; +} + +PhaseTag AnalyticSurfaceInterfaceGeometry::get_starting_phase(const ElementCutter * cutter) const +{ + PhaseTag phase; + ThrowRequire(1 == myCdfemSupport.num_ls_fields()); + phase.add(myCdfemSupport.ls_field(0).identifier, 0); + return phase; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.hpp b/packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.hpp new file mode 100644 index 000000000000..f72ce103d4d1 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AnalyticSurfaceInterfaceGeometry.hpp @@ -0,0 +1,111 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_AnalyticSurfaceInterfaceGeometry_h +#define Akri_AnalyticSurfaceInterfaceGeometry_h + +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" + +namespace krino { + +class CDFEM_Support; +class Phase_Support; + +class SurfaceElementCutter : public ElementCutter +{ +public: + SurfaceElementCutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const Surface & surface); + virtual ~SurfaceElementCutter() {} + + virtual bool might_have_interior_or_face_intersections() const override { return false; } + virtual void fill_interior_intersections(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const override {} + virtual std::vector get_sorted_cutting_interfaces() const override + { std::vector interfaces; interfaces.push_back(InterfaceID(0,0)); return interfaces; } + virtual std::vector get_interface_signs_based_on_crossings(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains) const override + { std::vector interfaceSigns(1,0); return interfaceSigns; } + virtual void fill_tetrahedron_face_interior_intersections(const std::array & faceNodes, + const InterfaceID & interface1, + const InterfaceID & interface2, + const ElementIntersectionPointFilter & intersectionPointFilter, + std::vector & intersections) const override {} + virtual std::string visualize(const stk::mesh::BulkData & mesh) const override { std::string empty; return empty; } + virtual bool have_crossing(const InterfaceID interface, const Segment3d & edge) const override; + virtual double interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const override; + virtual int sign_at_position(const InterfaceID interface, const Vector3d & paramCoords) const override; + virtual int get_starting_phase_for_cutting_surfaces() const override { return 0; } + +private: + Vector3d parametric_to_global_coordinates(const Vector3d & pCoords) const; + + const MasterElement & myMasterElem; + std::vector myElementNodeCoords; + const Surface & mySurface; +}; + +class AnalyticSurfaceInterfaceGeometry : public InterfaceGeometry { + +public: + AnalyticSurfaceInterfaceGeometry(const Surface & surface, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +: mySurface(surface), + myActivePart(activePart), + myCdfemSupport(cdfemSupport), + myPhaseSupport(phaseSupport) {} + + virtual ~AnalyticSurfaceInterfaceGeometry() {} + + virtual void prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToSnappedDomains) const override; + virtual void prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const std::vector & elementsToIntersect, + const NodeToCapturedDomainsMap & nodesToSnappedDomains) const override; + + virtual std::vector get_edge_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToSnappedDomains) const override; + virtual void append_element_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToSnappedDomains, + const std::vector & elementsToIntersect, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) const override; + + virtual void store_phase_for_uncut_elements(const stk::mesh::BulkData & mesh) const override {} + virtual void store_phase_for_elements_that_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const std::vector & intersectionPoints, + const std::vector & independentSnapInfos, + const NodeToCapturedDomainsMap & nodesToSnappedDomains) const override {} + virtual const ElementToDomainMap & get_phase_for_uncut_elements() const override { return myUncutElementPhases; } + + virtual std::unique_ptr build_element_cutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const std::function &)> & intersectingPlanesDiagonalPicker) const override; + + virtual PhaseTag get_starting_phase(const ElementCutter * cutter) const override; + +private: + const Surface & mySurface; + const stk::mesh::Part & myActivePart; + const CDFEM_Support & myCdfemSupport; + const Phase_Support & myPhaseSupport; + ElementToDomainMap myUncutElementPhases; + mutable std::vector myElementsToIntersect; +}; + +} // namespace krino + +#endif diff --git a/packages/krino/krino/krino_lib/Akri_AuxMetaData.cpp b/packages/krino/krino/krino_lib/Akri_AuxMetaData.cpp new file mode 100644 index 000000000000..99584a22279a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AuxMetaData.cpp @@ -0,0 +1,396 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include "Akri_FieldRef.hpp" // for FieldRef + +#include +#include "stk_mesh/base/FieldBase.hpp" // for FieldState +#include "stk_mesh/base/MetaData.hpp" +#include "stk_mesh/base/Part.hpp" // for Part +#include "stk_topology/topology.hpp" // for topology, etc +#include +#include "stk_util/util/ReportHandler.hpp" // for ThrowAssert, etc + +namespace krino +{ +const FieldType FieldType::UNSIGNED_INTEGER_64("UNSIGNED INTEGER 64", typeid(uint64_t), 1); +const FieldType FieldType::UNSIGNED_INTEGER("UNSIGNED INTEGER", typeid(unsigned), 1); +const FieldType FieldType::INTEGER("INTEGER", typeid(int), 1); +const FieldType FieldType::REAL("REAL", typeid(double), 1); + +const FieldType FieldType::VECTOR_2D("VECTOR_2D", typeid(double), 2); +const FieldType FieldType::VECTOR_3D("VECTOR_3D", typeid(double), 3); +const FieldType FieldType::MATRIX_22("MATRIX_22", typeid(double), 4); +const FieldType FieldType::MATRIX_33("MATRIX_33", typeid(double), 9); + +AuxMetaData & +AuxMetaData::get(const stk::mesh::MetaData & stk_meta) +{ + AuxMetaData * aux_meta = const_cast(stk_meta.get_attribute()); + ThrowRequireMsg(nullptr != aux_meta, "AuxMetaData not found on MetaData."); + return *aux_meta; +} + +AuxMetaData & +AuxMetaData::create(stk::mesh::MetaData & stk_meta) +{ + AuxMetaData * aux_meta = const_cast(stk_meta.get_attribute()); + ThrowRequireMsg(nullptr == aux_meta, "AuxMetaData::create should be caled only once per MetaData."); + if (nullptr == aux_meta) + { + aux_meta = new AuxMetaData(stk_meta); + stk_meta.declare_attribute_with_delete(aux_meta); + } + return *aux_meta; +} + +AuxMetaData::AuxMetaData(stk::mesh::MetaData & stk_meta) + : my_meta(stk_meta), + is_fmwk(false), + my_assert_32bit_flag(false), + my_force_64bit_flag(true), // just to guarantee testing 64 bit as much as possible + my_active_part(nullptr), + my_exposed_boundary_part(nullptr), + my_block_boundary_part(nullptr) +{ + stk::mesh::Part * existing_active_part = my_meta.get_part("ACTIVE_CONTEXT_BIT"); + if (nullptr != existing_active_part) + { + my_active_part = existing_active_part; + my_exposed_boundary_part = my_meta.get_part("EXPOSED_BOUNDARY_CONTEXT_BIT"); + my_block_boundary_part = my_meta.get_part("BLOCK_BOUNDARY_CONTEXT_BIT"); + } + else + { + ThrowRequireMsg(my_meta.spatial_dimension() > 0, "For non-Fmwk usage, AuxMetaData cannot be created until after the spatial_dimension is set on the stk::mesh::MetaData."); + ThrowRequireMsg(!my_meta.is_commit(), "For non-Fmwk usage, AuxMetaData must be created before the stk::mesh::MetaData is committed."); + my_active_part = &my_meta.declare_part("ACTIVE_CONTEXT_BIT"); + my_exposed_boundary_part = &my_meta.declare_part("EXPOSED_BOUNDARY_CONTEXT_BIT", my_meta.side_rank()); + my_block_boundary_part = &my_meta.declare_part("BLOCK_BOUNDARY_CONTEXT_BIT", my_meta.side_rank()); + } + + my_active_globally_shared_selector = stk::mesh::Selector(*my_active_part & my_meta.globally_shared_part()); + my_active_not_ghost_selector = stk::mesh::Selector(*my_active_part & (my_meta.locally_owned_part() | my_meta.globally_shared_part())); + my_active_locally_owned_selector = stk::mesh::Selector(*my_active_part & my_meta.locally_owned_part()); +} + +void +AuxMetaData::set_fmwk_functions( + const std::function & in_fmwk_get_iopart, + const std::function & in_fmwk_iopart, + const std::function & in_fmwk_define_iopart_alias, + const std::function & in_fmwk_register_field, + stk::mesh::Part * in_exposed_boundary_part, + stk::mesh::Part * in_block_boundary_part) +{ + is_fmwk = true; + clear_force_64bit_flag(); + fmwk_get_iopart = in_fmwk_get_iopart; + fmwk_iopart = in_fmwk_iopart; + fmwk_define_iopart_alias = in_fmwk_define_iopart_alias; + fmwk_register_field = in_fmwk_register_field; + my_exposed_boundary_part = in_exposed_boundary_part; + my_block_boundary_part = in_block_boundary_part; +} + +void +AuxMetaData::set_inducer_functions( + const std::function & in_inducer_induce_topology_nodesets, + const std::function & in_inducer_get_nodal_field_topology, + const std::function & in_inducer_selectField) +{ + fn_induce_topology_nodesets = in_inducer_induce_topology_nodesets; + fn_get_nodal_field_topology = in_inducer_get_nodal_field_topology; + fn_selectField = in_inducer_selectField; +} + +stk::mesh::Part & AuxMetaData::block_boundary_part() const +{ + ThrowAssert(nullptr != my_block_boundary_part); return *my_block_boundary_part; +} + +stk::mesh::Part & AuxMetaData::exposed_boundary_part() const +{ + ThrowAssert(nullptr != my_exposed_boundary_part); return *my_exposed_boundary_part; +} + +bool +AuxMetaData::has_field( const stk::mesh::EntityRank obj_type, const std::string& name ) const +{ + return NULL != my_meta.get_field(obj_type, name); +} + +bool +AuxMetaData::has_field( const stk::mesh::EntityRank obj_type, const std::string& name, stk::mesh::FieldState state ) const +{ + stk::mesh::FieldBase* field_ptr = my_meta.get_field(obj_type, name); + if (NULL == field_ptr) return false; + return NULL != field_ptr->field_state(state); +} + +FieldRef +AuxMetaData::get_field( const stk::mesh::EntityRank obj_type, const std::string& name ) const +{ + stk::mesh::FieldBase* field_ptr = my_meta.get_field(obj_type, name); + ThrowRequireMsg(NULL != field_ptr, "Field \"" << name << "\" not found."); + return FieldRef(field_ptr); +} + + +FieldRef +AuxMetaData::get_field( const stk::mesh::EntityRank obj_type, const std::string& name, stk::mesh::FieldState state ) const +{ + stk::mesh::FieldBase* field_ptr = my_meta.get_field(obj_type, name); + if(field_ptr == nullptr) + { + std::ostringstream err_msg; + err_msg << "Field \"" << name << "\" not found.\n"; + err_msg << "Registered fields are:\n"; + for(auto && field : my_meta.get_fields(obj_type)) + { + err_msg << field->name() << "\n"; + } + throw std::runtime_error(err_msg.str()); + } + return FieldRef(field_ptr, state); +} + +bool +AuxMetaData::has_part( const std::string& name ) const +{ + stk::mesh::Part * part = my_meta.get_part(name); + // If the part is not found see if the name is actually a Fmwk alias + if (!part && is_fmwk) part = fmwk_get_iopart(name); + return NULL != part; +} + +stk::mesh::Part& +AuxMetaData::get_part( const std::string& name ) const +{ + stk::mesh::Part * part = my_meta.get_part(name); + // If the part is not found see if the name is actually a Fmwk alias + if (!part && is_fmwk) part = fmwk_get_iopart(name); + ThrowRequireMsg(part, "Could not find part " << name;); + return *part; +} + +void +AuxMetaData::define_part_alias( stk::mesh::Part & part , const std::string & alias ) +{ + if (is_fmwk) + { + fmwk_define_iopart_alias(part, alias); + return; + } + // Silent no-op +} + +void +AuxMetaData::assign_part_id(stk::mesh::Part& part) +{ + static int64_t max_existing_part_id = stk::mesh::Part::INVALID_ID; + if (max_existing_part_id == stk::mesh::Part::INVALID_ID) + { + max_existing_part_id = 0; + for (auto && existing_part : my_meta.get_parts()) + { + max_existing_part_id = std::max(max_existing_part_id, existing_part->id()); + } + } + if (part.id() == stk::mesh::Part::INVALID_ID) + { + const int64_t part_id = ++max_existing_part_id; + my_meta.set_part_id(part, part_id); + } +} + +stk::mesh::Part& +AuxMetaData::declare_io_part( const std::string& name, const stk::mesh::EntityRank obj_type, const bool restartOnlyIOPart ) +{ + if (is_fmwk) + { + stk::mesh::Part & part = fmwk_iopart(name, obj_type); + if (restartOnlyIOPart) + { + myRestartOnlyIOParts.push_back(&part); + } + assign_part_id(part); + return part; + } + + stk::mesh::Part & part = my_meta.declare_part(name, obj_type); + + if (restartOnlyIOPart) + { + myRestartOnlyIOParts.push_back(&part); + } + else + { + if (!stk::io::is_part_io_part(part)) + { + stk::io::put_io_part_attribute(part); + } + } + + assign_part_id(part); + + return part; +} + +stk::mesh::Part& +AuxMetaData::declare_io_part_with_topology( const std::string& name, const stk::topology topology, const bool restartOnlyIOPart ) +{ + if (is_fmwk) + { + stk::mesh::Part & root_part = my_meta.get_topology_root_part(topology); + stk::mesh::Part & part = fmwk_iopart(name, root_part.primary_entity_rank()); + my_meta.declare_part_subset(root_part, part); + if (restartOnlyIOPart) + { + myRestartOnlyIOParts.push_back(&part); + } + assign_part_id(part); + return part; + } + + stk::mesh::Part & part = my_meta.declare_part_with_topology(name, topology); + + if (restartOnlyIOPart) + { + myRestartOnlyIOParts.push_back(&part); + } + else + { + if (!stk::io::is_part_io_part(part)) + { + stk::io::put_io_part_attribute(part); + } + } + + assign_part_id(part); + + return part; +} + +FieldRef +AuxMetaData::declare_field( + const std::string & fld_name, + const FieldType & field_type, + const stk::mesh::EntityRank entity_rank, + const unsigned num_states) +{ + stk::mesh::FieldBase * field = NULL; + const std::type_info & value_type = field_type.type_info(); + if (value_type == typeid(int)) + field = &my_meta.declare_field< stk::mesh::Field >(entity_rank, fld_name, num_states); + else if (value_type == typeid(double)) + field = &my_meta.declare_field< stk::mesh::Field >(entity_rank, fld_name, num_states); + else if (value_type == typeid(unsigned)) + field = &my_meta.declare_field< stk::mesh::Field >(entity_rank, fld_name, num_states); + else if (value_type == typeid(int64_t)) + field = &my_meta.declare_field< stk::mesh::Field >(entity_rank, fld_name, num_states); + else if (value_type == typeid(uint64_t)) + field = &my_meta.declare_field< stk::mesh::Field >(entity_rank, fld_name, num_states); + else { + ThrowRequireMsg(false, "Unhandled primitive type " << value_type.name()); + } + + return FieldRef(field); +} + +FieldRef +AuxMetaData::register_field( + const std::string & fld_name, + const FieldType & field_type, + const stk::mesh::EntityRank entity_rank, + const unsigned num_states, + const unsigned dimension, + const stk::mesh::Part & part, + const void * value_type_init) +{ + // don't rely on induction of non-ranked superset parts + // Note that we are only checking nodal fields here, but this could also be a problem if we are counting on a non-ranked superset part to be induced on faces or edges (unlikely?). + //ThrowRequire(entity_rank != stk::topology::NODE_RANK || !is_unranked_superset_part(part)); + + if (is_fmwk) + { + return FieldRef(fmwk_register_field(fld_name, field_type.name(), field_type.type_info(), field_type.dimension(), entity_rank, num_states, dimension, part, value_type_init)); + } + + const unsigned field_length = field_type.dimension()*dimension; + if (field_type.name() == FieldType::VECTOR_2D.name()) + { + auto & field = my_meta.declare_field< stk::mesh::Field >(entity_rank, fld_name, num_states); + stk::mesh::put_field_on_mesh(field, part, field_length, nullptr); + return FieldRef(field); + } + else if (field_type.name() == FieldType::VECTOR_3D.name()) + { + auto & field = my_meta.declare_field< stk::mesh::Field >(entity_rank, fld_name, num_states); + stk::mesh::put_field_on_mesh(field, part, field_length, nullptr); + return FieldRef(field); + } + + FieldRef field = declare_field(fld_name, field_type, entity_rank, num_states); + stk::mesh::put_field_on_mesh(field.field(), part, field_length, value_type_init); + return field; +} + +void +AuxMetaData::induce_topology_nodesets(stk::mesh::Selector selector) const +{ + if (fn_induce_topology_nodesets) + { + fn_induce_topology_nodesets(selector); + } + // no-op if inducer is not set + return; +} + +stk::topology +AuxMetaData::get_nodal_field_topology( const stk::mesh::FieldBase & field, stk::mesh::Entity entity ) const +{ + return get_nodal_field_topology(field, my_meta.mesh_bulk_data().bucket(entity)); +} + +stk::topology AuxMetaData::get_nodal_field_topology( const stk::mesh::FieldBase & field, const stk::mesh::Bucket & bucket ) const +{ + if (fn_get_nodal_field_topology) + { + return fn_get_nodal_field_topology(field, bucket); + } + // return bucket topology if inducer is not set and field is defined on bucket + const stk::mesh::Selector field_selector = stk::mesh::selectField(field); + stk::topology field_topology = stk::topology::INVALID_TOPOLOGY; + if (field_selector(bucket)) field_topology = bucket.topology(); + return field_topology; +} + +stk::mesh::Selector AuxMetaData::selectField( const stk::mesh::FieldBase & field, const stk::mesh::EntityRank target_rank ) const +{ + if (fn_selectField && field.entity_rank() == stk::topology::NODE_RANK) + { + return fn_selectField( field, target_rank ); + } + // return stk::mesh::selectField if inducer is not set + return stk::mesh::selectField(field); +} + +//---------------------------------------------------------------------- + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_AuxMetaData.hpp b/packages/krino/krino/krino_lib/Akri_AuxMetaData.hpp new file mode 100644 index 000000000000..e7590663a4be --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_AuxMetaData.hpp @@ -0,0 +1,180 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_AuxMetaData_h +#define Akri_AuxMetaData_h + +#include +#include // for operator<<, basic_string, etc +#include "stk_mesh/base/FieldState.hpp" // for FieldState +#include "stk_mesh/base/Selector.hpp" // for Selector +#include "stk_mesh/base/Entity.hpp" // for Entity + +namespace krino { class FieldRef; } + +#if ((__GNUC__ == 4) && (__GNUC_MINOR__ == 9)) || (__GNUC__ == 5) +// Looks like there is an issue with these compilers +// related to instatiating std::function with an incomplete +// type leading to undefine behavior +#include "stk_mesh/base/Part.hpp" // for Part +#include "stk_mesh/base/MetaData.hpp" +#include "stk_mesh/base/FieldBase.hpp" // for FieldState +#else +namespace stk { namespace mesh { class MetaData; } } +namespace stk { namespace mesh { class Part; } } +namespace stk { namespace mesh { class FieldBase; } } +#endif + +namespace krino +{ + +class FieldType { +public: + static const FieldType UNSIGNED_INTEGER_64; + static const FieldType UNSIGNED_INTEGER; + static const FieldType INTEGER; + static const FieldType REAL; + + static const FieldType VECTOR_2D; + static const FieldType VECTOR_3D; + static const FieldType MATRIX_22; + static const FieldType MATRIX_33; + + const std::string & name() const { return my_name; } + const std::type_info & type_info() const { return my_type_info; } + unsigned dimension() const { return my_dimension; } + + FieldType(const std::string & nme, const std::type_info & type, const unsigned dim) : my_name(nme), my_type_info(type), my_dimension(dim) {} + +private: + const std::string my_name; + const std::type_info & my_type_info; + const unsigned my_dimension; +}; + +class AuxMetaData final +{ +public: + static AuxMetaData & get(const stk::mesh::MetaData & stk_meta); + + static AuxMetaData & create(stk::mesh::MetaData & stk_meta); // must be called before calling get + + AuxMetaData ( const AuxMetaData & ) = delete; + AuxMetaData & operator= ( const AuxMetaData & ) = delete; + + bool using_fmwk() const { return is_fmwk; } + + stk::mesh::Part & active_part() const { return *my_active_part; } + stk::mesh::Part & block_boundary_part() const; + stk::mesh::Part & exposed_boundary_part() const; + + const stk::mesh::Selector & active_not_ghost_selector() const { return my_active_not_ghost_selector; } + const stk::mesh::Selector & active_locally_owned_selector() const { return my_active_locally_owned_selector; } + const stk::mesh::Selector & active_globally_shared_selector() const { return my_active_globally_shared_selector; } + + bool get_force_64bit_flag() const { return my_force_64bit_flag; } + void clear_force_64bit_flag() { my_force_64bit_flag = false; } + bool get_assert_32bit_flag() { return my_assert_32bit_flag; } + void set_assert_32bit_flag() { my_force_64bit_flag = false; my_assert_32bit_flag = true; } + + FieldRef declare_field( + const std::string & fld_name, + const FieldType & field_type, + const stk::mesh::EntityRank entity_rank, + const unsigned num_states); + + FieldRef register_field( + const std::string & fld_name, + const FieldType & field_type, + const stk::mesh::EntityRank entity_rank, + const unsigned num_states, + const unsigned dimension, + const stk::mesh::Part & part, + const void * value_type_init = nullptr); + + void assign_part_id(stk::mesh::Part& part); + stk::mesh::Part & declare_io_part(const std::string & name, stk::mesh::EntityRank entityRank, const bool restartOnlyIOPart=false); + stk::mesh::Part & declare_io_part_with_topology(const std::string & name, const stk::topology topology, const bool restartOnlyIOPart=false); + + const std::vector & get_restart_only_io_parts() const { return myRestartOnlyIOParts; } + + bool has_part( const std::string& name ) const; + stk::mesh::Part& get_part( const std::string& name ) const; + + void define_part_alias( stk::mesh::Part & part, const std::string & alias ); + + bool has_field( const stk::mesh::EntityRank obj_type, const std::string& name ) const; + bool has_field( const stk::mesh::EntityRank obj_type, const std::string& name, stk::mesh::FieldState state ) const; + + FieldRef get_field( const stk::mesh::EntityRank obj_type, const std::string& name ) const; + FieldRef get_field( const stk::mesh::EntityRank obj_type, const std::string& name, stk::mesh::FieldState state ) const; + + void induce_topology_nodesets(stk::mesh::Selector selector = stk::mesh::Selector()) const; + stk::topology get_nodal_field_topology( const stk::mesh::FieldBase & field, stk::mesh::Entity entity ) const; + stk::topology get_nodal_field_topology( const stk::mesh::FieldBase & field, const stk::mesh::Bucket & bucket ) const; + stk::mesh::Selector selectField( const stk::mesh::FieldBase & field, const stk::mesh::EntityRank target_rank ) const; + bool is_cell_edge(stk::mesh::Entity node0, stk::mesh::Entity node1) const { return fn_is_cell_edge(node0, node1); } + + void set_fmwk_functions( + const std::function & in_fmwk_get_iopart, + const std::function & in_fmwk_iopart, + const std::function & in_fmwk_define_iopart_alias, + const std::function & in_fmwk_register_field, + stk::mesh::Part * in_exposed_boundary_part, + stk::mesh::Part * in_block_boundary_part); + void set_inducer_functions( + const std::function & in_inducer_induce_topology_nodesets, + const std::function & in_inducer_get_nodal_field_topology, + const std::function & in_inducer_selectField); + void set_is_cell_edge_function(const std::function & in_is_cell_edge) { fn_is_cell_edge = in_is_cell_edge; } + +private: + explicit AuxMetaData(stk::mesh::MetaData & stk_meta); +private: + stk::mesh::MetaData & my_meta; + bool is_fmwk; + bool my_assert_32bit_flag; + bool my_force_64bit_flag; + stk::mesh::Part * my_active_part; + stk::mesh::Part * my_exposed_boundary_part; + stk::mesh::Part * my_block_boundary_part; + stk::mesh::Selector my_active_not_ghost_selector; + stk::mesh::Selector my_active_locally_owned_selector; + stk::mesh::Selector my_active_globally_shared_selector; + std::function fmwk_get_iopart; + std::function fmwk_iopart; + std::function fmwk_define_iopart_alias; + std::function fmwk_register_field; + std::function fn_induce_topology_nodesets; + std::function fn_get_nodal_field_topology; + std::function fn_selectField; + std::function fn_is_cell_edge; + std::vector myRestartOnlyIOParts; +}; + +} // namespace krino + +#endif // Akri_AuxMetaData_h diff --git a/packages/krino/krino/krino_lib/Akri_BoundingBox.cpp b/packages/krino/krino/krino_lib/Akri_BoundingBox.cpp new file mode 100644 index 000000000000..f456350ca065 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_BoundingBox.cpp @@ -0,0 +1,214 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +#include +#include +#include + +#include + +namespace krino{ + +template +void +BoundingBox_T::pad_epsilon() +{ + if (!valid()) return; + + const double eps = std::numeric_limits::epsilon(); + + for (int i = 0; i < DIM; i++ ) + { + min[i] -= std::abs(min[i])*eps; + max[i] += std::abs(max[i])*eps; + } +} + +template +void +BoundingBox_T::gather_bboxes( const BoundingBox_T & local_bbox, + std::vector < BoundingBox_T > & all_bboxes ) +{ /* %TRACE% */ /* %TRACE% */ + + // + // globally communicate the bounding box sizes for all procs + // + + all_bboxes.clear(); + all_bboxes.reserve( stk::EnvData::parallel_size() ); + + // put bbox data in Real vector for communication + + std::vector bbox_data( 2*DIM ); + for (int i = 0, j = 0; i < DIM; i++ ) + { + bbox_data[j++] = local_bbox.min[i]; + bbox_data[j++] = local_bbox.max[i]; + } + + std::vector bboxes_data( 2*DIM * stk::EnvData::parallel_size() ); + stk::parallel_vector_concat(stk::EnvData::parallel_comm(), bbox_data, bboxes_data); + + VecType remote_min, remote_max; + for ( int i = 0; i < stk::EnvData::parallel_size(); i++ ) + { + int index = 2*DIM * i; + for (int j = 0; j < DIM; j++ ) + { + remote_min[j] = bboxes_data[index++]; + remote_max[j] = bboxes_data[index++]; + } + all_bboxes.emplace_back( remote_min, remote_max ); + } +} + +template +void +BoundingBox_T::global_reduce() +{ + const VecType local_min = min; + const VecType local_max = max; + + stk::all_reduce_min(stk::EnvData::parallel_comm(), local_min.data(), min.data(), 3); + stk::all_reduce_max(stk::EnvData::parallel_comm(), local_max.data(), max.data(), 3); +} + +template +REAL +BoundingBox_T::SqrDistLowerBnd( const VecType & pt ) const +{ + // make sure bbox is valid + ThrowAssert( valid() ); + + Real delta, SqrDist = 0.; + + for ( int i = 0; i < DIM; i++ ) + { + if ( pt[i] < min[i] ) + { + delta = min[i] - pt[i]; + SqrDist += delta * delta; + } + else if ( pt[i] > max[i] ) + { + delta = pt[i] - max[i]; + SqrDist += delta * delta; + } + } + return ( SqrDist ); +} + +template +REAL +BoundingBox_T::SqrDistUpperBnd( const VecType & pt ) const +{ /* %TRACE% */ /* %TRACE% */ +// make sure bbox is valid + ThrowAssert( valid() ); + + // We are guaranteed that there is a point on the surface on each face of the + // bounding box. So we know that the upper bound for the distance to a face is + // the distance to the farthest point on that face. So the upper bound for this + // bounding box is the minimum of the upper bounds for each face. In other words, + // the upper bound is the minimum distance to the farthest point on each face. + + VecType close_pt; + VecType far_pt; + + for ( int i = 0; i < DIM; i++ ) + { + if ( pt[i] < min[i] ) + { + close_pt[i] = min[i]; + far_pt[i] = max[i]; + } + else if ( pt[i] > max[i] ) + { + close_pt[i] = max[i]; + far_pt[i] = min[i]; + } + else + { + if (pt[i]-min[i] < max[i]-pt[i]) + { + close_pt[i] = min[i]; + far_pt[i] = max[i]; + } + else + { + close_pt[i] = max[i]; + far_pt[i] = min[i]; + } + } + } + + Real SqrDistMin; + if (3 == DIM) + { + SqrDistMin = (pt-VecType(close_pt[0],far_pt[1],far_pt[2])).length_squared(); + SqrDistMin = std::min(SqrDistMin,(pt-VecType(far_pt[0],close_pt[1],far_pt[2])).length_squared()); + SqrDistMin = std::min(SqrDistMin,(pt-VecType(far_pt[0],far_pt[1],close_pt[2])).length_squared()); + } + else + { + ThrowAssert(2 == DIM); + const Real zero = 0.0; + SqrDistMin = (pt-VecType(close_pt[0],far_pt[1],zero)).length_squared(); + SqrDistMin = std::min(SqrDistMin,(pt-VecType(far_pt[0],close_pt[1],zero)).length_squared()); + } + return ( SqrDistMin ); +} + +template +REAL +BoundingBox_T::SqrDistUpperBnd( const BoundingBox_T & pt_box ) const +{ /* %TRACE% */ /* %TRACE% */ + // This is somewhat conservative. More aggressive methods might be able to reduce this estimate + // while still always being an upper bound. + // Here we just estimate the upper bound of this distance from any point in pt_bbox to the + // surface contained in *this by the following: + // Loop the faces of *this. + // Find the maximum distance from any point in pt_bbox to any point of the face. + // Take the minimum of these maximum face distances. + + if( !pt_box.valid() ) + return std::numeric_limits::max(); + + // make sure bbox is valid + ThrowAssert( valid() ); + + Real SqrDistMin = 0.0; + for ( int j = 0; j < DIM; j++ ) + { + for ( int side = 0; side < 2; ++side) + { + Real SqrDistSideMin = 0.0; + for ( int i = 0; i < DIM; i++ ) + { + const Real delta = (i!=j) ? std::max(pt_box.max[i]-min[i],max[i]-pt_box.min[i]) : + ((side==0) ? std::max(min[i]-pt_box.min[i], pt_box.max[i]-min[i]) : std::max(max[i]-pt_box.min[i], pt_box.max[i]-max[i])); + ThrowAssert(delta >= 0.0); + SqrDistSideMin += delta*delta; + } + if (SqrDistMin==0.0 || SqrDistSideMin; +template class BoundingBox_T; + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_BoundingBox.hpp b/packages/krino/krino/krino_lib/Akri_BoundingBox.hpp new file mode 100644 index 000000000000..e64ce337df88 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_BoundingBox.hpp @@ -0,0 +1,212 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_BoundingBox_h +#define Akri_BoundingBox_h + +#include + +#include + +#include +#include + +namespace krino { + +template +class BoundingBox_T { + +public: + typedef REAL Real; + typedef Vec VecType; + +private: + VecType min; + VecType max; + +public: + static void gather_bboxes( const BoundingBox_T & local_bbox, + std::vector< BoundingBox_T > & all_bboxes ); + + void global_reduce(); + + template + void accommodate( const VECTYPE & pt ); + + template + void accommodate( const BoundingBox_T & bbox ); + + const VecType & get_min() const { return min; } + const VecType & get_max() const { return max; } + + template + bool contains( const VECTYPE & pt ) const; + + // Lower bound on the square of the distance from the point pt to surfaces contained in *this + Real SqrDistLowerBnd( const VecType & pt ) const; + + // Upper bound on the square of the distance from the point pt to surfaces contained in *this + Real SqrDistUpperBnd( const VecType & pt ) const; + + // Upper bound on the square of the distance from any point in pt_bbox to surfaces contained in *this + Real SqrDistUpperBnd( const BoundingBox_T & pt_box ) const; + + void pad( const Real & dist ); + void pad_epsilon(); + + void scale( const Real & fraction ); + + Real SqrSize() const; + + bool valid() const + { + bool is_not_valid = (min[0]>max[0]); + return !is_not_valid; + } + + bool intersects( const BoundingBox_T & bbox ) const; + bool contains( const BoundingBox_T & bbox ) const; + + int max_span_direction() const; + + VecType center() const { return Real(0.5)*(min+max); } + + void clear() { + min = VecType(std::numeric_limits::max(),std::numeric_limits::max(),std::numeric_limits::max()); + max = VecType(-std::numeric_limits::max(),-std::numeric_limits::max(),-std::numeric_limits::max()); + } + + template + BoundingBox_T( const VECTYPE & pt_min, + const VECTYPE & pt_max ) + : min(pt_min[0], pt_min[1], pt_min[2]), + max(pt_max[0], pt_max[1], pt_max[2]) {} + + BoundingBox_T() + : min(std::numeric_limits::max(),std::numeric_limits::max(),std::numeric_limits::max()), + max(-std::numeric_limits::max(),-std::numeric_limits::max(),-std::numeric_limits::max()) {} +}; + +template +template +inline void +BoundingBox_T::accommodate( const VECTYPE & pt ) +{ + for ( int i = 0; i < DIM; ++i ) + { + if ( pt[i] < min[i] ) min[i] = pt[i]; + if ( pt[i] > max[i] ) max[i] = pt[i]; + } +} + +template +template +inline void +BoundingBox_T::accommodate( const BoundingBox_T & bbox ) +{ + for ( int i = 0; i < DIM; ++i ) + { + if ( bbox.get_min()[i] < min[i] ) min[i] = bbox.get_min()[i]; + if ( bbox.get_max()[i] > max[i] ) max[i] = bbox.get_max()[i]; + } +} + +template +template +inline bool +BoundingBox_T::contains( const VECTYPE & pt ) const +{ + for ( int i = 0; i < DIM; ++i ) + { + if ( pt[i] < min[i] || pt[i] > max[i] ) return false; + } + return true; +} + +template +inline bool +BoundingBox_T::intersects( const BoundingBox_T & bbox ) const +{ + for ( int i = 0; i < DIM; ++i ) + { + if ( bbox.max[i] < min[i] || bbox.min[i] > max[i] ) return false; + } + return true; +} + +template +inline bool +BoundingBox_T::contains( const BoundingBox_T & bbox ) const +{ + for ( int i = 0; i < DIM; ++i ) + { + if ( bbox.min[i] < min[i] || bbox.max[i] > max[i] ) return false; + } + return true; +} + +template +inline int +BoundingBox_T::max_span_direction() const +{ + int max_dir = 0; + for ( int i = 1; i < DIM; ++i ) + { + if ( max[i] - min[i] > max[max_dir] - min[max_dir] ) max_dir = i; + } + return max_dir; +} + +template +inline REAL +BoundingBox_T::SqrSize() const +{ + Real sqrSize = 0.0; + if (valid()) + { + for ( int i = 0; i < DIM; ++i ) + { + sqrSize += (max[i]-min[i])*(max[i]-min[i]); + } + } + return sqrSize; +} + +template +inline void +BoundingBox_T::pad( const Real & dist ) +{ + if (!valid()) return; + + VecType extension; + for (int i = 0; i < DIM; i++ ) + extension[i] = dist; + + min -= extension; + max += extension; +} + +template +inline void +BoundingBox_T::scale( const Real & scale_factor ) +{ + if (!valid()) return; + + Real fraction = 0.5 * ( scale_factor - 1. ); + VecType extension; + for (int i = 0; i < DIM; i++ ) + extension[i] = fraction * ( max[i] - min[i] ); + min -= extension; + max += extension; +} + +typedef BoundingBox_T BoundingBox; + +} // namespace krino + +#endif // Akri_BoundingBox_h diff --git a/packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.cpp b/packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.cpp new file mode 100644 index 000000000000..32bd6e2b02b6 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.cpp @@ -0,0 +1,697 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace krino{ + +BoundingBoxMesh::BoundingBoxMesh(stk::topology element_topology, const std::vector & entity_rank_names) +: m_element_topology(element_topology), m_nx(0), m_ny(0), m_nz(0) +{ + ThrowRequire(element_topology == stk::topology::TRIANGLE_3_2D || + element_topology == stk::topology::QUADRILATERAL_4_2D || + element_topology == stk::topology::TETRAHEDRON_4 || + element_topology == stk::topology::HEXAHEDRON_8); + + m_meta = std::make_unique(element_topology.dimension(), entity_rank_names); + AuxMetaData & aux_meta = AuxMetaData::create(*m_meta); + stk::mesh::Part & block_part = m_meta->declare_part_with_topology( "block_1", element_topology ); + stk::io::put_io_part_attribute(block_part); + m_elem_parts.push_back(&block_part); + m_elem_parts.push_back(&aux_meta.active_part()); + m_node_parts.push_back(&aux_meta.active_part()); + + declare_domain_side_parts(block_part); +} + +void +BoundingBoxMesh::set_domain(const BoundingBoxType & mesh_bbox, const double mesh_size, const int pad_size) +{ + m_mesh_bbox = mesh_bbox; + const Vector3d padding(0.5*mesh_size*pad_size, 0.5*mesh_size*pad_size, 0.5*mesh_size*pad_size); + m_mesh_bbox = BoundingBoxType(mesh_bbox.get_min() - padding, mesh_bbox.get_max() + padding); + + const typename BoundingBoxType::VecType min = m_mesh_bbox.get_min(); + const typename BoundingBoxType::VecType max = m_mesh_bbox.get_max(); + const typename BoundingBoxType::VecType span = max-min; + m_nx = (size_t) (0.5 + span[0] / mesh_size); + const bool isTriangularLattice = (TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == myMeshStructureType || FLAT_WALLED_TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == myMeshStructureType); + const double dy = isTriangularLattice ? (0.5*mesh_size*std::sqrt(3.0)) : mesh_size; + m_ny = (size_t) (0.5 + span[1] / dy); + m_nz = (m_element_topology.dimension() == 2) ? 1 : ((size_t) (0.5 + span[2] / mesh_size)); + + if (m_element_topology.dimension() == 2) + { + krinolog << "Generated mesh with for domain " + << "(min,max) = ((" << min[0] << "," << min[1] << "),("<< max[0] << "," << max[1] << ")" + << ", (nx,ny) = (" << m_nx << "," << m_ny << ")" << stk::diag::dendl; + } + else + { + krinolog << "Generated mesh with for domain " + << "(min,max) = ((" << min[0] << "," << min[1] << "," << min[2] << "),("<< max[0] << "," << max[1] << "," << max[2] << ")" + << ", (nx,ny,nz) = (" << m_nx << "," << m_ny << "," << m_nz << ")" << stk::diag::dendl; + } + +} + +void BoundingBoxMesh::set_is_cell_edge_function_for_cell_based_mesh() const +{ + const std::array N = {m_nx, m_ny, m_nz}; + const int dim = m_meta->spatial_dimension(); + ThrowRequireMsg(m_mesh, "Cannot call set_is_cell_edge_function() before BulkData is created."); + const stk::mesh::BulkData & mesh = *m_mesh; + + auto is_cell_edge = [N,dim,&mesh](stk::mesh::Entity node0, stk::mesh::Entity node1) + { + const auto node0Indices = get_node_x_y_z(mesh.identifier(node0), N); + const auto node1Indices = get_node_x_y_z(mesh.identifier(node1), N); + if (dim == 2) + { + return node0Indices[0] == node1Indices[0] || node0Indices[1] == node1Indices[1]; + } + const bool matchX = node0Indices[0] == node1Indices[0]; + const bool matchY = node0Indices[1] == node1Indices[1]; + const bool matchZ = node0Indices[2] == node1Indices[2]; + return (matchX && matchY) || (matchX && matchZ) || (matchY && matchZ); + }; + + AuxMetaData & aux_meta = AuxMetaData::get(*m_meta); + aux_meta.set_is_cell_edge_function(is_cell_edge); +} + +void BoundingBoxMesh::set_is_cell_edge_function_for_BCC_mesh() const +{ + auto is_cell_edge = [](stk::mesh::Entity node0, stk::mesh::Entity node1) + { + return true; + }; + + AuxMetaData & aux_meta = AuxMetaData::get(*m_meta); + aux_meta.set_is_cell_edge_function(is_cell_edge); +} + +void +BoundingBoxMesh::populate_mesh(stk::ParallelMachine pm, const stk::mesh::BulkData::AutomaticAuraOption auto_aura_option) +{ /* %TRACE[ON]% */ Trace trace__("krino::BoundingBoxMesh::populate_mesh()"); /* %TRACE% */ + ThrowRequireMsg(m_mesh_bbox.valid(), "Must call set_domain() before populate_mesh()"); + m_mesh = std::make_unique(*m_meta, pm, auto_aura_option); + if (CUBIC_BOUNDING_BOX_MESH == myMeshStructureType) + populate_cell_based_mesh(); + else if (TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == myMeshStructureType || FLAT_WALLED_TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == myMeshStructureType) + populate_2D_triangular_lattice_based_mesh(); + else if (BCC_BOUNDING_BOX_MESH == myMeshStructureType || FLAT_WALLED_BCC_BOUNDING_BOX_MESH == myMeshStructureType) + populate_BCC_mesh(); + else + ThrowRequireMsg(false, "Unsupported or unrecognized mesh structure type " << myMeshStructureType); +} + +enum BCCNode { BCC_NODE=8, BCC_NODE_XMINUS=9, BCC_NODE_XPLUS=10, BCC_NODE_YMINUS=11, BCC_NODE_YPLUS=12, BCC_NODE_ZMINUS=13, BCC_NODE_ZPLUS=14 }; + +void +BoundingBoxMesh::build_face_tets( size_t cell_id, size_t ix , size_t iy , size_t iz, int iface, const std::vector & cell_node_ids ) +{ + std::vector>> faceTets = { + {{BCC_NODE, BCC_NODE_XMINUS, 0, 4}, + {BCC_NODE, BCC_NODE_XMINUS, 4, 7}, + {BCC_NODE, BCC_NODE_XMINUS, 7, 3}, + {BCC_NODE, BCC_NODE_XMINUS, 3, 0}}, + {{BCC_NODE, BCC_NODE_XPLUS, 1, 2}, + {BCC_NODE, BCC_NODE_XPLUS, 2, 6}, + {BCC_NODE, BCC_NODE_XPLUS, 6, 5}, + {BCC_NODE, BCC_NODE_XPLUS, 5, 1}}, + {{BCC_NODE, BCC_NODE_YMINUS, 0, 1}, + {BCC_NODE, BCC_NODE_YMINUS, 1, 5}, + {BCC_NODE, BCC_NODE_YMINUS, 5, 4}, + {BCC_NODE, BCC_NODE_YMINUS, 4, 0}}, + {{BCC_NODE, BCC_NODE_YPLUS, 2, 3}, + {BCC_NODE, BCC_NODE_YPLUS, 3, 7}, + {BCC_NODE, BCC_NODE_YPLUS, 7, 6}, + {BCC_NODE, BCC_NODE_YPLUS, 6, 2}}, + {{BCC_NODE, BCC_NODE_ZMINUS, 0, 3}, + {BCC_NODE, BCC_NODE_ZMINUS, 3, 2}, + {BCC_NODE, BCC_NODE_ZMINUS, 2, 1}, + {BCC_NODE, BCC_NODE_ZMINUS, 1, 0}}, + {{BCC_NODE, BCC_NODE_ZPLUS, 4, 5}, + {BCC_NODE, BCC_NODE_ZPLUS, 5, 6}, + {BCC_NODE, BCC_NODE_ZPLUS, 6, 7}, + {BCC_NODE, BCC_NODE_ZPLUS, 7, 4}} + }; + + const stk::mesh::EntityId elem_id_start = 1; + const int num_elem_per_face = 4; + const int num_faces_per_cell = 6; + const int num_nodes_per_elem = 4; + std::vector elemNodes(num_nodes_per_elem); + + const auto & faceTet = faceTets[iface]; + for (int ielem = 0; ielem < num_elem_per_face; ++ielem) + { + stk::mesh::EntityId elem_id = num_elem_per_face*num_faces_per_cell*cell_id + num_elem_per_face*iface + ielem + elem_id_start; // Uses twice as many IDs as necessary + for (int elem_node_index = 0; elem_node_indexcoordinate_field(); + stk::mesh::EntityId nodeId = get_node_id(ix, iy, iz); + stk::mesh::Entity const node = m_mesh->get_entity( stk::topology::NODE_RANK, nodeId ); + if (m_mesh->is_valid(node)) + { + m_mesh->change_entity_parts(node, m_node_parts); + + double * coord_data = static_cast(stk::mesh::field_data( *coord_field, node )); + my_coord_mapping->get_node_coordinates(coord_data, m_meta->spatial_dimension(), {ix, iy, iz}); + } +} + +void +BoundingBoxMesh::setup_BCC_node( size_t ix , size_t iy , size_t iz, int dx, int dy, int dz ) +{ + stk::mesh::FieldBase const* coord_field = m_meta->coordinate_field(); + stk::mesh::EntityId nodeId = get_BCC_node_id(ix, iy, iz, dx, dy, dz); + stk::mesh::Entity const node = m_mesh->get_entity( stk::topology::NODE_RANK, nodeId ); + if (m_mesh->is_valid(node)) + { + m_mesh->change_entity_parts(node, m_node_parts); + + const bool flattenBoundaries = FLAT_WALLED_BCC_BOUNDING_BOX_MESH == myMeshStructureType; + double * coord_data = static_cast(stk::mesh::field_data( *coord_field, node )); + my_coord_mapping->get_BCC_node_coordinates(coord_data, m_meta->spatial_dimension(), flattenBoundaries, {ix, iy, iz}, {dx, dy, dz}); + } +} + +size_t get_triangle_lattice_node_id(size_t ix, size_t iy, size_t nx, size_t ny) +{ + return 1 + iy*(nx+1) + ix + ((iy%2 == 0) ? (iy/2) : ((iy-1)/2)); +} + +void fill_triangle_lattice_node_indices(const size_t ix, const size_t iy, std::array,3> & triNodeIndices) +{ + if (iy%2 == 0) + { + if (ix%2 == 0) + triNodeIndices = {{ {{ix/2,iy}}, {{ix/2+1,iy+1}}, {{ix/2,iy+1}} }}; + else + triNodeIndices = {{ {{(ix-1)/2,iy}}, {{(ix-1)/2+1,iy}}, {{(ix-1)/2+1,iy+1}} }}; + } + else + { + if (ix%2 == 0) + triNodeIndices = {{ {{ix/2,iy}}, {{ix/2+1,iy}}, {{ix/2,iy+1}} }}; + else + triNodeIndices = {{ {{(ix-1)/2+1,iy}}, {{(ix-1)/2+1,iy+1}}, {{(ix-1)/2,iy+1}} }}; + } +} + +void +BoundingBoxMesh::populate_2D_triangular_lattice_based_mesh() +{ /* %TRACE[ON]% */ Trace trace__("krino::BoundingBoxMesh::populate_2D_triangular_lattice_based_mesh()"); /* %TRACE% */ + ThrowRequire(m_mesh); + + const int p_size = m_mesh->parallel_size(); + const int p_rank = m_mesh->parallel_rank(); + stk::mesh::FieldBase const* coord_field = m_meta->coordinate_field(); + my_coord_mapping = std::make_unique(m_nx, m_ny, m_nz, m_mesh_bbox); + + ThrowRequire(m_element_topology == stk::topology::TRIANGLE_3_2D); + const bool flattenBoundaries = FLAT_WALLED_TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == myMeshStructureType; + + const size_t NelemPerSlabInY = (2*m_nx+1); + const size_t Nelem = m_ny*NelemPerSlabInY; + const size_t beg_elem = ( Nelem * p_rank ) / p_size ; + const size_t end_elem = ( Nelem * ( p_rank + 1 ) ) / p_size ; + + const size_t num_local_cells = end_elem-beg_elem; + krinolog << "BoundingBoxMesh creating " << num_local_cells << " local cells." << stk::diag::dendl; + + std::vector triNodes(3); + std::array,3> triNodeIndices; + + m_mesh->modification_begin(); + + { + for (size_t elemIndex=beg_elem; elemIndex!=end_elem; ++elemIndex) + { + const size_t iy = elemIndex / NelemPerSlabInY; + const size_t ix = elemIndex - iy*NelemPerSlabInY; + + fill_triangle_lattice_node_indices(ix, iy, triNodeIndices); + + triNodes = {get_triangle_lattice_node_id(triNodeIndices[0][0], triNodeIndices[0][1], m_nx, m_ny), + get_triangle_lattice_node_id(triNodeIndices[1][0], triNodeIndices[1][1], m_nx, m_ny), + get_triangle_lattice_node_id(triNodeIndices[2][0], triNodeIndices[2][1], m_nx, m_ny)}; + + stk::mesh::declare_element( *m_mesh, m_elem_parts, elemIndex+1, triNodes ); + + for (int n=0; n<3; ++n) + { + stk::mesh::Entity const node = m_mesh->get_entity( stk::topology::NODE_RANK , triNodes[n] ); + m_mesh->change_entity_parts(node, m_node_parts); + + double * coord_data = static_cast(stk::mesh::field_data( *coord_field, node )); + + my_coord_mapping->get_triangle_lattice_node_coordinates(coord_data, flattenBoundaries, {{triNodeIndices[n][0], triNodeIndices[n][1]}}); + } + } + stk::tools::fix_node_sharing_via_search(*m_mesh); + } + m_mesh->modification_end(); + + std::vector counts; + stk::mesh::count_entities( m_meta->locally_owned_part(), *m_mesh, counts ); + krinolog << "Generated mesh with " << counts[stk::topology::ELEM_RANK] << " local elements and " << counts[stk::topology::NODE_RANK] << " local nodes." << stk::diag::dendl; +} + +void +BoundingBoxMesh::populate_BCC_mesh() +{ /* %TRACE[ON]% */ Trace trace__("krino::BoundingBoxMesh::populate_BCC_mesh()"); /* %TRACE% */ + ThrowRequire(m_mesh); + set_is_cell_edge_function_for_BCC_mesh(); + + const int p_size = m_mesh->parallel_size(); + const int p_rank = m_mesh->parallel_rank(); + + std::vector> hex_cell_node_locations = { {0,0,0}, {1,0,0}, {1,1,0}, {0,1,0}, {0,0,1}, {1,0,1}, {1,1,1}, {0,1,1} }; + std::vector>> BCC_cell_neighbors = { {BCC_NODE,{0,0,0}}, + {BCC_NODE_XMINUS,{-1,0,0}}, {BCC_NODE_XPLUS,{+1,0,0}}, + {BCC_NODE_YMINUS,{0,-1,0}}, {BCC_NODE_YPLUS,{0,+1,0}}, + {BCC_NODE_ZMINUS,{0,0,-1}}, {BCC_NODE_ZPLUS,{0,0,+1}}}; + + my_coord_mapping = std::make_unique(m_nx, m_ny, m_nz, m_mesh_bbox); + + std::pair proc_cell_range = determine_processor_cells(p_size, p_rank); + const size_t num_local_cells = proc_cell_range.second-proc_cell_range.first; + krinolog << "BoundingBoxMesh creating " << num_local_cells << " local cells." << stk::diag::dendl; + + const int num_nodes_per_cell = 8; + const int num_nodes_per_cell_plus_BCC_nodes = 15; + std::vector cell_node_ids(num_nodes_per_cell_plus_BCC_nodes); + + m_mesh->modification_begin(); + + { + size_t count = 0; + for (size_t cell_id=proc_cell_range.first; cell_id!=proc_cell_range.second; ++cell_id) + { + size_t ix = 0, iy = 0, iz = 0; + get_cell_x_y_z(cell_id, ix, iy, iz); + + if ( count != 0 && count % 1000000 == 0) + { + krinolog << "Creating local cell " << count << " ." << stk::diag::dendl; + } + ++count; + + for (int cell_node_index=0; cell_node_indexmodification_end(); + + std::vector counts; + stk::mesh::count_entities( m_meta->locally_owned_part(), *m_mesh, counts ); + krinolog << "Generated mesh with " << counts[stk::topology::ELEM_RANK] << " local elements and " << counts[stk::topology::NODE_RANK] << " local nodes." << stk::diag::dendl; +} + +void +BoundingBoxMesh::populate_cell_based_mesh() +{ /* %TRACE[ON]% */ Trace trace__("krino::BoundingBoxMesh::populate_cell_based_mesh()"); /* %TRACE% */ + ThrowRequire(m_mesh); + set_is_cell_edge_function_for_cell_based_mesh(); + + const int p_size = m_mesh->parallel_size(); + const int p_rank = m_mesh->parallel_rank(); + const int dim = m_element_topology.dimension(); + stk::mesh::FieldBase const* coord_field = m_meta->coordinate_field(); + + std::vector> hex_cell_node_locations = { {0,0,0}, {1,0,0}, {1,1,0}, {0,1,0}, {0,0,1}, {1,0,1}, {1,1,1}, {0,1,1} }; + std::vector> hex_cell_elem_nodes = {{0, 1, 2, 3, 4, 6, 7}}; + + std::vector> tet_even_cell_elem_nodes = {{0, 1, 2, 5}, + {0, 2, 7, 5}, + {0, 2, 3, 7}, + {0, 5, 7, 4}, + {2, 7, 5, 6}}; + std::vector> tet_odd_cell_elem_nodes = {{0, 1, 3, 4}, + {1, 2, 3, 6}, + {1, 3, 4, 6}, + {3, 4, 6, 7}, + {1, 6, 4, 5}}; + + std::vector> quad_cell_node_locations = { {0,0,0}, {1,0,0}, {1,1,0}, {0,1,0} }; + std::vector> quad_cell_elem_nodes = {{0, 1, 2, 3}}; + + std::vector> tri_even_cell_elem_nodes = {{0, 1, 2}, {0, 2, 3}}; + std::vector> tri_odd_cell_elem_nodes = {{0, 1, 3}, {1, 2, 3}}; + + const std::vector> & cell_node_locations = (dim == 2) ? quad_cell_node_locations : hex_cell_node_locations; + const std::vector> & even_cell_elem_nodes = + (m_element_topology == stk::topology::TRIANGLE_3_2D) ? tri_even_cell_elem_nodes : + ((m_element_topology == stk::topology::QUADRILATERAL_4) ? quad_cell_elem_nodes : + ((m_element_topology == stk::topology::TETRAHEDRON_4) ? tet_even_cell_elem_nodes : + hex_cell_elem_nodes)); + const std::vector> & odd_cell_elem_nodes = + (m_element_topology == stk::topology::TRIANGLE_3_2D) ? tri_odd_cell_elem_nodes : + ((m_element_topology == stk::topology::QUADRILATERAL_4) ? quad_cell_elem_nodes : + ((m_element_topology == stk::topology::TETRAHEDRON_4) ? tet_odd_cell_elem_nodes : + hex_cell_elem_nodes)); + + std::unordered_map> nodes_to_procs; + generate_node_to_processor_map(p_size, p_rank, cell_node_locations, nodes_to_procs); + const int num_elem_per_cell = even_cell_elem_nodes.size(); + const int num_nodes_per_cell = cell_node_locations.size(); + const int num_nodes_per_elem = even_cell_elem_nodes[0].size(); + const stk::mesh::EntityId elem_id_start = 1; + std::vector cell_node_ids(num_nodes_per_cell); + std::vector elem_nodes(num_nodes_per_elem); + + my_coord_mapping = std::make_unique(m_nx, m_ny, m_nz, m_mesh_bbox); + + std::pair proc_cell_range = determine_processor_cells(p_size, p_rank); + const size_t num_local_cells = proc_cell_range.second-proc_cell_range.first; + krinolog << "BoundingBoxMesh creating " << num_local_cells << " local cells." << stk::diag::dendl; + + m_mesh->modification_begin(); + + { + size_t count = 0; + for (size_t cell_id=proc_cell_range.first; cell_id!=proc_cell_range.second; ++cell_id) + { + size_t ix = 0, iy = 0, iz = 0; + get_cell_x_y_z(cell_id, ix, iy, iz); + + const std::vector> & cell_elem_nodes = ((ix+iy+iz)%2 == 0) ? even_cell_elem_nodes : odd_cell_elem_nodes; + + if ( count % 1000000 == 0) + { + krinolog << "Creating local cell " << count << " ." << stk::diag::dendl; + } + ++count; + + for (int cell_node_index=0; cell_node_indexget_entity( stk::topology::NODE_RANK , node_id ); + m_mesh->change_entity_parts(node, m_node_parts); + + auto map_it = nodes_to_procs.find(node_id); + if (map_it != nodes_to_procs.end()) + { + for (auto other_proc : map_it->second) + { + m_mesh->add_node_sharing(node, other_proc); + } + } + + // Compute and assign coordinates to the node + get_node_x_y_z(node_id, ix, iy, iz); + + double * coord_data = static_cast(stk::mesh::field_data( *coord_field, node )); + my_coord_mapping->get_node_coordinates(coord_data, dim, {ix, iy, iz}); + } + } + } + } + m_mesh->modification_end(); + + std::vector counts; + stk::mesh::count_entities( m_meta->locally_owned_part(), *m_mesh, counts ); + krinolog << "Generated mesh with " << counts[stk::topology::ELEM_RANK] << " local elements and " << counts[stk::topology::NODE_RANK] << " local nodes." << stk::diag::dendl; +} + +void +BoundingBoxMesh::declare_domain_side_parts(const stk::mesh::Part & blockPart) +{ + AuxMetaData & aux_meta = AuxMetaData::get(*m_meta); + + stk::topology side_topology = m_element_topology.side_topology(); + + mySideParts.clear(); + mySideParts.push_back(&aux_meta.declare_io_part_with_topology("Xminus", side_topology)); + mySideParts.push_back(&aux_meta.declare_io_part_with_topology("Xplus", side_topology)); + mySideParts.push_back(&aux_meta.declare_io_part_with_topology("Yminus", side_topology)); + mySideParts.push_back(&aux_meta.declare_io_part_with_topology("Yplus", side_topology)); + if (m_meta->spatial_dimension() == 3) + { + mySideParts.push_back(&aux_meta.declare_io_part_with_topology("Zminus", side_topology)); + mySideParts.push_back(&aux_meta.declare_io_part_with_topology("Zplus", side_topology)); + } + + for (auto && sidePart : mySideParts) + m_meta->set_surface_to_block_mapping(sidePart, {&blockPart}); +} + +void BoundingBoxMesh::require_has_flat_boundaries() const +{ + ThrowRequireMsg(has_flat_boundaries(), "Domain sides can only be added for CUBIC or FLAT_WALLED_BC generated meshes."); +} + +static bool equal_within_tol(const double x1, const double x2, const double tol) +{ + return std::abs(x1-x2) < tol; +} + +void +BoundingBoxMesh::create_domain_sides() +{ + if (mySideParts.empty()) + return; + + require_has_flat_boundaries(); + + AuxMetaData & aux_meta = AuxMetaData::get(*m_meta); + stk::mesh::create_exposed_block_boundary_sides(*m_mesh, m_meta->universal_part(), {&aux_meta.exposed_boundary_part()}); + + stk::topology side_topology = m_element_topology.side_topology(); + ThrowRequire(mySideParts.size() >= m_meta->spatial_dimension()*2); + + std::vector sides; + stk::mesh::get_selected_entities( aux_meta.exposed_boundary_part() & m_meta->locally_owned_part(), m_mesh->buckets( m_meta->side_rank() ), sides ); + std::vector add_parts(sides.size()); + std::vector remove_parts(sides.size()); + stk::mesh::FieldBase const* coord_field = m_meta->coordinate_field(); + + const auto & min = m_mesh_bbox.get_min(); + const auto & max = m_mesh_bbox.get_max(); + const double relativeTol = 0.01; // should not be at all sensitive to this tolerance + const std::array tol {(max[0]-min[0])/m_nx*relativeTol, (max[1]-min[1])/m_ny*relativeTol, ((m_meta->spatial_dimension() == 3) ? ((max[2]-min[2])/m_nz*relativeTol) : 0.)}; + + const unsigned num_side_nodes = side_topology.num_nodes(); + for (size_t iside=0; isidebegin_nodes(sides[iside]); + std::array domainSide {-2,-2,-2}; + for (unsigned n=0; n(stk::mesh::field_data( *coord_field, side_nodes[n] )); + for (unsigned i=0; ispatial_dimension(); ++i ) + { + if (equal_within_tol(coords[i],min[i],tol[i])) + { + ThrowRequire(domainSide[i] != 1); + if (domainSide[i] == -2) domainSide[i] = -1; + } + else if (equal_within_tol(coords[i],max[i],tol[i])) + { + ThrowRequire(domainSide[i] != -1); + if (domainSide[i] == -2) domainSide[i] = 1; + } + else + { + domainSide[i] = 0; + } + } + } + + const int num_sides_set = ((domainSide[0]==-1||domainSide[0]==1) ? 1 : 0) + ((domainSide[1]==-1||domainSide[1]==1) ? 1 : 0) + ((domainSide[2]==-1||domainSide[2]==1) ? 1 : 0); + ThrowRequire(num_sides_set == 1); + stk::mesh::Part * side_part = nullptr; + if (domainSide[0] == -1) side_part = mySideParts[0]; + else if (domainSide[0] == 1) side_part = mySideParts[1]; + else if (domainSide[1] == -1) side_part = mySideParts[2]; + else if (domainSide[1] == 1) side_part = mySideParts[3]; + else if (domainSide[2] == -1) side_part = mySideParts[4]; + else if (domainSide[2] == 1) side_part = mySideParts[5]; + + add_parts[iside].push_back(side_part); + } + + m_mesh->batch_change_entity_parts(sides, add_parts, remove_parts); +} + +void +BoundingBoxMesh::get_cell_x_y_z( stk::mesh::EntityId cell_id, size_t &ix , size_t &iy , size_t &iz ) const +{ + ix = cell_id % m_nx; + cell_id /= m_nx; + + iy = cell_id % m_ny; + cell_id /= m_ny; + + iz = cell_id; +} + +std::array +BoundingBoxMesh::get_node_x_y_z( stk::mesh::EntityId entity_id, const std::array & N ) +{ + const size_t node_id_start = 1; + entity_id -= node_id_start; + + std::array indices; + indices[0] = entity_id % (N[0]+1); + entity_id /= (N[0]+1); + + indices[1] = entity_id % (N[1]+1); + entity_id /= (N[2]+1); + + indices[2] = entity_id; + return indices; +} + +void +BoundingBoxMesh::get_node_x_y_z( stk::mesh::EntityId entity_id, size_t &ix , size_t &iy , size_t &iz ) const +{ + auto indices = get_node_x_y_z(entity_id, {{m_nx,m_ny,m_nz}}); + ix = indices[0]; + iy = indices[1]; + iz = indices[2]; +} + +std::pair +BoundingBoxMesh::determine_processor_cells(const int p_size, const int p_rank) const +{ + const size_t Ntot = m_nx*m_ny*m_nz; + const size_t beg_elem = ( Ntot * p_rank ) / p_size ; + const size_t end_elem = ( Ntot * ( p_rank + 1 ) ) / p_size ; + return std::make_pair(beg_elem, end_elem); +} + +void +BoundingBoxMesh::generate_node_to_processor_map(const int p_size, + const int p_rank, + const std::vector> & cell_node_locations, + std::unordered_map> & nodes_to_procs) const +{ + std::unordered_set locally_used_nodes; + + std::pair proc_cell_range = determine_processor_cells(p_size, p_rank); + + // First create set of all nodes used by local elements + for (size_t cell_id=proc_cell_range.first; cell_id!=proc_cell_range.second; ++cell_id) + { + size_t ix = 0, iy = 0, iz = 0; + get_cell_x_y_z(cell_id, ix, iy, iz); + + for (size_t cell_node_index=0; cell_node_index other_proc_cell_range = determine_processor_cells(p_size, other_p_rank); + + for (size_t cell_id=other_proc_cell_range.first; cell_id!=other_proc_cell_range.second; ++cell_id) + { + size_t ix = 0, iy = 0, iz = 0; + get_cell_x_y_z(cell_id, ix, iy, iz); + + for (size_t cell_node_index=0; cell_node_index & node_procs = nodes_to_procs[nodeId]; + if (std::find(node_procs.begin(), node_procs.end(), other_p_rank) == node_procs.end()) + { + node_procs.push_back(other_p_rank); + } + } + } + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.hpp b/packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.hpp new file mode 100644 index 000000000000..45572ed5ea79 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_BoundingBoxMesh.hpp @@ -0,0 +1,157 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_BoundingBoxMesh_h +#define Akri_BoundingBoxMesh_h + +#include +#include +#include +#include +#include + +namespace krino { + +class CartesianCoordinateMapping +{ +public: + CartesianCoordinateMapping(const size_t nx, const size_t ny, const size_t nz, + const BoundingBox_T & bbox) + : m_N{nx, ny, nz}, + m_bbox(bbox) + {} + void get_node_coordinates(double * node_coords, const int spatial_dim, const std::array & ijk) const + { + const Vector3d & min = m_bbox.get_min(); + const Vector3d & max = m_bbox.get_max(); + for (int i=0; i < spatial_dim; ++i) + node_coords[i] = min[i] + (max[i]-min[i])*ijk[i]/m_N[i]; + } + void get_triangle_lattice_node_coordinates(double * node_coords, const bool flattenBoundaries, const std::array & ij) const + { + const Vector3d & min = m_bbox.get_min(); + const Vector3d & max = m_bbox.get_max(); + const double offset = ij[1]%2==0 ? 0.0 : -0.5; + node_coords[0] = min[0] + (max[0]-min[0])/m_N[0] * (ij[0]+offset); + if (flattenBoundaries) + node_coords[0] = std::min(max[0], std::max(min[0], node_coords[0])); + node_coords[1] = min[1] + (max[1]-min[1])/m_N[1] * ij[1]; + } + void get_BCC_node_coordinates(double * node_coords, const int spatial_dim, const bool flattenBoundaries, const std::array & ijk, const std::array & dijk) const + { + const Vector3d & min = m_bbox.get_min(); + const Vector3d & max = m_bbox.get_max(); + for (int i=0; i < spatial_dim; ++i) + { + node_coords[i] = min[i] + (max[i]-min[i])*(0.5+ijk[i]+dijk[i])/m_N[i]; + if (flattenBoundaries) + node_coords[i] = std::min(max[i], std::max(min[i], node_coords[i])); + } + } +private: + const std::array m_N; + const BoundingBox_T m_bbox; +}; + +enum BoundingBoxMeshStructureType +{ + CUBIC_BOUNDING_BOX_MESH = 0, + BCC_BOUNDING_BOX_MESH = 1, + FLAT_WALLED_BCC_BOUNDING_BOX_MESH = 2, + TRIANGULAR_LATTICE_BOUNDING_BOX_MESH = 3, + FLAT_WALLED_TRIANGULAR_LATTICE_BOUNDING_BOX_MESH = 4 +}; + +class BoundingBoxMesh { +public: + typedef BoundingBox_T BoundingBoxType; + static std::array get_node_x_y_z( stk::mesh::EntityId entity_id, const std::array & N ); + +public: + BoundingBoxMesh(stk::topology element_topology, const std::vector& rank_names = std::vector()); + void set_domain(const BoundingBoxType & mesh_bbox, const double mesh_size, const int pad_cells = 0); + void populate_mesh(stk::ParallelMachine pm = MPI_COMM_WORLD, const stk::mesh::BulkData::AutomaticAuraOption auto_aura_option = stk::mesh::BulkData::AUTO_AURA); + stk::mesh::MetaData & meta_data() { ThrowAssert( nullptr != m_meta.get() ) ; return *m_meta; } + stk::mesh::BulkData & bulk_data() { ThrowAssert( nullptr != m_mesh.get() ) ; return *m_mesh; } + const stk::mesh::MetaData & meta_data() const { ThrowAssert( nullptr != m_meta.get() ) ; return *m_meta; } + const stk::mesh::BulkData & bulk_data() const { ThrowAssert( nullptr != m_mesh.get() ) ; return *m_mesh; } + + void create_domain_sides(); + const CartesianCoordinateMapping & get_coord_mapping() const { return *my_coord_mapping; } + void get_node_x_y_z( stk::mesh::EntityId entity_id, size_t &ix , size_t &iy , size_t &iz ) const; + void set_mesh_structure_type(BoundingBoxMeshStructureType type) { myMeshStructureType = type; } + bool has_flat_boundaries() const { return CUBIC_BOUNDING_BOX_MESH == myMeshStructureType || FLAT_WALLED_BCC_BOUNDING_BOX_MESH == myMeshStructureType || FLAT_WALLED_TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == myMeshStructureType; } +private: + void declare_domain_side_parts(const stk::mesh::Part & blockPart); + void require_has_flat_boundaries() const; + void populate_2D_triangular_lattice_based_mesh(); + void populate_BCC_mesh(); + void populate_cell_based_mesh(); + void setup_cell_node( size_t ix , size_t iy , size_t iz ); + void setup_BCC_node( size_t ix , size_t iy , size_t iz, int dx, int dy, int dz ); + void build_face_tets( size_t cell_id, size_t ix , size_t iy , size_t iz, int iface, const std::vector & cell_node_ids ); + stk::mesh::EntityId get_node_id(const size_t ix, const size_t iy, const size_t iz ) const + { + const size_t node_id_start = 1; + return node_id_start + ix + ( m_nx + 1 ) * ( iy + ( m_ny + 1 ) * iz ); + } + stk::mesh::EntityId get_BCC_node_id(const size_t ix, const size_t iy, const size_t iz, const int dx, const int dy, const int dz ) const + { + const size_t node_id_start = 1; + const size_t num_nodes = ( m_nx + 1 ) * ( m_ny + 1 ) * ( m_nz + 1 ); + return node_id_start + num_nodes + (ix+1+dx) + ( m_nx + 2 ) * ( (iy+1+dy) + ( m_ny + 2 ) * (iz+1+dz) ); + } + void get_cell_x_y_z( stk::mesh::EntityId cell_id, size_t &ix , size_t &iy , size_t &iz ) const; + std::pair determine_processor_cells(const int p_size, const int p_rank) const; + void generate_node_to_processor_map(const int p_size, + const int p_rank, + const std::vector> & cell_node_locations, + std::unordered_map> & nodes_to_procs) const; + void set_is_cell_edge_function_for_BCC_mesh() const; + void set_is_cell_edge_function_for_cell_based_mesh() const; +private: + std::unique_ptr m_meta; + std::unique_ptr m_mesh; + std::unique_ptr my_coord_mapping; + stk::mesh::PartVector m_elem_parts; + stk::mesh::PartVector m_node_parts; + const stk::topology m_element_topology; + BoundingBoxMeshStructureType myMeshStructureType{CUBIC_BOUNDING_BOX_MESH}; + size_t m_nx, m_ny, m_nz; + BoundingBoxType m_mesh_bbox; + std::vector mySideParts; +}; + +class BoundingBoxMeshTri3 : public BoundingBoxMesh +{ +public: + BoundingBoxMeshTri3() : BoundingBoxMesh(stk::topology::TRIANGLE_3_2D) {} +}; + +class BoundingBoxMeshQuad4 : public BoundingBoxMesh +{ +public: + BoundingBoxMeshQuad4() : BoundingBoxMesh(stk::topology::QUADRILATERAL_4_2D) {} +}; + +class BoundingBoxMeshTet4 : public BoundingBoxMesh +{ +public: + BoundingBoxMeshTet4() : BoundingBoxMesh(stk::topology::TETRAHEDRON_4) {} +}; + +class BoundingBoxMeshHex8 : public BoundingBoxMesh +{ +public: + BoundingBoxMeshHex8() : BoundingBoxMesh(stk::topology::HEXAHEDRON_8) {} +}; + +} // namespace krino + +#endif // Akri_BoundingBoxMesh_h diff --git a/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.cpp b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.cpp new file mode 100644 index 000000000000..b856b61eed17 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.cpp @@ -0,0 +1,636 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include + +namespace krino{ + +CDFEM_Parent_Edge::CDFEM_Parent_Edge(const std::vector & edgeNodes, + const std::vector & edgeNodePositions) +: my_edge_nodes(edgeNodes), + my_edge_node_positions(edgeNodePositions) +{ +} + +double CDFEM_Parent_Edge::MinSize() +{ + return SegmentLowerEnvelope::MinSize(); +} + +double CDFEM_Parent_Edge::get_edge_node_position(stk::mesh::Entity edgeNode) const +{ + for (size_t i=0; isecond << " "; + } + krinolog << "}" << "\n"; +} + +void +CDFEM_Parent_Edge::find_crossings(const std::vector > & nodes_isovar) +{ + my_crossings.clear(); + my_crossing_signs.clear(); + + const int num_nodes = get_num_nodes(); + ThrowAssert(static_cast(nodes_isovar.size()) == num_nodes); + const int num_ls = nodes_isovar[0].size(); + if (num_ls > 1 && Phase_Support::has_one_levelset_per_phase()) + { + find_crossings_multiple_levelset(nodes_isovar); + find_crossings_including_fake_ones(nodes_isovar); + return; + } + + // TODO: respect minimum_internal_edge_size + + for ( int ls_index = 0; ls_index < num_ls; ++ls_index ) + { + InterfaceID iface(ls_index, ls_index); + my_crossing_signs[iface] = LevelSet::sign(nodes_isovar[num_nodes-1][ls_index]); + if( !LevelSet::sign_change(nodes_isovar[0][ls_index], nodes_isovar[num_nodes-1][ls_index]) ) continue; + for ( int s = 0; s < num_nodes-1; ++s ) + { + const double ls0 = nodes_isovar[s][ls_index]; + const double ls1 = nodes_isovar[s+1][ls_index]; + if ( LevelSet::sign_change(ls0, ls1) ) + { + const double interval_position = ls0 / ( ls0 - ls1 ); + const double abs_position = (1.-interval_position)*my_edge_node_positions[s] + interval_position*my_edge_node_positions[s+1]; + my_crossings[iface] = abs_position; + my_crossing_signs[iface] = LevelSet::sign(ls1); + } + } + } + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + debug_print_crossings(); + } +} + +std::vector*> get_sorted_internal_crossings(CrossingMap & crossings) +{ + std::vector*> sortedCrossings; + sortedCrossings.reserve(crossings.size()); + for (auto && crossing : crossings) + if (crossing.second > 0. && crossing.second < 1.) + sortedCrossings.push_back(&crossing); + + std::sort(sortedCrossings.begin(), sortedCrossings.end(), + [](const std::pair* crossing0, const std::pair* crossing1) + { return crossing0->second < crossing1->second || (crossing0->second == crossing1->second && crossing0->first < crossing1->first); }); + return sortedCrossings; +} + +void collapse_small_internal_segments_while_perserving_topology(CrossingMap &crossings, const double snapTol) +{ + const std::vector*> sortedCrossings = get_sorted_internal_crossings(crossings); + + bool done = false; + while (!done) + { + size_t minSegment = 0; + double minSegmentSize = snapTol; + for (size_t i=1; isecond - sortedCrossings[i-1]->second); + if (segmentSize > 0 && segmentSize <= minSegmentSize) + { + minSegment = i; + minSegmentSize = segmentSize; + } + } + done = minSegment == 0; + if (!done) + { + double & loc0 = sortedCrossings[minSegment-1]->second; + double & loc1 = sortedCrossings[minSegment]->second; + const double newLoc = 0.5*(loc0+loc1); + loc0 = newLoc; + loc1 = newLoc; + } + } +} + +void CDFEM_Parent_Edge::collapse_small_segments_while_preserving_topology(const double snapTol) +{ + for (auto && crossing : my_crossings) + { + if (crossing.second <= snapTol) + crossing.second = 0.0; + else if (crossing.second >= 1.-snapTol ) + crossing.second = 1.0; + } + collapse_small_internal_segments_while_perserving_topology(my_crossings, snapTol); +} + +std::vector get_effective_sorted_parent_node_domains(const int parentNodeIndex, const CrossingMap & crossings, const std::vector & sortedParentNodeDomains) +{ + std::vector effectiveSortedParentNodeDomains = sortedParentNodeDomains; + double endPoint = (parentNodeIndex == 0) ? 0. : 1.; + for (auto && crossing : crossings) + { + if (std::binary_search(sortedParentNodeDomains.begin(), sortedParentNodeDomains.end(), crossing.first.first_ls()) || + std::binary_search(sortedParentNodeDomains.begin(), sortedParentNodeDomains.end(), crossing.first.second_ls())) + { + endPoint = (parentNodeIndex == 0) ? std::max(endPoint, crossing.second) : std::min(endPoint, crossing.second); + } + } + for (auto && crossing : crossings) + { + const bool inRange = (parentNodeIndex == 0) ? (crossing.second < endPoint) : (crossing.second > endPoint); + if (inRange) + { + effectiveSortedParentNodeDomains.push_back(crossing.first.first_ls()); + effectiveSortedParentNodeDomains.push_back(crossing.first.second_ls()); + } + } + + stk::util::sort_and_unique(effectiveSortedParentNodeDomains); + return effectiveSortedParentNodeDomains; +} + +void adjust_crossing_locations_based_on_node_captured_domains_for_level_set_per_interface(const std::vector & sortedParentNode0Domains, const std::vector & sortedParentNode1Domains, CrossingMap & crossings) +{ + if (sortedParentNode0Domains.empty() && sortedParentNode1Domains.empty()) + return; + + for (auto && crossing : crossings) + { + const InterfaceID & iface = crossing.first; + ThrowAssert(iface.first_ls() == iface.second_ls()); + const bool in0 = std::binary_search(sortedParentNode0Domains.begin(), sortedParentNode0Domains.end(), iface.first_ls()); + const bool in1 = std::binary_search(sortedParentNode1Domains.begin(), sortedParentNode1Domains.end(), iface.first_ls()); + + double & loc = crossing.second; + if (in0 && (!in1 || loc<0.5)) + loc = 0.; + else if (in1 && (!in0 || loc>=0.5)) + loc = 1.; + } +} + +void adjust_crossing_locations_based_on_node_captured_domains_for_level_set_per_phase(const int parentNodeIndex, const std::vector & sortedParentNodeDomains, CrossingMap & allCrossings) +{ + if (sortedParentNodeDomains.empty()) + return; + + ThrowAssert(parentNodeIndex == 0 || parentNodeIndex == 1); + const double nodePos = (parentNodeIndex == 0) ? 0. : 1.; + + double furthestLocationToAdjust = (parentNodeIndex == 0) ? 0. : 1.; + for (auto && crossing : allCrossings) + { + const InterfaceID & iface = crossing.first; + if (std::binary_search(sortedParentNodeDomains.begin(), sortedParentNodeDomains.end(), iface.first_ls()) && + std::binary_search(sortedParentNodeDomains.begin(), sortedParentNodeDomains.end(), iface.second_ls())) + { + furthestLocationToAdjust = (parentNodeIndex == 0) ? std::max(furthestLocationToAdjust, crossing.second) : std::min(furthestLocationToAdjust, crossing.second); + crossing.second = nodePos; + } + } + + for (auto && crossing : allCrossings) + { + if ((parentNodeIndex == 0 && crossing.second < furthestLocationToAdjust) || + (parentNodeIndex == 1 && crossing.second > furthestLocationToAdjust)) + { + crossing.second = nodePos; + } + } +} + +void adjust_crossing_locations_based_on_node_captured_domains_for_level_set_per_phase(const std::vector & sortedParentNode0Domains, const std::vector & sortedParentNode1Domains, CrossingMap & allCrossings) +{ + if (sortedParentNode0Domains.empty() && sortedParentNode1Domains.empty()) + return; + + double highestLocToSendTo0 = 0.; + double lowestLocToSendTo1 = 1.; + for (auto && crossing : allCrossings) + { + const InterfaceID & iface = crossing.first; + const bool in0 = + std::binary_search(sortedParentNode0Domains.begin(), sortedParentNode0Domains.end(), iface.first_ls()) && + std::binary_search(sortedParentNode0Domains.begin(), sortedParentNode0Domains.end(), iface.second_ls()); + const bool in1 = + std::binary_search(sortedParentNode1Domains.begin(), sortedParentNode1Domains.end(), iface.first_ls()) && + std::binary_search(sortedParentNode1Domains.begin(), sortedParentNode1Domains.end(), iface.second_ls()); + + double & loc = crossing.second; + if (in0 && (!in1 || loc<0.5)) + { + highestLocToSendTo0 = std::max(loc, highestLocToSendTo0); + loc = 0.; + } + if (in1 && (!in0 || loc>=0.5)) + { + highestLocToSendTo0 = std::min(loc, lowestLocToSendTo1); + loc = 1.; + } + } + + for (auto && crossing : allCrossings) + { + double & loc = crossing.second; + if (loc < highestLocToSendTo0) + loc = 0.; + if (loc > lowestLocToSendTo1) + loc = 1.; + } +} + +void copy_real_crossing_locations_to_fake_crossing_locations(const CrossingMap & realCrossings, CrossingMap & allCrossings) +{ + for (auto && crossing : allCrossings) + { + auto iter = realCrossings.find(crossing.first); + if (iter != realCrossings.end()) + crossing.second = iter->second; + } +} + +std::pair get_begin_and_end_phases(const CrossingMap & realCrossings, const CrossingSignMap & realCrossingSign) +{ + double begLoc = 1.; + double endLoc = 0.; + int begPhase = -1; + int endPhase = -1; + for (auto && crossing : realCrossings) + { + const InterfaceID iface = crossing.first; + const double loc = crossing.second; + const int sign = realCrossingSign.at(iface); + const int fromPhase = (sign == 1) ? iface.first_ls() : iface.second_ls(); + if (loc < begLoc || (loc == begLoc && fromPhase > begPhase)) + { + begPhase = fromPhase; + begLoc = loc; + } + const int toPhase = (sign == -1) ? iface.first_ls() : iface.second_ls(); + if (loc > endLoc || (loc == endLoc && toPhase > endPhase)) + { + endPhase = toPhase; + endLoc = loc; + } + } + return {begPhase,endPhase}; +} + +static double find_next_crossing_location(const CrossingMap & crossings, + const CrossingSignMap & crossingSigns, + const double currentLocation, + const int currentPhase) +{ + double nextLocation = 1.; + for (auto && crossing : crossings) + { + if (crossing.second >= currentLocation) + { + const InterfaceID iface = crossing.first; + const int fromPhase = (crossingSigns.at(iface) == 1) ? iface.first_ls() : iface.second_ls(); + if (fromPhase == currentPhase) + nextLocation = std::min(nextLocation, crossing.second); + } + } + return nextLocation; +} + +static std::vector find_next_phase_candidates_at_location(const CrossingMap & crossings, + const CrossingSignMap & crossingSigns, + const double nextLocation, + const int currentPhase) +{ + std::vector nextPhaseCandidates; + for (auto && crossing : crossings) + { + if (crossing.second == nextLocation) + { + const InterfaceID iface = crossing.first; + const int sign = crossingSigns.at(iface); + const int fromPhase = (sign == 1) ? iface.first_ls() : iface.second_ls(); + if (fromPhase == currentPhase) + { + const int toPhase = (sign == -1) ? iface.first_ls() : iface.second_ls(); + nextPhaseCandidates.push_back(toPhase); + } + } + } + return nextPhaseCandidates; +} + +std::vector find_next_phase_candidates(const CrossingMap & crossings, + const CrossingSignMap & crossingSigns, + const double currentLocation, + const int currentPhase) +{ + const double nextLocation = find_next_crossing_location(crossings, crossingSigns, currentLocation, currentPhase); + return find_next_phase_candidates_at_location(crossings, crossingSigns, nextLocation, currentPhase); +} + +std::vector> shortest_path_to_end(const std::vector> & pathSoFar, + const CrossingMap & crossings, + const CrossingSignMap & crossingSigns, + const double currentLocation, + const int currentPhase, + const int endPhase) +{ + const std::vector nextPhaseCandidates = find_next_phase_candidates(crossings, crossingSigns, currentLocation, currentPhase); + if (nextPhaseCandidates.empty()) + { + if (currentPhase == endPhase) + return pathSoFar; + else + return {}; + } + + std::vector> shortestPath; + size_t shortestPathSize = std::numeric_limits::max(); + for (int nextPhase : nextPhaseCandidates) + { + std::vector> path = pathSoFar; + const InterfaceID iface(currentPhase, nextPhase); + const double nextLocation = crossings.at(iface); + path.emplace_back(iface, nextLocation); + const auto fullPath = shortest_path_to_end(path, crossings, crossingSigns, nextLocation, nextPhase, endPhase); + if (!fullPath.empty() && fullPath.size() < shortestPathSize) + { + shortestPath = fullPath; + shortestPathSize = fullPath.size(); + } + } + return shortestPath; +} + +std::vector> shortest_path_from_begin_to_end(const CrossingMap & crossings, + const CrossingSignMap & crossingSigns, + const int beginPhase, + const int endPhase) +{ + std::vector> emptyPath; + double beginLocation = -1.; + return shortest_path_to_end(emptyPath, crossings, crossingSigns, beginLocation, beginPhase, endPhase); +} + +bool determine_real_crossings_from_locations(CrossingMap & realCrossings, CrossingSignMap & realCrossingSigns, std::set & edgePhases, const CrossingMap & allCrossings, const CrossingSignMap & allCrossingSigns) +{ + const auto begEndPhases = get_begin_and_end_phases(realCrossings, realCrossingSigns); + + if (begEndPhases.first == begEndPhases.second) + { + return true; + } + + realCrossings.clear(); + realCrossingSigns.clear(); + edgePhases.clear(); + + const auto shortestPath = shortest_path_from_begin_to_end(allCrossings, allCrossingSigns, begEndPhases.first, begEndPhases.second); + if (shortestPath.empty()) + return false; + + for (auto && crossing : shortestPath) + { + realCrossings[crossing.first] = crossing.second; + realCrossingSigns[crossing.first] = allCrossingSigns.at(crossing.first); + edgePhases.insert(crossing.first.first_ls()); + edgePhases.insert(crossing.first.second_ls()); + } + return true; +} + +void CDFEM_Parent_Edge::adjust_crossing_locations_based_on_node_captured_domains(const std::vector & sortedParentNode0Domains, const std::vector & sortedParentNode1Domains) +{ + if (sortedParentNode0Domains.empty() && sortedParentNode1Domains.empty()) + return; + if (Phase_Support::has_one_levelset_per_phase()) + { + if (my_crossings_including_fake.empty()) + return; + + adjust_crossing_locations_based_on_node_captured_domains_for_level_set_per_phase(sortedParentNode0Domains, sortedParentNode1Domains, my_crossings_including_fake); + + const bool success = determine_real_crossings_from_locations(my_crossings, my_crossing_signs, edge_phases, my_crossings_including_fake, my_crossing_signs_including_fake); + if (!success) + krinolog << "Failed to adjust crossings " << *this << stk::diag::dendl; + } + else + { + if (my_crossings.empty()) + return; + adjust_crossing_locations_based_on_node_captured_domains_for_level_set_per_interface(sortedParentNode0Domains, sortedParentNode1Domains, my_crossings); + } +} + +void +CDFEM_Parent_Edge::find_crossings_multiple_levelset(const std::vector > & nodes_isovar) +{ + const Segment_Vector lower_envelope = SegmentLowerEnvelope::find_lower_envelope(my_edge_node_positions, nodes_isovar); + + edge_phases.clear(); + for(auto && it : lower_envelope) + { + edge_phases.insert(it.ls_index()); + } + + // Create crossings between the segments of the lower_envelope + for(Segment_Vector::const_iterator it = lower_envelope.begin(); it != lower_envelope.end()-1; ++it) + { + const LS_Segment & cur = *it; + const LS_Segment & next = *(it+1); + if (cur.ls_index() == next.ls_index()) continue; + + const double cur_right = cur.right_endpoint(); + const double next_left = next.left_endpoint(); + ThrowRequire(cur_right == next_left); + ThrowRequire(cur.ls_index() != next.ls_index()); + InterfaceID iface(cur.ls_index(), next.ls_index()); + ThrowRequireMsg(my_crossings.find(iface) == my_crossings.end(), "Multiple interface crossing error after pruning."); + my_crossings[iface] = cur_right; + my_crossing_signs[iface] = (cur.ls_index() < next.ls_index()) ? 1 : -1; + } + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + debug_print_crossings(); + } +} + +bool +CDFEM_Parent_Edge::have_any_crossings() const +{ + return !my_crossings.empty(); +} + +std::tuple +CDFEM_Parent_Edge::get_crossing_position_and_sign(const InterfaceID key) const +{ + ThrowRequire(Phase_Support::has_one_levelset_per_phase()); + + if(have_crossing(key)) + { + return std::make_tuple(get_crossing_position(key), get_crossing_sign(key), false); + } + + return std::make_tuple(get_fake_crossing_position(key), get_fake_crossing_sign(key), true); +} + +typedef std::array*,2> CrossingInterval; + +static CrossingInterval get_crossing_interval(const CDFEM_Parent_Edge & edge, const std::pair & fakeCrossing) +{ + const double fakeLoc = fakeCrossing.second; + const std::pair * before = nullptr; + const std::pair * after = nullptr; + for (auto && crossing : edge.get_crossings()) + { + const double loc = crossing.second; + if (loc == fakeLoc) + { + return {&crossing, &crossing}; + } + else if (loc < fakeLoc) + { + if (before == nullptr || loc > before->second) + before = &crossing; + } + else + { + if (after == nullptr || loc < after->second) + after = &crossing; + } + } + return {before, after}; +} + +static int get_phase_on_interval(const CDFEM_Parent_Edge & edge, const CrossingInterval & crossingInterval) +{ + const std::pair * before = crossingInterval[0]; + const std::pair * after = crossingInterval[1]; + ThrowRequire(before != nullptr || after != nullptr); + if (before != nullptr) + { + return (edge.get_crossing_sign(before->first) == -1) ? before->first.first_ls() : before->first.second_ls(); + } + return (edge.get_crossing_sign(after->first) == -1) ? after->first.second_ls() : after->first.first_ls(); +} + +static bool fake_crossing_is_actually_real(const CDFEM_Parent_Edge & edge, const CrossingInterval & crossingInterval, const std::pair & fakeCrossing) +{ + if (crossingInterval[0] != crossingInterval[1]) + { + const int phaseOnInterval = get_phase_on_interval(edge, crossingInterval); + const InterfaceID fakeInterface = fakeCrossing.first; + const bool crossingIsActuallyReal = (fakeInterface.first_ls() == phaseOnInterval || fakeInterface.second_ls() == phaseOnInterval); + return crossingIsActuallyReal; + } + return false; +} + +static void fixup_fake_crossing_location_for_consistency(CDFEM_Parent_Edge & edge, std::pair & fakeCrossing) +{ + const CrossingInterval & crossingInterval = get_crossing_interval(edge, fakeCrossing); + if (fake_crossing_is_actually_real(edge, crossingInterval, fakeCrossing)) + { + const std::pair * before = crossingInterval[0]; + const std::pair * after = crossingInterval[1]; + double & loc = fakeCrossing.second; + const bool useBefore = before ? (after ? (loc-before->second < after->second-loc) : true) : false; + loc = useBefore ? before->second : after->second; + } +} + +static bool fake_crossing_is_actually_real(const CDFEM_Parent_Edge & edge, const std::pair & fakeCrossing) +{ + const CrossingInterval & crossingInterval = get_crossing_interval(edge, fakeCrossing); + return fake_crossing_is_actually_real(edge, crossingInterval, fakeCrossing); +} + +bool CDFEM_Parent_Edge::all_fake_crossings_are_really_fake() const +{ + for (auto && crossing : my_crossings_including_fake) + if (!have_crossing(crossing.first) && fake_crossing_is_actually_real(*this, crossing)) + return false; + return true; +} + +void CDFEM_Parent_Edge::fixup_fake_crossing_locations_for_consistency() +{ + for (auto && crossing : my_crossings_including_fake) + if (!have_crossing(crossing.first)) + fixup_fake_crossing_location_for_consistency(*this, crossing); +} + +void +CDFEM_Parent_Edge::find_crossings_including_fake_ones(const std::vector > & nodes_isovar) +{ + my_crossings_including_fake.clear(); + my_crossing_signs_including_fake.clear(); + + ThrowRequire(Phase_Support::has_one_levelset_per_phase()); + const int numLS = nodes_isovar[0].size(); + std::vector lsMins(numLS,std::numeric_limits::max()); + std::vector lsMaxs(numLS,std::numeric_limits::lowest()); + for (auto && nodeIsovar : nodes_isovar) + { + for (int i=0; i result = have_crossing(iface) ? + std::make_pair(get_crossing_position(iface), get_crossing_sign(iface)) : + find_crossing_position_and_sign(iface, nodes_isovar); + if (result.first >= 0.) + { + my_crossings_including_fake[iface] = result.first; + my_crossing_signs_including_fake[iface] = result.second; + } + } + } + } + + fixup_fake_crossing_locations_for_consistency(); +} + +std::pair +CDFEM_Parent_Edge::find_crossing_position_and_sign(const InterfaceID key, const std::vector > & nodes_isovar) const +{ + ThrowRequire(Phase_Support::has_one_levelset_per_phase()); + return krino::find_crossing_position_and_sign(key, my_edge_node_positions, nodes_isovar); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.hpp b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.hpp new file mode 100644 index 000000000000..cceb31cf10e1 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edge.hpp @@ -0,0 +1,137 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_CDFEM_Parent_Edge_h +#define Akri_CDFEM_Parent_Edge_h + +#include +#include +#include + +#include +#include +#include + +namespace stk { namespace mesh { class BulkData; } } + +namespace krino { + +class CDFEM_Parent_Edge { +public: + CDFEM_Parent_Edge(const std::vector & edgeNodes, + const std::vector & edgeNodePositions, + const std::vector > & nodes_isovar) + : my_edge_nodes(edgeNodes), + my_edge_node_positions(edgeNodePositions) + { + ThrowAssert(edgeNodePositions.size() == nodes_isovar.size()); + find_crossings(nodes_isovar); + } + + CDFEM_Parent_Edge(const std::vector & edgeNodePositions, + const std::vector > & nodes_isovar) + : CDFEM_Parent_Edge({}, edgeNodePositions, nodes_isovar) {} + + CDFEM_Parent_Edge(const std::vector & edgeNodes, + const std::vector > & nodes_isovar) + : CDFEM_Parent_Edge(edgeNodes, {0.,1.}, nodes_isovar) {} + + CDFEM_Parent_Edge(const std::vector > & nodes_isovar) + : CDFEM_Parent_Edge({0.,1.}, nodes_isovar) {} + + CDFEM_Parent_Edge(const std::vector & edgeNodes, + const std::vector & edgeNodePositions); + CDFEM_Parent_Edge() {} + + // Must be larger than machine epsilon, but puts limit on smallest effective snap tolerance + static double MinSize(); + + bool valid() const { return !my_edge_node_positions.empty(); } + + void find_crossings(const std::vector > & nodes_isovar); + void collapse_small_segments_while_preserving_topology(const double snapTol); + + void adjust_crossing_locations_based_on_node_captured_domains(const std::vector & sortedParentNode0Domains, const std::vector & sortedParentNode1Domains); + + bool have_crossing(const InterfaceID key) const { return my_crossings.find(key) != my_crossings.end(); } + const CrossingMap & get_crossings() const { return my_crossings; } + const CrossingMap & get_crossings_including_fake() const { return my_crossings_including_fake; } + bool all_fake_crossings_are_really_fake() const; + + double get_crossing_position(const InterfaceID key) const { + CrossingMap::const_iterator it = my_crossings.find(key); + return it != my_crossings.end() ? it->second : -1.0; + } + // Crossing sign is defined as whether the interface is + or - for x > crossing point + // For multiple LS problems we'll say that -1 corresponds to InterfaceID.first being lower for x > x_crossing_point + // For uncrossed edges it will be the sign of both parent nodes + int get_crossing_sign(const InterfaceID key) const { + CrossingSignMap::const_iterator it = my_crossing_signs.find(key); + return it->second; + } + std::tuple get_crossing_position_and_sign(const InterfaceID key) const; + unsigned get_num_nodes() const { return my_edge_node_positions.size(); } + const std::vector & get_nodes() const { return my_edge_nodes; } + bool have_any_crossings() const; + std::pair get_parent_nodes() const { return std::pair(my_edge_nodes.front(), my_edge_nodes.back()); } + int get_uncrossed_phase() const { return (edge_phases.size() == 1) ? (*edge_phases.begin()) : -1; } + const std::set & get_edge_phases() const { return edge_phases; } + double get_edge_node_position(stk::mesh::Entity edgeNode) const; + + friend std::ostream & operator << (std::ostream &os, const CDFEM_Parent_Edge & edge); + void debug_print_crossings() const; + +private: + void find_crossings_multiple_levelset(const std::vector > & nodes_isovar); + std::pair find_crossing_position_and_sign(const InterfaceID key, const std::vector > & nodes_isovar) const; + void find_crossings_including_fake_ones(const std::vector > & nodes_isovar); + double get_fake_crossing_position(const InterfaceID key) const { + CrossingMap::const_iterator it = my_crossings_including_fake.find(key); + return it != my_crossings_including_fake.end() ? it->second : -1.0; + } + int get_fake_crossing_sign(const InterfaceID key) const { + CrossingSignMap::const_iterator it = my_crossing_signs_including_fake.find(key); + return it->second; + } + void fixup_fake_crossing_locations_for_consistency(); + + std::vector my_edge_nodes; + std::vector my_edge_node_positions; + CrossingMap my_crossings; + CrossingSignMap my_crossing_signs; + CrossingMap my_crossings_including_fake; + CrossingSignMap my_crossing_signs_including_fake; + std::set edge_phases; +}; + +inline std::ostream & operator << (std::ostream &os, const CDFEM_Parent_Edge & edge) +{ + const unsigned num_nodes = edge.get_num_nodes(); + os << "CDFEM parent edge has " << num_nodes << " nodes and phases { "; + for (int phase : edge.get_edge_phases()) + os << phase << " "; + os << "}\n crossings: { "; + const auto oldPrecision = os.precision(); + os.precision(16); + for ( auto && crossing : edge.get_crossings() ) + { + os << crossing.first << "@" << crossing.second << ", sign=" << edge.get_crossing_sign(crossing.first) << " "; + } + os << "}" << "\n crossings including fake: { "; + for ( auto && crossing : edge.get_crossings_including_fake() ) + { + os << crossing.first << "@" << crossing.second << ", sign=" << edge.get_fake_crossing_sign(crossing.first) << " "; + } + os << "}" << "\n"; + os.precision(oldPrecision); + return os; +} + +} // namespace krino + +#endif // Akri_CDFEM_Parent_Edge_h diff --git a/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.cpp b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.cpp new file mode 100644 index 000000000000..d3eab40fb058 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.cpp @@ -0,0 +1,492 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino +{ + +std::set get_phases_present_on_edges(const std::vector & parentEdges) +{ + std::set phasesPresent; + for(auto && parentEdge : parentEdges) + if(parentEdge) + for (int phase : parentEdge->get_edge_phases()) + phasesPresent.insert(phase); + return phasesPresent; +} + +static void add_interface_phases(const InterfaceID & interface, std::set & phases) +{ + phases.insert(interface.first_ls()); + phases.insert(interface.second_ls()); +} + +static std::set get_all_interfaces_including_fake(const std::vector & parentEdges) +{ + std::set allInterfacesIncludingFake; + for(auto && parentEdge : parentEdges) + if(parentEdge) + for (auto && crossing : parentEdge->get_crossings_including_fake()) + allInterfacesIncludingFake.insert(crossing.first); + return allInterfacesIncludingFake; +} + +bool phase_has_interfaces_to_all_other_phases(const int phase, const std::set & phasesPresent, const std::set & interfaces) +{ + for (int otherPhase : phasesPresent) + if (interfaces.find(InterfaceID(phase,otherPhase)) == interfaces.end()) + return false; + return true; +} + +void add_phases_possibly_present_on_interior(const std::vector & parentEdges, std::set & phasesPresent) +{ + const std::set allInterfacesIncludingFake = get_all_interfaces_including_fake(parentEdges); + + std::set phasesPossiblyPresent; + for (auto && interface : allInterfacesIncludingFake) + add_interface_phases(interface, phasesPossiblyPresent); + + // This should be unit tested. What about order dependency? + for (int phase : phasesPossiblyPresent) + if (phasesPresent.find(phase) == phasesPresent.end()) + if (phase_has_interfaces_to_all_other_phases(phase, phasesPresent, allInterfacesIncludingFake)) + phasesPresent.insert(phase); +} + +std::set get_phases_present_on_edges_and_interior(const std::vector & elementParentEdges) +{ + std::set phasesPresent = get_phases_present_on_edges(elementParentEdges); + add_phases_possibly_present_on_interior(elementParentEdges, phasesPresent); + return phasesPresent; +} + +static +std::set get_interfaces_with_any_real_edge_crossings(const std::vector & elementParentEdges) +{ + std::set interfacesPresent; + for(auto && parentEdge : elementParentEdges) + if(parentEdge) + for(const auto & crossing : parentEdge->get_crossings()) + interfacesPresent.insert(crossing.first); + return interfacesPresent; +} + +std::set get_interfaces_present(const std::vector & elementParentEdges) +{ + return get_interfaces_with_any_real_edge_crossings(elementParentEdges); +} + +void fill_element_parent_edges(const stk::mesh::BulkData & mesh, + const stk::mesh::Entity elem, + const ParentEdgeMap & parentEdges, + std::vector & elementParentEdges, + std::vector & areParentEdgesAreOrientedSameAsElementEdges) +{ + const stk::topology stk_topology = mesh.bucket(elem).topology(); + const unsigned numEdges = stk_topology.num_edges(); + const stk::mesh::Entity * const nodes = mesh.begin_nodes(elem); + elementParentEdges.assign(numEdges, nullptr); + areParentEdgesAreOrientedSameAsElementEdges.assign(numEdges, true); + for(unsigned i=0; i < numEdges; ++i) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(stk_topology, i); + const CDFEM_Parent_Edge * parent_edge = + find_parent_edge(mesh, parentEdges, nodes[edge_node_ordinals[0]], nodes[edge_node_ordinals[1]]); + elementParentEdges[i] = parent_edge; + if(parent_edge && parent_edge->get_parent_nodes().first != nodes[edge_node_ordinals[0]]) + areParentEdgesAreOrientedSameAsElementEdges[i] = false; + } +} + +void fill_face_nodes_and_parent_edges(const stk::topology & elementTopology, + const int iFace, + const std::vector & elementNodes, + const std::vector & elementParentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + std::vector & faceNodes, + std::vector & faceParentEdges, + std::vector & areParentEdgesOrientedSameAsFaceEdges) +{ + ThrowAssert(elementTopology == stk::topology::TETRAHEDRON_4); + constexpr std::array,4> faceEdges = {{ {{0,4,3}}, {{1,5,4}}, {{3,5,2}}, {{2,1,0}} }}; + constexpr std::array,4> isFaceEdgeOrientedSameAsElementEdge = {{ {{true,true,false}}, {{true,true,false}}, {{true,false,true}}, {{false,false,false}} }}; + + faceNodes.resize(3); + elementTopology.face_nodes(elementNodes, iFace, faceNodes.data()); + + constexpr int numFaceEdges = 3; + faceParentEdges.resize(numFaceEdges); + areParentEdgesOrientedSameAsFaceEdges.resize(3); + for (int i=0; iprimary_entity_rank() != stk::topology::ELEMENT_RANK) continue; + + const stk::mesh::Part * nonconformal_node_iopart = phaseSupport.find_nonconformal_part(*part_ptr); + + if (phaseSupport.level_set_is_used_by_nonconformal_part(cdfemSupport.ls_field(ls_index).ptr, nonconformal_node_iopart)) + { + return true; + } + } + + return false; +} + +static bool +has_io_part_containing_phase(const Phase_Support & phase_support, const stk::mesh::PartVector & parts, const PhaseTag & phase) +{ + for(stk::mesh::PartVector::const_iterator part_iter = parts.begin(); part_iter != parts.end(); ++part_iter) + { + const stk::mesh::Part * const part = *part_iter; + if (part->primary_entity_rank() != stk::topology::ELEMENT_RANK || // limit ourselves to volume parts + !(stk::io::is_part_io_part(*part) || + phase_support.is_nonconformal(part))) + continue; + + const PhaseTag & iopart_phase = phase_support.get_iopart_phase(*part); + if (iopart_phase.contain(phase)) + { + return true; + } + } + + return false; +} + +static bool node_touches_alive_block(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + stk::mesh::Entity node, + const int ls_index ) +{ + const CDFEM_Inequality_Spec * death_spec = cdfemSupport.get_death_spec(ls_index); + if (nullptr == death_spec) return false; + + const PhaseTag & alive_phase = death_spec->get_active_phase(); + const stk::mesh::PartVector & node_parts = mesh.bucket(node).supersets(); + return has_io_part_containing_phase(phaseSupport, node_parts, alive_phase); +} + +static bool node_touches_dead_block(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + stk::mesh::Entity node, + const int ls_index ) +{ + const CDFEM_Inequality_Spec * death_spec = cdfemSupport.get_death_spec(ls_index); + if (nullptr == death_spec) return false; + + const PhaseTag & dead_phase = death_spec->get_deactivated_phase(); + const stk::mesh::PartVector & node_parts = mesh.bucket(node).supersets(); + return has_io_part_containing_phase(phaseSupport, node_parts, dead_phase); +} + +static bool node_has_real_ls_value(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + stk::mesh::Entity node, + const int ls_index ) +{ + if( node_touches_dead_block(mesh, cdfemSupport, phaseSupport, node, ls_index) && + !node_touches_alive_block(mesh, cdfemSupport, phaseSupport, node, ls_index) ) + return false; + + return in_block_decomposed_by_ls(mesh, cdfemSupport, phaseSupport, node, ls_index); +} + +static void debug_print_edge_info(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + const std::vector & edge_nodes, + const std::vector > & nodes_isovar) +{ + const int num_nodes = nodes_isovar.size(); + if(krinolog.shouldPrint(LOG_DEBUG)) + { + const auto old_precision = krinolog.getStream().precision(); + krinolog.getStream().precision(16); + const int num_ls = cdfemSupport.num_ls_fields(); + krinolog << stk::diag::dendl << "CDFEM_Parent_Edge::find_crossings():" << "\n"; + for ( int n = 0; n < num_nodes; ++n ) + { + krinolog << " Node: " << mesh.identifier(edge_nodes[n]) << ", in_block_decomposed_by_ls = { "; + for ( int ls_index = 0; ls_index < num_ls; ++ls_index ) krinolog << in_block_decomposed_by_ls(mesh, cdfemSupport, phaseSupport, edge_nodes[n], ls_index) << " "; + krinolog << "}, has_real_ls_value = { "; + for ( int ls_index = 0; ls_index < num_ls; ++ls_index ) krinolog << node_has_real_ls_value(mesh, cdfemSupport, phaseSupport, edge_nodes[n], ls_index) << " "; + krinolog << "}, ls = { "; + for ( int ls_index = 0; ls_index < num_ls; ++ls_index ) krinolog << nodes_isovar[n][ls_index] << " "; + krinolog << "}"; + krinolog << stk::diag::dendl; + } + krinolog.getStream().precision(old_precision); + } +} + +static void edge_ls_node_values(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + const std::vector & edge_nodes, + std::vector >& nodes_isovar) +{ + const unsigned num_nodes = edge_nodes.size(); + const int num_ls = cdfemSupport.num_ls_fields(); + + nodes_isovar.assign(num_nodes, std::vector(num_ls, -1.0)); + for (unsigned n = 0; n < num_nodes; ++n) + { + for (int ls_index = 0; ls_index < num_ls; ++ls_index) + { + const CDFEM_Inequality_Spec * death_spec = cdfemSupport.get_death_spec(ls_index); + FieldRef isovar = cdfemSupport.ls_field(ls_index).isovar; + + const bool node_has_ls = node_has_real_ls_value(mesh, cdfemSupport, phaseSupport, edge_nodes[n], ls_index); + nodes_isovar[n][ls_index] = 0.0; + if (node_has_ls) + { + if (nullptr != death_spec && isovar.entity_rank() == stk::topology::ELEMENT_RANK) + { + // currently requires aura to work correctly in parallel + ThrowAssertMsg(mesh.is_automatic_aura_on(), "Capability requires aura."); + bool have_pos_elem = false; + bool have_neg_elem = false; + const unsigned num_node_elems = mesh.num_elements(edge_nodes[n]); + const stk::mesh::Entity* node_elems = mesh.begin_elements(edge_nodes[n]); + for (unsigned node_elem_index=0; node_elem_index(isovar, node_elem); + if (nullptr != isoptr) + { + if (*isoptr - cdfemSupport.ls_field(ls_index).isoval < 0.) + have_neg_elem = true; + else + have_pos_elem = true; + } + } + nodes_isovar[n][ls_index] = (have_pos_elem) ? (have_neg_elem ? 0.0 : 1.0) : -1.0; + } + else + { + const double * isoptr = field_data(isovar, edge_nodes[n]); + ThrowRequireMsg(nullptr != isoptr, "Isovar " << isovar.name() << " missing on node " << debug_entity(mesh, edge_nodes[n])); + nodes_isovar[n][ls_index] = *isoptr - cdfemSupport.ls_field(ls_index).isoval; + } + } + else if (nullptr != death_spec && node_touches_dead_block(mesh, cdfemSupport, phaseSupport, edge_nodes[n], ls_index)) + { + const bool dead_is_positive = death_spec->get_deactivated_phase().contain(cdfemSupport.ls_field(ls_index).identifier,+1); + if (dead_is_positive) nodes_isovar[n][ls_index] = 1.0; + } + + if (nullptr != death_spec && node_touches_dead_block(mesh, cdfemSupport, phaseSupport, edge_nodes[n], ls_index)) + { + const bool dead_is_positive = death_spec->get_deactivated_phase().contain(cdfemSupport.ls_field(ls_index).identifier, +1); + if (dead_is_positive && nodes_isovar[n][ls_index] < 0.0) + { + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "Setting node " << mesh.identifier(edge_nodes[n]) << " to zero to enforce irreversibility, ls = " << nodes_isovar[n][ls_index] << "\n"; + nodes_isovar[n][ls_index] = 0.0; + } + else if (!dead_is_positive && nodes_isovar[n][ls_index] >= 0.0) + { + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "Setting node " << mesh.identifier(edge_nodes[n]) << " to -REAL_MIN to enforce irreversibility, ls = " << nodes_isovar[n][ls_index] << "\n"; + nodes_isovar[n][ls_index] = -std::numeric_limits::min(); + } + } + } + } +} + +static CDFEM_Parent_Edge & +build_parent_edge(const stk::mesh::BulkData & mesh, ParentEdgeMap & parentEdges, const ParentsToChildMapper & parentsToChildMapper, const bool linearizeEdge, stk::mesh::Entity node0, stk::mesh::Entity node1) +{ + const stk::mesh::EntityId id0 = mesh.identifier(node0); + const stk::mesh::EntityId id1 = mesh.identifier(node1); + const ParentEdgeKey edge_key(id0, id1); + CDFEM_Parent_Edge & edge = parentEdges[edge_key]; + + std::vector edgeNodes; + std::vector edgeNodePositions; + + if(!edge.valid()) + { + const std::array nodes = (id0 < id1) ? std::array{node0, node1} : std::array{node1, node0}; + if (linearizeEdge) + fill_linear_edge_nodes_and_positions(nodes[0], nodes[1], edgeNodes, edgeNodePositions); + else + fill_edge_nodes_and_positions(mesh, nodes[0], nodes[1], parentsToChildMapper, edgeNodes, edgeNodePositions); + edge = CDFEM_Parent_Edge(edgeNodes, edgeNodePositions); + } + return edge; +} + +static void find_parent_edge_crossings(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + ParentEdgeMap & parentEdges) +{ + std::vector> nodes_isovar; + + for (auto && map_entry : parentEdges) + { + CDFEM_Parent_Edge & edge = map_entry.second; + const std::vector edge_nodes = edge.get_nodes(); + edge_ls_node_values(mesh, cdfemSupport, phaseSupport, edge_nodes, nodes_isovar); + debug_print_edge_info(mesh, cdfemSupport, phaseSupport, edge_nodes, nodes_isovar); + edge.find_crossings(nodes_isovar); + } + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << stk::diag::dendl; +} + +stk::mesh::Selector get_parent_element_selector(const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + stk::mesh::Selector parentElementSelector = + (cdfemSupport.get_parent_part() | (activePart & !cdfemSupport.get_child_part())) & + phaseSupport.get_all_decomposed_blocks_selector(); + + return parentElementSelector; +} + +stk::mesh::Selector get_owned_parent_element_selector(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + stk::mesh::Selector parentElementSelector = + (cdfemSupport.get_parent_part() | (activePart & !cdfemSupport.get_child_part())) & + phaseSupport.get_all_decomposed_blocks_selector() & + mesh.mesh_meta_data().locally_owned_part(); + + return parentElementSelector; +} + +std::vector get_owned_parent_elements(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + const stk::mesh::Selector parentElementSelector = get_owned_parent_element_selector(mesh, activePart, cdfemSupport, phaseSupport); + std::vector parentElements; + stk::mesh::get_selected_entities( parentElementSelector, mesh.get_buckets(stk::topology::ELEMENT_RANK, parentElementSelector), parentElements, false ); + return parentElements; +} + +ParentEdgeMap +build_parent_edges(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper & parentsToChildMapper, + const std::function & should_build_linear_edge, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + std::vector elements; + stk::mesh::get_entities( mesh, stk::topology::ELEMENT_RANK, mesh.mesh_meta_data().locally_owned_part(), elements, false); + + return build_parent_edges_using_elements(mesh, parentsToChildMapper, should_build_linear_edge, elements, activePart, cdfemSupport, phaseSupport); +} + +std::function build_no_linearized_edges_function() +{ + return [](stk::mesh::Entity node0, stk::mesh::Entity node1) + { return false; }; +} + +std::function build_all_linearized_edges_function() +{ + return [](stk::mesh::Entity node0, stk::mesh::Entity node1) + { return true; }; +} + +ParentEdgeMap +build_parent_edges_using_elements(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper & parentsToChildMapper, + const std::function & should_build_linear_edge, + const std::vector & elements, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + const stk::mesh::Selector parentElementSelector = get_owned_parent_element_selector(mesh, activePart, cdfemSupport, phaseSupport); + + ParentEdgeMap parentEdges; + + for (auto && elem : elements) + { + if (parentElementSelector(mesh.bucket(elem))) + { + const stk::topology topology = mesh.bucket(elem).topology(); + const stk::mesh::Entity* elem_nodes = mesh.begin_nodes(elem); + const unsigned numEdges = topology.num_edges(); + + for (unsigned iedge = 0; iedge < numEdges; ++iedge) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(topology, iedge); + stk::mesh::Entity node0 = elem_nodes[edge_node_ordinals[0]]; + stk::mesh::Entity node1 = elem_nodes[edge_node_ordinals[1]]; + + const bool linearizeEdge = should_build_linear_edge(node0, node1); + build_parent_edge(mesh, parentEdges, parentsToChildMapper, linearizeEdge, node0, node1); + } + } + } + + find_parent_edge_crossings(mesh, cdfemSupport, phaseSupport, parentEdges); + + return parentEdges; +} + +const CDFEM_Parent_Edge * +find_parent_edge(const stk::mesh::BulkData & mesh, const ParentEdgeMap & parentEdges, stk::mesh::Entity node0, stk::mesh::Entity node1) +{ + const stk::mesh::EntityId id0 = mesh.identifier(node0); + const stk::mesh::EntityId id1 = mesh.identifier(node1); + const ParentEdgeKey edge_key(id0, id1); + + ParentEdgeMap::const_iterator it = parentEdges.find(edge_key); + + if (it != parentEdges.end()) + return &(it->second); + + return nullptr; +} + +} // namespace diff --git a/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.hpp b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.hpp new file mode 100644 index 000000000000..50003bf3a910 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDFEM_Parent_Edges.hpp @@ -0,0 +1,86 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_CDFEM_PARENT_EDGES_H_ +#define KRINO_INCLUDE_AKRI_CDFEM_PARENT_EDGES_H_ +#include +#include +#include + +namespace krino +{ +class CDFEM_Support; +class Phase_Support; +class InterfaceID; +class ParentsToChildMapper; + +typedef OrderedIdPair ParentEdgeKey; +typedef std::map ParentEdgeMap; + +std::set get_phases_present_on_edges(const std::vector & parentEdges); +std::set get_phases_present_on_edges_and_interior(const std::vector & elementParentEdges); +std::set get_interfaces_present(const std::vector & elementParentEdges); + +const CDFEM_Parent_Edge * find_parent_edge(const stk::mesh::BulkData & mesh, const ParentEdgeMap & parentEdges, stk::mesh::Entity node0, stk::mesh::Entity node1); + +std::function build_no_linearized_edges_function(); + +std::function build_all_linearized_edges_function(); + +ParentEdgeMap +build_parent_edges_using_elements(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper & parentsToChildMapper, + const std::function & should_build_linear_edge, + const std::vector & elements, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport); + +ParentEdgeMap +build_parent_edges(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper & parentsToChildMapper, + const std::function & should_build_linear_edge, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport); + +void fill_element_parent_edges(const stk::mesh::BulkData & mesh, + const stk::mesh::Entity elem, + const ParentEdgeMap & parentEdges, + std::vector & elementParentEdges, + std::vector & areParentEdgesAreOrientedSameAsElementEdges); + +void fill_face_nodes_and_parent_edges(const stk::topology & elementTopology, + const int iFace, + const std::vector & elementNodes, + const std::vector & elementParentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + std::vector & faceNodes, + std::vector & faceParentEdges, + std::vector & areParentEdgesOrientedSameAsFaceEdges); + +stk::mesh::Selector get_parent_element_selector(const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport); + +stk::mesh::Selector get_owned_parent_element_selector(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport); + +std::vector get_owned_parent_elements(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport); + +} + + + + +#endif /* KRINO_INCLUDE_AKRI_CDFEM_PARENT_EDGES_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_CDFEM_Snapper.hpp b/packages/krino/krino/krino_lib/Akri_CDFEM_Snapper.hpp new file mode 100644 index 000000000000..d63c5af7b6b9 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDFEM_Snapper.hpp @@ -0,0 +1,31 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_CDFEM_SNAPPER_H_ +#define KRINO_INCLUDE_AKRI_CDFEM_SNAPPER_H_ +#include + +namespace krino { + +class CDFEM_Snapper { +public: + CDFEM_Snapper() : my_cdfem_edge_tol(default_edge_tol) {} + + void set_edge_tolerance(double edge_tol) { my_cdfem_edge_tol = edge_tol; } + double get_edge_tolerance() const { ThrowRequireMsg(my_cdfem_edge_tol < 0.5, "Should not be requesting tolerance when always snapping."); return my_cdfem_edge_tol; } + bool always_snap() const { return my_cdfem_edge_tol > 0.5; } + bool snap_lo(double crossingVal) const { return always_snap() ? (crossingVal <= 0.5) : (crossingVal < get_edge_tolerance()); } + bool snap_hi(double crossingVal) const { return always_snap() ? (crossingVal > 0.5) : (crossingVal > 1.0-get_edge_tolerance()); } +private: + static constexpr double default_edge_tol = 1.e-3; + double my_cdfem_edge_tol; +}; + +} + +#endif /* KRINO_INCLUDE_AKRI_CDFEM_SNAPPER_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_CDFEM_Support.cpp b/packages/krino/krino/krino_lib/Akri_CDFEM_Support.cpp new file mode 100644 index 000000000000..55ed155e5b0e --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDFEM_Support.cpp @@ -0,0 +1,364 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include + +namespace krino{ + +Edge_Interpolation_Model CDFEM_Support::the_edge_interpolation_model = PIECEWISE_LINEAR; + +CDFEM_Support & +CDFEM_Support::get(stk::mesh::MetaData & meta) +{ + CDFEM_Support * support = const_cast(meta.get_attribute()); + if (NULL == support) + { + support = new CDFEM_Support(meta); + meta.declare_attribute_with_delete(support); + } + return *support; +} + +CDFEM_Support & +CDFEM_Support::get(const stk::mesh::MetaData & meta) +{ + CDFEM_Support * support = const_cast(meta.get_attribute()); + ThrowRequireMsg(nullptr != support, "Could not find CDFEM_Support attribute on MetaData."); + return *support; +} + +CDFEM_Support::CDFEM_Support(stk::mesh::MetaData & meta) + : my_meta(meta), + my_aux_meta(AuxMetaData::get(meta)), + my_simplex_generation_method(CUT_QUADS_BY_LARGEST_ANGLE), + my_fully_coupled_cdfem(false), + my_num_initial_decomposition_cycles(1), + myGlobalIDsAreParallelConsistent(true), + my_interface_minimum_refinement_level(0), + my_interface_maximum_refinement_level(0), + my_post_adapt_uniform_refinement_levels(0), + my_post_cdfem_refinement_levels(0), + my_nonconformal_adapt_target_element_count(0), + my_cdfem_edge_degeneracy_handling(SNAP_TO_NODE), + my_cdfem_snapper(), + my_cdfem_dof_edge_tol(0.0), + my_internal_face_stabilization_multiplier(0.0), + my_flag_use_hierarchical_dofs(false), + my_flag_constrain_CDFEM_to_XFEM_space(false), + my_flag_use_nonconformal_element_size(true), + my_timer_cdfem("CDFEM", sierra::Diag::sierraTimer()), + my_timer_adapt("Nonconformal Adapt", my_timer_cdfem) +{ + my_prolongation_model = ALE_NEAREST_POINT; + + if (3 == my_meta.spatial_dimension()) + my_simplex_generation_method = CUT_QUADS_BY_NEAREST_EDGE_CUT; + + create_parts(); +} + +void CDFEM_Support::create_parts() +{ + my_parent_part = &my_meta.declare_part("CDFEM_PARENT_CONTEXT_BIT"); + my_child_part = &my_meta.declare_part("CDFEM_CHILD_CONTEXT_BIT"); + my_internal_side_part = &my_meta.declare_part("CDFEM_INTERNAL_SIDE"); + + if (my_aux_meta.using_fmwk()) + { + const bool restartOnlyIOPart = true; + my_child_edge_node_part = &my_aux_meta.declare_io_part_with_topology("CDFEM_EDGE_NODE_2_PARENTS", stk::topology::NODE, restartOnlyIOPart); + } + else + { + // Currently no need to output nodeset for krino usage + my_child_edge_node_part = &my_meta.declare_part_with_topology("CDFEM_EDGE_NODE_2_PARENTS", stk::topology::NODE); + } +} + +void CDFEM_Support::register_cdfem_mesh_displacements_field() +{ + ThrowRequireMsg(my_meta.spatial_dimension() > 1, "Spatial dimension must be set and equal to 2 or 3."); + + const FieldType & vec_type = (my_meta.spatial_dimension() == 3) ? FieldType::VECTOR_3D : FieldType::VECTOR_2D; + FieldRef cdfem_disp_field = my_aux_meta.register_field(cdfem_mesh_displacements_field_name(), vec_type, stk::topology::NODE_RANK, 2, 1, get_universal_part()); + set_cdfem_displacement_field(cdfem_disp_field); +} + +void CDFEM_Support::register_parent_node_ids_field() +{ + FieldType id_field_type = (my_aux_meta.get_assert_32bit_flag()) ? FieldType::UNSIGNED_INTEGER : FieldType::UNSIGNED_INTEGER_64; + my_parent_node_ids_field = my_aux_meta.register_field("CDFEM_2_PARENT_NODE_IDS", + id_field_type, stk::topology::NODE_RANK, 1, + 2, *my_child_edge_node_part); +} + +stk::mesh::Selector +CDFEM_Support::get_post_cdfem_refinement_selector() const +{ + stk::mesh::Selector selector = my_aux_meta.active_part(); + if (!my_post_cdfem_refinement_blocks.empty()) + { + stk::mesh::PartVector blocks; + for (auto && block_name : my_post_cdfem_refinement_blocks) + { + if (my_aux_meta.has_part(block_name)) + { + stk::mesh::Part & block = my_aux_meta.get_part(block_name); + blocks.push_back(&block); + } + else + { + stk::RuntimeDoomedAdHoc() << "post_cdfem_refinement_block " << block_name << " not found.\n"; + } + } + selector &= stk::mesh::selectUnion(blocks); + } + return selector; +} + +void +CDFEM_Support::setup_fields() +{ + my_coords_field = LevelSet::get_current_coordinates(my_meta); + + Phase_Support::get(my_meta).check_phase_parts(); + + my_ls_fields.clear(); + const LevelSetManager & region_ls = LevelSetManager::get(my_meta); + for (auto&& ls : region_ls) + { + LS_Field calc_ls_field(ls->name(),ls->get_identifier(),ls->get_isovar_field(),ls->get_isoval(),ls.get()); + my_ls_fields.push_back(calc_ls_field); + } + + const unsigned num_ls = region_ls.numberLevelSets(); + my_death_specs.resize(num_ls, nullptr); + for (unsigned i=0; i(is); + my_ale_prolongation_fields.insert(field.field_state(state)); + } +} + +void +CDFEM_Support::add_interpolation_field(const FieldRef field) +{ + ThrowAssert(field.valid()); + ThrowRequireMsg(!is_ale_prolongation_field(field), "Cannot add " << field.name() << " as interpolation field because it is already an ALE prolongation field."); + for ( unsigned is = 0; is < field.number_of_states(); ++is ) + { + const stk::mesh::FieldState state = static_cast(is); + my_interpolation_fields.insert(field.field_state(state)); + } +} + +void +CDFEM_Support::finalize_fields() +{ + krinolog << "Finalizing field prolongation strategies for CDFEM." << stk::diag::push << stk::diag::dendl; + for ( auto && field_ptr : my_meta.get_fields() ) + { + const FieldRef field(field_ptr); + if( !field.type_is() || field.field_state(stk::mesh::StateNew) == my_cdfem_displacements_field ) continue; + + if( field.entity_rank()==stk::topology::ELEMENT_RANK && + !stk::equal_case(field.name(), "transition_element") && + !stk::equal_case(field.name(), "transition_element_3") && + !stk::equal_case(field.name(), "parent_element") ) + { + my_element_fields.insert(field); + } + + if( field.entity_rank()!=stk::topology::NODE_RANK ) continue; + + const auto& initial_prolong_field_name_entry = my_initial_prolongation_field_name_map.find(field.name()); + if (initial_prolong_field_name_entry != my_initial_prolongation_field_name_map.end()) + { + const std::string & src_field_name = initial_prolong_field_name_entry->second; + ThrowErrorMsgIf(!my_aux_meta.has_field(stk::topology::NODE_RANK, src_field_name), + "Error: Could not find initial prolongation field with name " << src_field_name); + + // If the src field does not have the desired state, use StateNone (which is the same as StateNew). + FieldRef src_field = my_aux_meta.get_field(stk::topology::NODE_RANK, src_field_name); + stk::mesh::FieldState src_state = (field.state() < src_field.number_of_states()) ? field.state() : stk::mesh::StateNone; + src_field = src_field.field_state(src_state); + + my_initial_prolongation_field_map[field] = src_field; + krinolog << "Added " + << src_field.name() << " (" << state_string(src_state) << ") as initial prolongation field for " + << field.name() << " (" << state_string(field.state()) << ")" << stk::diag::dendl; + } + + if (std::find(my_force_ale_prolongation_fields.begin(), + my_force_ale_prolongation_fields.end(), + field.name()) != my_force_ale_prolongation_fields.end()) + { + add_ale_prolongation_field(field); + } + + if(is_interpolation_field(field)) + { + krinolog << field.name() << " will use interpolation." << stk::diag::dendl; + continue; + } + else if(is_ale_prolongation_field(field)) + { + krinolog << field.name() << " will use ALE prolongation." << stk::diag::dendl; + continue; + } + else if (field.name() == "node_registry") + { + krinolog << field.name() << " will not be modified." << stk::diag::dendl; + continue; + } + + krinolog << field.name() << " will be zeroed." << stk::diag::dendl; + my_zeroed_fields.insert(field); + } + krinolog << stk::diag::pop << stk::diag::dendl; +} + +void CDFEM_Support::force_ale_prolongation_for_field(const std::string & field_name) +{ + my_force_ale_prolongation_fields.push_back(field_name); +} + +bool +CDFEM_Support::add_initial_prolongation_field(const std::string & dest_field_name, const std::string & src_field_name) +{ + if (my_initial_prolongation_field_name_map.find(dest_field_name) != my_initial_prolongation_field_name_map.end()) + { + return false; + } + my_initial_prolongation_field_name_map[dest_field_name] = src_field_name; + return true; +} + +FieldRef +CDFEM_Support::get_initial_prolongation_field(const FieldRef field) const +{ + auto iter = my_initial_prolongation_field_map.find(field); + if (iter == my_initial_prolongation_field_map.end()) + return FieldRef(); + return iter->second; +} + +void +CDFEM_Support::set_simplex_generation_method(const Simplex_Generation_Method & method) +{ + ThrowAssert(method < MAX_SIMPLEX_GENERATION_METHOD); + ThrowRequireMsg(3 == my_meta.spatial_dimension() || method != CUT_QUADS_BY_NEAREST_EDGE_CUT, "Simplex generation method CUT_QUADS_BY_NEAREST_EDGE_CUT only supported in 3d."); + my_simplex_generation_method = method; +} + +void +CDFEM_Support::activate_interface_refinement(int minimumLevel, int maximumLevel) +{ + /* %TRACE% */ Traceback trace__("krino::CDFEM_Support::activate_interface_refinement(int minimum_level, int maximum_level)"); /* %TRACE% */ + + ThrowRequireMsg(my_interface_minimum_refinement_level == 0 && my_interface_maximum_refinement_level == 0, + "Interface refinement levels should only be specified once."); + ThrowRequireMsg(maximumLevel >= minimumLevel || maximumLevel == 0, + "Maximum interface refinement level must be greater than or equal to the minimum interface refinement level or left unspecified."); + if (maximumLevel == 0) maximumLevel = minimumLevel; + + my_interface_minimum_refinement_level = minimumLevel; + my_interface_maximum_refinement_level = maximumLevel; + + setup_refinement_marker(); + + if (maximumLevel > 0) + set_global_ids_are_NOT_parallel_consistent(); +} + +void +CDFEM_Support::activate_nonconformal_adaptivity(const int numLevels) +{ + /* %TRACE% */ Traceback trace__("krino::CDFEM_Support::activate_nonconformal_adaptivity(const int num_levels)"); /* %TRACE% */ + + if (numLevels < my_interface_maximum_refinement_level) + { + krinolog << "Ignoring request to activate " << numLevels << " of CDFEM nonconformal adaptivity because a maximum of " << my_interface_maximum_refinement_level << " have already been activated." << stk::diag::dendl; + return; + } + + my_interface_minimum_refinement_level = numLevels; + my_interface_maximum_refinement_level = numLevels; + + setup_refinement_marker(); + + if (numLevels > 0) + set_global_ids_are_NOT_parallel_consistent(); +} + +void +CDFEM_Support::setup_refinement_marker() +{ + /* %TRACE% */ Traceback trace__("krino::CDFEM_Support::activate_nonconformal_adaptivity(const int num_levels)"); /* %TRACE% */ + + my_nonconformal_adapt_marker_name = "CDFEM_NONCONFORMAL_MARKER"; + + my_aux_meta.register_field(my_nonconformal_adapt_marker_name, FieldType::INTEGER, stk::topology::ELEMENT_RANK, 1, 1, get_universal_part()); + my_aux_meta.register_field(my_nonconformal_adapt_marker_name, FieldType::INTEGER, stk::topology::NODE_RANK, 1, 1, get_universal_part()); +} + +int +CDFEM_Support::get_ls_index(const LevelSet * ls) const +{ + for ( unsigned ls_index = 0; ls_index < my_ls_fields.size(); ++ls_index ) + { + if (ls == ls_field(ls_index).ptr) return ls_index; + } + ThrowRuntimeError("Invalid level set field index"); +} + +void +CDFEM_Support::activate_nonconformal_adapt_target_count(const uint64_t target_count) +{ + /* %TRACE% */ Traceback trace__("CDFEM_Support::activate_nonconformal_adapt_target_count(const uint64_t target_count)"); /* %TRACE% */ + + my_nonconformal_adapt_target_element_count = target_count; + my_nonconformal_adapt_indicator_name = "CDFEM_ADAPTIVITY_ERROR_INDICATOR"; + + my_aux_meta.register_field(my_nonconformal_adapt_indicator_name, + FieldType::REAL, + stk::topology::ELEMENT_RANK, + 1, + 1, + get_universal_part()); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_CDFEM_Support.hpp b/packages/krino/krino/krino_lib/Akri_CDFEM_Support.hpp new file mode 100644 index 000000000000..3d07bd11f63a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDFEM_Support.hpp @@ -0,0 +1,259 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_CDFEM_Support_h +#define Akri_CDFEM_Support_h +// +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace krino { + +class LevelSet; +class CDFEM_Inequality_Spec; + +struct LS_Field +{ + LS_Field(const std::string & name_, const LevelSet_Identifier & identifier_, const FieldRef isovar_, const double isoval_, const LevelSet * const ptr_) + : name(name_), identifier(identifier_), isovar(isovar_), isoval(isoval_), ptr(ptr_) { + ThrowRequireMsg(isovar_.valid(), "Invalid field " + isovar_.name() + " used in CDFEM initialization"); + } + + // Constructor just for unit tests + LS_Field(const std::string & name_, const LevelSet_Identifier & identifier_) + : name(name_), identifier(identifier_), isoval(0), ptr(NULL) { + } + + std::string name; + LevelSet_Identifier identifier; + FieldRef isovar; + double isoval; + const LevelSet * ptr; +}; + +enum Prolongation_Model +{ + ALE_NEAREST_NODE=0, + ALE_NEAREST_POINT, + INTERPOLATION, + MAX_PROLONGATION_MODEL +}; + +enum Edge_Interpolation_Model +{ + PIECEWISE_LINEAR=0, + CONSTRAINED_LINEAR, + MAX_EDGE_INTERPOLATION_MODEL +}; + +enum Edge_Degeneracy_Handling +{ + SNAP_TO_NODE=0, + SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE, + MAX_EDGE_DEGENERACY_HANDLING_TYPE +}; + +enum Simplex_Generation_Method +{ + CUT_QUADS_BY_GLOBAL_IDENTIFIER=0, + CUT_QUADS_BY_LARGEST_ANGLE, + CUT_QUADS_BY_NEAREST_EDGE_CUT, + MAX_SIMPLEX_GENERATION_METHOD +}; + +class CDFEM_Support { +public: + + CDFEM_Support(stk::mesh::MetaData & meta); + ~CDFEM_Support() {} + CDFEM_Support(CDFEM_Support const&) = delete; + CDFEM_Support& operator=(CDFEM_Support const&) = delete; + + static CDFEM_Support & get(stk::mesh::MetaData & meta); + static CDFEM_Support & get(const stk::mesh::MetaData & meta); + static void use_constrained_edge_interpolation() { the_edge_interpolation_model = CONSTRAINED_LINEAR; } + static Edge_Interpolation_Model get_edge_interpolation_model() { return the_edge_interpolation_model; } + static bool use_nonconformal_adaptivity(stk::mesh::MetaData & meta) { CDFEM_Support & cdfem_support = get(meta); return cdfem_support.get_interface_maximum_refinement_level() > 0; } + static std::string cdfem_mesh_displacements_field_name() { return "CDFEM_MESH_DISPLACEMENTS"; } + + static bool is_active(const stk::mesh::MetaData & meta) + { + return Phase_Support::exists_and_has_phases_defined(meta); + } + + stk::mesh::MetaData & get_mesh_meta() { return my_meta; } + const stk::mesh::MetaData & get_mesh_meta() const { return my_meta; } + int num_ls_fields() const { return my_ls_fields.size(); } + const LS_Field & ls_field(int i) const { return my_ls_fields[i]; } + LS_Field & ls_field(int i) { return my_ls_fields[i]; } + const std::vector & ls_fields() const { return my_ls_fields; } + Prolongation_Model get_prolongation_model() const { return my_prolongation_model; } + void set_prolongation_model(const Prolongation_Model & model) { my_prolongation_model = model; } + Simplex_Generation_Method get_simplex_generation_method() const { return my_simplex_generation_method; } + void set_simplex_generation_method(const Simplex_Generation_Method & method); + bool get_global_ids_are_parallel_consistent() const { return myGlobalIDsAreParallelConsistent; } + void set_global_ids_are_NOT_parallel_consistent() { myGlobalIDsAreParallelConsistent = false; } + const CDFEM_Inequality_Spec * get_death_spec(int ls_index) const { return my_death_specs[ls_index]; } + void activate_interface_refinement(int minimum_level, int maximum_level); + void activate_nonconformal_adaptivity(const int num_levels); + int get_ls_index(const LevelSet * ls) const; + + void create_parts(); + + void register_cdfem_mesh_displacements_field(); + void register_parent_node_ids_field(); + + void setup_fields(); + void finalize_fields(); + void set_cdfem_displacement_field(const FieldRef field) { my_cdfem_displacements_field = field; } + void set_cdfem_snap_displacement_field(const FieldRef field) { myCDFEMSnapDisplacementsField = field; } + void add_ale_prolongation_field(const FieldRef field); + void add_interpolation_field(const FieldRef field); + // Just for unit test setup purposes + void add_ls_field(const LS_Field & to_add, const CDFEM_Inequality_Spec * death_spec = nullptr) { my_ls_fields.push_back(to_add); my_death_specs.push_back(death_spec); } + + void set_coords_field(const FieldRef coords_field) { my_coords_field = coords_field; } + const FieldRef get_coords_field() const { return my_coords_field; } + const FieldRef get_cdfem_displacements_field() { return my_cdfem_displacements_field; } + const FieldRef get_cdfem_snap_displacements_field() const { return myCDFEMSnapDisplacementsField; } + const FieldSet & get_ale_prolongation_fields() const { return my_ale_prolongation_fields; } + const FieldSet & get_interpolation_fields() const { return my_interpolation_fields; } + const FieldSet & get_zeroed_fields() const { return my_zeroed_fields; } + const FieldSet & get_element_fields() const { return my_element_fields; } + + bool add_initial_prolongation_field(const std::string & dest_field_name, const std::string & src_field_name); + FieldRef get_initial_prolongation_field(const FieldRef field) const; + + stk::mesh::Part & get_parent_part() const { return *my_parent_part; } + stk::mesh::Part & get_child_part() const { return *my_child_part; } + stk::mesh::Part & get_internal_side_part() const { return *my_internal_side_part; } + stk::mesh::Part & get_active_part() const { return my_aux_meta.active_part(); } + stk::mesh::Part & get_universal_part() const { return my_meta.universal_part(); } + stk::mesh::Part & get_locally_owned_part() const { return my_meta.locally_owned_part(); } + stk::mesh::Part & get_globally_shared_part() const { return my_meta.globally_shared_part(); } + + stk::mesh::Selector get_post_cdfem_refinement_selector() const; + + stk::mesh::Part & get_child_edge_node_part() const { return *my_child_edge_node_part; } + FieldRef get_parent_node_ids_field() const { return my_parent_node_ids_field; } + + void activate_fully_coupled_cdfem() { my_fully_coupled_cdfem = true; } + bool fully_coupled_cdfem() const { return my_fully_coupled_cdfem; } + + void activate_nonconformal_adapt_target_count(uint64_t val); + uint64_t get_nonconformal_adapt_target_count() const { return my_nonconformal_adapt_target_element_count; } + int get_interface_minimum_refinement_level() const { return my_interface_minimum_refinement_level; } + int get_interface_maximum_refinement_level() const { return my_interface_maximum_refinement_level; } + void set_post_adapt_refinement_levels(int levels) { my_post_adapt_uniform_refinement_levels = levels; } + int get_post_adapt_refinement_levels() const { return my_post_adapt_uniform_refinement_levels; } + void set_post_cdfem_refinement_levels(int levels) { my_post_cdfem_refinement_levels = levels; } + int get_post_cdfem_refinement_levels() const { return my_post_cdfem_refinement_levels; } + void set_post_cdfem_refinement_blocks(const std::vector & post_cdfem_refinement_blocks) { my_post_cdfem_refinement_blocks = post_cdfem_refinement_blocks; } + void set_num_initial_decomposition_cycles(int num_initial_decomposition_cycles) { my_num_initial_decomposition_cycles = num_initial_decomposition_cycles; } + // In case of both nonconformal adaptivity, perform at least 2 rounds of level set initialization and decomposition. + // This is to capture features that might be missed on the unrefined mesh. + int get_num_initial_decomposition_cycles() const { return (my_num_initial_decomposition_cycles > 1) ? my_num_initial_decomposition_cycles : ((my_interface_maximum_refinement_level > 0) ? 2 : 1); } + const std::string & get_nonconformal_adapt_marker_name() const { return my_nonconformal_adapt_marker_name; } + const std::string & get_nonconformal_adapt_indicator_name() const { return my_nonconformal_adapt_indicator_name; } + void set_nonconformal_hadapt(const std::function & hadapt) { my_nonconformal_hadapt = hadapt; } + const std::function & get_nonconformal_hadapt() const { return my_nonconformal_hadapt; } + + bool is_ale_prolongation_field(const FieldRef field) const + { + return (my_ale_prolongation_fields.find(field) != my_ale_prolongation_fields.end()); + } + bool is_interpolation_field(const FieldRef field) const + { + return (my_interpolation_fields.find(field) != my_interpolation_fields.end()); + } + + void use_nonconformal_element_size(bool flag) { my_flag_use_nonconformal_element_size = flag; } + bool use_nonconformal_element_size() const { return my_flag_use_nonconformal_element_size; } + + Edge_Degeneracy_Handling get_cdfem_edge_degeneracy_handling() const { return my_cdfem_edge_degeneracy_handling; } + void set_cdfem_edge_degeneracy_handling( const Edge_Degeneracy_Handling type ) { my_cdfem_edge_degeneracy_handling = type; } + + stk::diag::Timer & get_timer_cdfem() const { return my_timer_cdfem; } + stk::diag::Timer & get_timer_adapt() const { return my_timer_adapt; } + + void set_cdfem_edge_tol( const double tol ) { my_cdfem_snapper.set_edge_tolerance(tol); } + const CDFEM_Snapper & get_snapper() const { return my_cdfem_snapper; } + const double & get_cdfem_dof_edge_tol() const { return my_cdfem_dof_edge_tol; } + void set_cdfem_dof_edge_tol( const double tol ) { my_cdfem_dof_edge_tol = tol; } + bool use_internal_face_stabilization() const { return my_internal_face_stabilization_multiplier > 0.0; } + double get_internal_face_stabilization_multiplier() const { return my_internal_face_stabilization_multiplier; } + void set_internal_face_stabilization_multiplier( const double mult ) { my_internal_face_stabilization_multiplier = mult; } + bool get_use_hierarchical_dofs() const { return my_flag_use_hierarchical_dofs; } + void set_use_hierarchical_dofs(bool flag) { my_flag_use_hierarchical_dofs = flag; } + bool get_constrain_CDFEM_to_XFEM_space() const { return my_flag_constrain_CDFEM_to_XFEM_space; } + void set_constrain_CDFEM_to_XFEM_space(bool flag) { my_flag_constrain_CDFEM_to_XFEM_space = flag; } + + void force_ale_prolongation_for_field(const std::string & field_name); + +private: + void setup_refinement_marker(); + +private: + stk::mesh::MetaData & my_meta; + AuxMetaData & my_aux_meta; + + FieldRef my_coords_field; + std::vector my_ls_fields; + std::vector my_death_specs; + FieldRef my_cdfem_displacements_field; + FieldRef myCDFEMSnapDisplacementsField; + Prolongation_Model my_prolongation_model; + Simplex_Generation_Method my_simplex_generation_method; + std::vector my_force_ale_prolongation_fields; + FieldSet my_ale_prolongation_fields; + FieldSet my_interpolation_fields; + FieldSet my_zeroed_fields; + FieldSet my_element_fields; + std::map my_initial_prolongation_field_name_map; + std::map my_initial_prolongation_field_map; + static Edge_Interpolation_Model the_edge_interpolation_model; + stk::mesh::Part * my_parent_part; + stk::mesh::Part * my_child_part; + stk::mesh::Part * my_internal_side_part; + stk::mesh::Part * my_child_edge_node_part; + FieldRef my_parent_node_ids_field; + bool my_fully_coupled_cdfem; + int my_num_initial_decomposition_cycles; + bool myGlobalIDsAreParallelConsistent; + int my_interface_minimum_refinement_level; + int my_interface_maximum_refinement_level; + int my_post_adapt_uniform_refinement_levels; + int my_post_cdfem_refinement_levels; + std::vector my_post_cdfem_refinement_blocks; + uint64_t my_nonconformal_adapt_target_element_count; + std::string my_nonconformal_adapt_marker_name; + std::string my_nonconformal_adapt_indicator_name; + std::function my_nonconformal_hadapt; + Edge_Degeneracy_Handling my_cdfem_edge_degeneracy_handling; + CDFEM_Snapper my_cdfem_snapper; + double my_cdfem_dof_edge_tol; + double my_internal_face_stabilization_multiplier; + bool my_flag_use_hierarchical_dofs; + bool my_flag_constrain_CDFEM_to_XFEM_space; + bool my_flag_use_nonconformal_element_size; + mutable stk::diag::Timer my_timer_cdfem; + mutable stk::diag::Timer my_timer_adapt; +}; + +} // namespace krino + +#endif // Akri_CDFEM_Support_h diff --git a/packages/krino/krino/krino_lib/Akri_CDMesh.cpp b/packages/krino/krino/krino_lib/Akri_CDMesh.cpp new file mode 100644 index 000000000000..71b66be817d4 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDMesh.cpp @@ -0,0 +1,3689 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // Needed for all_reduce_max +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace krino{ + +std::shared_ptr CDMesh::the_new_mesh; + +//-------------------------------------------------------------------------------- + +CDMesh::CDMesh( stk::mesh::BulkData & mesh, const std::shared_ptr & old_mesh ) + : my_meta(mesh.mesh_meta_data()), + my_aux_meta(AuxMetaData::get(my_meta)), + my_entity_id_pool(my_meta), + my_spatial_dim( my_meta.spatial_dimension() ), + my_cdfem_support(CDFEM_Support::get(my_meta)), + my_phase_support(Phase_Support::get(my_meta)), + my_old_mesh(old_mesh), + my_stash_step_count(-1), + my_missing_remote_prolong_facets(false), + my_timer_decompose("Decompose", my_cdfem_support.get_timer_cdfem()), + my_timer_decomposition_has_changed("Need CDFEM", my_cdfem_support.get_timer_cdfem()), + my_timer_snap("Snapping", my_timer_decompose), + my_timer_stash_field_data("Stash Field Data", my_timer_decompose), + my_timer_modify_mesh("Modify Mesh", my_cdfem_support.get_timer_cdfem()), + my_timer_prolongation("Prolongation", my_cdfem_support.get_timer_cdfem()), + my_timer_compute_CFL("Compute CFL", my_cdfem_support.get_timer_cdfem()) +{ /* %TRACE[ON]% */ Trace trace__("CDMesh::CDMesh( stk::mesh::BulkData & mesh )"); /* %TRACE% */ + + stk::mesh::insert(my_attribute_parts, my_aux_meta.active_part()); + stk::mesh::insert(my_attribute_parts, my_aux_meta.exposed_boundary_part()); + stk::mesh::insert(my_attribute_parts, my_aux_meta.block_boundary_part()); + + if(my_old_mesh) + { + my_old_mesh->my_old_mesh.reset(); + } +} + +CDMesh::~CDMesh() +{ + clear(); +} + +SubElementNode * CDMesh::add_managed_node(std::unique_ptr node) +{ + nodes.emplace_back(std::move(node)); + return nodes.back().get(); +} + +//-------------------------------------------------------------------------------- + + +stk::mesh::Part & CDMesh::get_locally_owned_part() const { return my_meta.locally_owned_part(); } +stk::mesh::Part & CDMesh::get_globally_shared_part() const { return my_meta.globally_shared_part(); } +stk::mesh::Part & CDMesh::get_active_part() const { return my_aux_meta.active_part(); } +stk::mesh::Part & CDMesh::get_block_boundary_part() const { return my_aux_meta.block_boundary_part(); } + +//-------------------------------------------------------------------------------- + +void CDMesh::add_periodic_node_pair(stk::mesh::Entity node1, stk::mesh::Entity node2) +{ + my_periodic_node_id_map[stk_bulk().identifier(node1)].push_back(stk_bulk().identifier(node2)); + my_periodic_node_id_map[stk_bulk().identifier(node2)].push_back(stk_bulk().identifier(node1)); +} + +const std::vector & CDMesh::all_interface_ids() const +{ + if(crossing_keys.empty()) + { + const int num_ls = num_ls_fields(); + if(num_ls < 2 || !Phase_Support::has_one_levelset_per_phase()) + { + crossing_keys.resize(num_ls_fields()); + for(int i=0; i < num_ls_fields(); ++i) + { + crossing_keys[i] = InterfaceID(i,i); + } + } + else + { + for(int i=0; i < num_ls_fields(); ++i) + { + for(int j=i+1; j < num_ls_fields(); ++j) + { + crossing_keys.push_back(InterfaceID(i,j)); + } + } + } + } + return crossing_keys; +} + +std::vector CDMesh::active_interface_ids() const +{ + const std::vector all_interfaces = all_interface_ids(); + if (all_interfaces.size() == 1) return all_interfaces; + + std::vector id_is_active_locally(all_interfaces.size(), false); + for (const auto & elem : elements) + { + for (auto && elemInterface : elem->get_sorted_cutting_interfaces()) + { + const auto lower = std::lower_bound(all_interfaces.begin(), all_interfaces.end(), elemInterface); + ThrowAssert(*lower == elemInterface); + id_is_active_locally[std::distance(all_interfaces.begin(), lower)] = true; + } + } + + std::vector id_is_active_globally(all_interfaces.size()); + stk::all_reduce_sum(stk_bulk().parallel(), id_is_active_locally.data(), id_is_active_globally.data(), id_is_active_locally.size()); + + std::vector active_ids; + for (size_t id=0; id & oldMesh = the_new_mesh->my_old_mesh; + const bool noSuccessfulDecompositionSinceLastFailedStep = nullptr == oldMesh; + const bool lastStepFailed = nullptr != oldMesh && oldMesh->my_stash_step_count == step_count; + const bool restoreMesh = lastStepFailed || + noSuccessfulDecompositionSinceLastFailedStep; // Even though mesh has already been restored, another process might have modified it (ie rayTracer) + if (restoreMesh) + { + the_new_mesh = std::make_shared(mesh, std::shared_ptr()); + the_new_mesh->generate_nonconformal_elements(); + the_new_mesh->restore_subelement_edge_nodes(); + the_new_mesh->restore_subelements(); + } + } +} + +void CDMesh::build_and_stash_old_mesh(const int stepCount) +{ + std::shared_ptr & old_mesh = my_old_mesh; + if (!old_mesh) + { + old_mesh = std::make_shared(stk_bulk(), std::shared_ptr()); + old_mesh->generate_nonconformal_elements(); + old_mesh->stash_field_data(-1, *this); + } + else + { + if (my_cdfem_support.get_interface_maximum_refinement_level() > 0) + { + old_mesh = std::make_shared(stk_bulk(), std::shared_ptr()); + old_mesh->rebuild_child_part(); + old_mesh->rebuild_parent_and_active_parts_using_nonconformal_and_child_parts(); + old_mesh->generate_nonconformal_elements(); + old_mesh->restore_subelement_edge_nodes(); + old_mesh->restore_subelements(); + } + + old_mesh->stash_field_data(stepCount, *this); + } +} + +static FieldSet get_snap_fields(const CDFEM_Support & cdfemSupport) +{ + FieldSet snapFields = cdfemSupport.get_interpolation_fields(); + + FieldRef cdfemSnapField = cdfemSupport.get_cdfem_snap_displacements_field(); + if (cdfemSnapField.valid()) + { + for ( unsigned is = 0; is < cdfemSnapField.number_of_states(); ++is ) + { + const stk::mesh::FieldState state = static_cast(is); + snapFields.erase(cdfemSnapField.field_state(state)); + } + + for (auto && field : cdfemSupport.ls_fields()) + { + const FieldRef lsField = field.isovar.field(); + for ( unsigned is = 0; is < lsField.number_of_states(); ++is ) + { + const stk::mesh::FieldState state = static_cast(is); + if (state != stk::mesh::StateNew) + snapFields.erase(lsField.field_state(state)); + } + } + } + return snapFields; +} + +static void interpolate_nodal_field(const FieldRef field, + stk::mesh::Entity node, + const std::vector & interpNodes, + const std::vector & interpWeights) +{ + const unsigned fieldLength = field.length(); + + double * val = field_data(field, node); + if (nullptr == val) return; + + for (unsigned i=0; i(field, interpNodes[iNode]); + ThrowRequire(nullptr != nodeVal); + + for (unsigned i=0; i & parentNodes, + std::vector & parentWeights) +{ + std::map nodeStencil; + node.build_stencil(nodeStencil); + + parentNodes.clear(); + parentWeights.clear(); + for (auto && entry : nodeStencil) + { + parentNodes.push_back(entry.first->entity()); + parentWeights.push_back(entry.second); + } +} + +static bool any_node_was_snapped(const std::vector & nodes, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + for (auto && node : nodes) + { + const auto iter = nodesToCapturedDomains.find(node); + if (iter != nodesToCapturedDomains.end() && !iter->second.empty()) + return true; + } + return false; +} + +static void apply_snapping_to_children_of_snapped_nodes(const CDFEM_Support & cdfemSupport, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + const krino::SubElementNode & node, + std::vector & parentNodes, + std::vector & parentWeights) +{ + fill_parent_nodes_and_weights(node, parentNodes, parentWeights); + if (any_node_was_snapped(parentNodes, nodesToCapturedDomains)) + { + for (auto && field : cdfemSupport.get_interpolation_fields()) + interpolate_nodal_field(field, node.entity(), parentNodes, parentWeights); + } +} + +void CDMesh::snap_and_update_fields_and_captured_domains(const InterfaceGeometry & interfaceGeometry, + NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + const FieldSet snapFields = get_snap_fields(my_cdfem_support); + + FieldRef cdfemSnapField = my_cdfem_support.get_cdfem_snap_displacements_field(); + if (cdfemSnapField.valid()) + stk::mesh::field_copy(my_cdfem_support.get_coords_field(), cdfemSnapField); + + const stk::mesh::Selector parentElementSelector = get_parent_element_selector(get_active_part(), my_cdfem_support, my_phase_support); + nodesToCapturedDomains = snap_as_much_as_possible_while_maintaining_quality(stk_bulk(), parentElementSelector, snapFields, interfaceGeometry, my_cdfem_support.get_global_ids_are_parallel_consistent()); + + if (cdfemSnapField.valid()) + stk::mesh::field_axpby(+1.0, my_cdfem_support.get_coords_field(), -1.0, cdfemSnapField); + + std::vector parentNodes; + std::vector parentWeights; + + if (cdfemSnapField.valid() && my_old_mesh) + for (auto && node : my_old_mesh->nodes) + if (!node->is_mesh_node()) + apply_snapping_to_children_of_snapped_nodes(my_cdfem_support, nodesToCapturedDomains, *node, parentNodes, parentWeights); +} + +int +CDMesh::decompose_mesh(stk::mesh::BulkData & mesh, + const InterfaceGeometry & interfaceGeometry, + const int step_count, + const std::vector> & periodic_node_pairs) +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::decompose_mesh()"); /* %TRACE% */ + stk::diag::TimeBlock root_timer__(CDFEM_Support::get(mesh.mesh_meta_data()).get_timer_cdfem()); + + CDFEM_Support & cdfemSupport = CDFEM_Support::get(mesh.mesh_meta_data()); + + if (!the_new_mesh) + { + // FIXME: This can cause problems for shells. + attach_sides_to_elements(mesh); + } + + + krinolog << "Decomposing mesh for region into phase conformal elements." << stk::diag::dendl; + NodeToCapturedDomainsMap nodesToCapturedDomains; + + { + the_new_mesh = std::make_shared(mesh, the_new_mesh); + + for(auto && pair : periodic_node_pairs) + { + the_new_mesh->add_periodic_node_pair(pair.first, pair.second); + } + + // Not sure if this is krino's responsibility or the driving application. If we have + // elemental death fields, these need to be parallel consistent on aura elements. + the_new_mesh->parallel_communicate_elemental_death_fields(); + + stk::diag::TimeBlock timer__(the_new_mesh->my_timer_decompose); + + if (cdfemSupport.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + the_new_mesh->snap_and_update_fields_and_captured_domains(interfaceGeometry, nodesToCapturedDomains); + + interfaceGeometry.prepare_to_process_elements(the_new_mesh->stk_bulk(), nodesToCapturedDomains); + } + + { + stk::diag::TimeBlock timer__(the_new_mesh->my_timer_decompose); + + the_new_mesh->generate_nonconformal_elements(); + if (cdfemSupport.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + the_new_mesh->snap_nearby_intersections_to_nodes(interfaceGeometry, nodesToCapturedDomains); + the_new_mesh->set_phase_of_uncut_elements(interfaceGeometry); + the_new_mesh->triangulate(interfaceGeometry); + the_new_mesh->decompose(); + } + + the_new_mesh->build_and_stash_old_mesh(step_count); + + const bool mesh_modified = the_new_mesh->modify_mesh(); + + the_new_mesh->prolongation(); + + // debugging + if ( krinolog.shouldPrint(LOG_DEBUG) ) + { + the_new_mesh->debug_output(); + } + + { + const ScaledJacobianQualityMetric qualityMetric; + krinolog << "After cutting quality is " << determine_quality(mesh, the_new_mesh->get_active_part(), qualityMetric) << stk::diag::dendl; + } + + + if (!the_new_mesh->aux_meta().using_fmwk()) + { + the_new_mesh->print_conformal_volumes_and_surface_areas(); + } + + const int status = mesh_modified ? (COORDINATES_MAY_BE_MODIFIED | MESH_MODIFIED) : COORDINATES_MAY_BE_MODIFIED; + return status; +} + +bool +CDMesh::modify_mesh() +{/* %TRACE[ON]% */ Trace trace__("krino::Mesh::modify_mesh()"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timer_modify_mesh); + + ParallelThrowAssert(stk_bulk().parallel(), check_face_and_edge_ownership(stk_bulk())); + ParallelThrowAssert(stk_bulk().parallel(), check_face_and_edge_relations(stk_bulk())); + + set_entities_for_identical_nodes(); + const bool all_elems_are_set_and_correct = set_entities_for_existing_child_elements(); + + std::vector< stk::mesh::Entity> unused_old_child_elems; + get_unused_old_child_elements(unused_old_child_elems); + + const bool modificationIsNeeded = (my_cdfem_support.get_interface_maximum_refinement_level() > 0) || stk::is_true_on_any_proc(stk_bulk().parallel(), !all_elems_are_set_and_correct || !unused_old_child_elems.empty()); + + if (modificationIsNeeded) + { + stk::mesh::toggle_sideset_updaters(stk_bulk(), false); + + stk_bulk().modification_begin(); + create_node_entities(); + std::vector side_requests; + create_element_and_side_entities(side_requests); + destroy_custom_ghostings(stk_bulk()); + delete_mesh_entities(stk_bulk(), unused_old_child_elems); + stk_bulk().modification_end(); + ParallelThrowAssert(stk_bulk().parallel(), check_shared_entity_nodes(stk_bulk())); + + add_possible_interface_sides(side_requests); + batch_create_sides(stk_bulk(), side_requests); + + stk::mesh::toggle_sideset_updaters(stk_bulk(), true); + stk_bulk().modification_begin(); + update_node_activation(stk_bulk(), aux_meta().active_part()); // we should be able to skip this step if there are no higher order elements + update_element_side_parts(); + stk_bulk().modification_end(); + + ParallelThrowAssert(stk_bulk().parallel(), check_element_side_connectivity(stk_bulk(), aux_meta().exposed_boundary_part(), aux_meta().active_part())); + ParallelThrowAssert(stk_bulk().parallel(), check_element_side_parts()); + + aux_meta().induce_topology_nodesets(aux_meta().active_locally_owned_selector()); + + ParallelThrowAssert(stk_bulk().parallel(), check_induced_parts(stk_bulk())); + } + + return modificationIsNeeded; +} + +void +CDMesh::set_entities_for_identical_nodes() +{ + CDMesh* old_mesh = get_old_mesh(); + if (nullptr == old_mesh) return; + + for (auto && node : nodes) + { + if (!node->entity_is_valid(stk_bulk())) + { + const SubElementNode * old_node = node->find_node_with_common_ancestry(*old_mesh); + if (nullptr != old_node) + { + stk::mesh::Entity old_node_entity = old_node->entity(); + if (stk_bulk().is_valid(old_node_entity)) + { + node->set_entity(stk_bulk(), old_node_entity); + } + } + } + } +} + +void +CDMesh::parallel_communicate_elemental_death_fields() const +{ + std::vector< const stk::mesh::FieldBase *> element_fields; + for (auto && field : ls_fields()) + { + if (field.isovar.entity_rank() == stk::topology::ELEMENT_RANK) + { + element_fields.push_back(&field.isovar.field()); + } + } + stk::mesh::communicate_field_data(stk_bulk(), element_fields); +} + +bool +CDMesh::set_entities_for_existing_child_elements() +{ + CDMesh* old_mesh = get_old_mesh(); + if (nullptr == old_mesh) return false; + + std::vector subelem_node_entities; + std::vector existing_elems; + + bool all_element_entities_are_set_and_correct = true; + for (const auto & elem : elements) + { + if (elem->have_subelements()) + { + std::vector conformal_subelems; + elem->get_subelements(conformal_subelems); + + for (auto && subelem : conformal_subelems) + { + // If all nodes are set, look for existing element using the nodes. + subelem_node_entities.clear(); + for (auto&& subelem_node : subelem->get_nodes()) + { + stk::mesh::Entity node = subelem_node->entity(); + if (stk_bulk().is_valid(node)) subelem_node_entities.push_back(node); + } + existing_elems.clear(); + if (subelem_node_entities.size() == subelem->get_nodes().size()) + { + stk::mesh::get_entities_through_relations(stk_bulk(), subelem_node_entities, stk::topology::ELEMENT_RANK, existing_elems); + ThrowAssert(existing_elems.size() <= 1); + } + + if (existing_elems.empty()) + { + all_element_entities_are_set_and_correct = false; + } + else + { + subelem->set_entity(stk_bulk(), existing_elems[0]); + ThrowAssert(subelem->check_entity_nodes(stk_bulk())); + if (all_element_entities_are_set_and_correct && elem_io_part_changed(*subelem)) all_element_entities_are_set_and_correct = false; + } + } + } + else + { + if (all_element_entities_are_set_and_correct && elem_io_part_changed(*elem)) all_element_entities_are_set_and_correct = false; + } + } + return all_element_entities_are_set_and_correct; +} + +void +CDMesh::get_unused_old_child_elements(std::vector & unused_old_child_elems) +{ + stk::mesh::Selector selector = get_child_part(); + std::vector old_child_elems; + stk::mesh::get_selected_entities( selector, stk_bulk().buckets( stk::topology::ELEMENT_RANK ), old_child_elems ); + + unused_old_child_elems.clear(); + unused_old_child_elems.reserve(old_child_elems.size()); + + for (auto&& old_child_elem : old_child_elems) + { + const SubElement * subelem = find_child_element(old_child_elem); + if (subelem == nullptr) + { + unused_old_child_elems.push_back(old_child_elem); + } + } + child_elements.clear(); // reset child element vector +} + +bool +CDMesh::decomposition_needs_update(const InterfaceGeometry & interfaceGeometry, + const std::vector> & periodic_node_pairs) +{ + return !the_new_mesh || the_new_mesh->decomposition_has_changed(interfaceGeometry); +} + +void +CDMesh::mark_interface_elements_for_adaptivity(stk::mesh::BulkData & mesh, const InterfaceGeometry & interfaceGeometry, const std::string & marker_field_name, const int num_refinements) +{/* %TRACE[SPEC]% */ Tracespec trace__("CDMesh::mark_interface_elements_for_adaptivity(stk::mesh::BulkData & mesh, const std::string & marker_field_name, const int num_refinements)"); /* %TRACE% */ + + CDMesh cdmesh(mesh, std::shared_ptr()); + krino::mark_interface_elements_for_adaptivity(cdmesh.stk_bulk(), interfaceGeometry, cdmesh.active_interface_ids(), cdmesh.get_snapper(), cdmesh.aux_meta(), cdmesh.get_cdfem_support(), cdmesh.get_coords_field(), marker_field_name, num_refinements); +} + +void +CDMesh::nonconformal_adaptivity(stk::mesh::BulkData & mesh, const InterfaceGeometry & interfaceGeometry) +{/* %TRACE[SPEC]% */ Tracespec trace__("CDMesh::nonconformal_adaptivity(stk::mesh::BulkData & mesh)"); /* %TRACE% */ + + const auto & cdfem_support = CDFEM_Support::get(mesh.mesh_meta_data()); + stk::diag::TimeBlock timer__(cdfem_support.get_timer_adapt()); + + const std::string & marker_name = cdfem_support.get_nonconformal_adapt_marker_name(); + auto & h_adapt = cdfem_support.get_nonconformal_hadapt(); + + std::function marker_function = + [&mesh, &interfaceGeometry](const std::string & marker_field_name, int num_refinements) + { + mark_interface_elements_for_adaptivity(mesh, interfaceGeometry, marker_field_name, num_refinements); + }; + + perform_multilevel_adaptivity(mesh, marker_name, marker_function, h_adapt, cdfem_do_not_refine_or_unrefine_selector(cdfem_support)); +} + +void +CDMesh::rebuild_after_rebalance() +{ + clear(); + generate_nonconformal_elements(); + restore_subelement_edge_nodes(); + restore_subelements(); +} + +static bool side_is_adaptivity_or_cdfem_parent(stk::mesh::BulkData & mesh, stk::mesh::Entity side, const stk::mesh::Part & cdfemParentPart) +{ + if (mesh.num_connectivity(side, stk::topology::CONSTRAINT_RANK) > 0) + return true; + for (auto element : StkMeshEntities{mesh.begin_elements(side), mesh.end_elements(side)}) + if (mesh.bucket(element).member(cdfemParentPart)) + return true; + return false; +} + +void delete_extraneous_inactive_sides(stk::mesh::BulkData & mesh, const stk::mesh::Part & cdfemParentPart, const stk::mesh::Part & activePart) +{ + stk::mesh::Selector notActive = !activePart; + + std::vector sides; + stk::mesh::get_selected_entities(notActive, mesh.buckets(mesh.mesh_meta_data().side_rank()), sides, false); + + mesh.modification_begin(); + + for (auto && side : sides) + if (!side_is_adaptivity_or_cdfem_parent(mesh, side, cdfemParentPart)) + ThrowRequireMsg(disconnect_and_destroy_entity(mesh, side), "Could not destroy entity " << mesh.entity_key(side)); + + mesh.modification_end(); +} + +void +CDMesh::rebuild_from_restart_mesh(stk::mesh::BulkData & mesh) +{ + ParallelThrowRequire(mesh.parallel(), !the_new_mesh); + + the_new_mesh = std::make_shared(mesh, the_new_mesh); + the_new_mesh->rebuild_child_part(); + the_new_mesh->rebuild_parent_and_active_parts_using_nonconformal_and_child_parts(); + the_new_mesh->generate_nonconformal_elements(); + the_new_mesh->restore_subelement_edge_nodes(); + the_new_mesh->restore_subelements(); + + // rebuild conformal side parts + the_new_mesh->stk_bulk().modification_begin(); + the_new_mesh->update_element_side_parts(); + the_new_mesh->stk_bulk().modification_end(); + + delete_extraneous_inactive_sides(mesh, the_new_mesh->get_parent_part(), the_new_mesh->get_active_part()); +} + +void +CDMesh::rebuild_child_part() +{ + auto & child_part = get_child_part(); + auto & mesh = stk_bulk(); + + // Need to iterate all locally owned elements to find child elements, + // which are identified by detecting that they use child edge nodes + stk::mesh::EntityVector local_elements; + stk::mesh::Selector sel = get_active_part() & get_locally_owned_part(); + stk::mesh::get_selected_entities(sel, mesh.buckets(stk::topology::ELEMENT_RANK), + local_elements); + + auto conformal_selector = stk::mesh::selectUnion(my_phase_support.get_conformal_parts()); + const auto & child_edge_node_part = get_child_edge_node_part(); + mesh.modification_begin(); + for(auto && elem : local_elements) + { + auto elem_nodes = mesh.begin_nodes(elem); + auto num_nodes = mesh.num_nodes(elem); + bool is_child_elem = false; + for(unsigned i=0; i < num_nodes; ++i) + { + if(mesh.bucket(elem_nodes[i]).member(child_edge_node_part)) + { + is_child_elem = true; + break; + } + } + + if(is_child_elem) + { + mesh.change_entity_parts(elem, stk::mesh::ConstPartVector{&child_part}, stk::mesh::ConstPartVector{}); + } + } + mesh.modification_end(); +} + +void +CDMesh::rebuild_parent_and_active_parts_using_nonconformal_and_child_parts() +{ + auto & parent_part = get_parent_part(); + auto & mesh = stk_bulk(); + + stk::mesh::EntityVector entities; + stk::mesh::Selector sel = get_active_part() & get_locally_owned_part() & + stk::mesh::selectUnion(my_phase_support.get_nonconformal_parts()); + stk::mesh::get_selected_entities(sel, mesh.buckets(stk::topology::ELEMENT_RANK), + entities); + + mesh.modification_begin(); + for(auto && elem : entities) + { + mesh.change_entity_parts(elem, stk::mesh::ConstPartVector{&parent_part}, stk::mesh::ConstPartVector{&get_active_part()}); + } + + // Also remove active part from nonconformal sides + entities.clear(); + auto side_rank = mesh.mesh_meta_data().side_rank(); + stk::mesh::get_selected_entities(sel, mesh.buckets(side_rank), entities); + for(auto && side : entities) + { + mesh.change_entity_parts(side, stk::mesh::ConstPartVector{}, stk::mesh::ConstPartVector{&get_active_part()}); + } + + // Also need to mark parents in undecomposed blocks that have children due to hanging + // nodes + entities.clear(); + stk::mesh::Selector undecomposed_child_sel = get_active_part() & get_locally_owned_part() & + !my_phase_support.get_all_decomposed_blocks_selector() & + get_child_part(); + stk::mesh::get_selected_entities(undecomposed_child_sel, mesh.buckets(stk::topology::ELEMENT_RANK), + entities); + for(auto && elem : entities) + { + auto parent_elem = get_parent_element(elem); + ThrowRequire(mesh.is_valid(parent_elem)); + mesh.change_entity_parts(parent_elem, stk::mesh::ConstPartVector{&parent_part}, stk::mesh::ConstPartVector{&get_active_part()}); + } + mesh.modification_end(); +} + +const SubElementNode * +CDMesh::build_subelement_edge_node(const stk::mesh::Entity node_entity) +{ + const auto & mesh = stk_bulk(); + + // This is super inefficient but lets just get it working for now. + ThrowRequire(mesh.is_valid(node_entity)); + auto id = mesh.identifier(node_entity); + auto find_existing = std::find_if(nodes.begin(), nodes.end(), + [id](const std::unique_ptr & compare) + { return compare->entityId() == id; }); + if(find_existing != nodes.end()) + return find_existing->get(); + + auto parent_ids = get_edge_node_parent_ids(mesh, get_parent_node_ids_field(), node_entity); + + auto search0 = std::find_if(nodes.begin(), nodes.end(), + [parent_ids](const std::unique_ptr & compare) + { return compare->entityId() == parent_ids[0]; }); + const SubElementNode * immediate_parent0 = (search0 == nodes.end()) ? + build_subelement_edge_node(mesh.get_entity(stk::topology::NODE_RANK, parent_ids[0])) : + search0->get(); + + auto search1 = std::find_if(nodes.begin(), nodes.end(), + [parent_ids](const std::unique_ptr & compare) + { return compare->entityId() == parent_ids[1]; }); + const SubElementNode * immediate_parent1 = (search1 == nodes.end()) ? + build_subelement_edge_node(mesh.get_entity(stk::topology::NODE_RANK, parent_ids[1])) : + search1->get(); + + // For LS per phase the immediate parents are not necessarily actual parent element nodes, + // we need to go all the way up the parentage tree to find the original parent nodes so we can + // get the parent Mesh_Element. + std::set parent_elem_nodes_set; + get_parent_nodes_from_child( + mesh, node_entity, get_parent_node_ids_field(), parent_elem_nodes_set); + std::vector parent_elem_nodes( + parent_elem_nodes_set.begin(), parent_elem_nodes_set.end()); + std::vector parent_elems; + stk::mesh::get_entities_through_relations(stk_bulk(), parent_elem_nodes, stk::topology::ELEMENT_RANK, parent_elems); + + stk::mesh::Entity owner_entity; + const stk::mesh::Selector locally_owned_parent_selector = get_locally_owned_part() & get_parent_part(); + for(auto && e : parent_elems) + { + if(locally_owned_parent_selector(mesh.bucket(e))) + { + owner_entity = e; + break; + } + } + if(!mesh.is_valid(owner_entity)) + { + std::ostringstream err_msg; + err_msg << "Failed to find locally owned element connected to nodes" + << immediate_parent0->entityId() << " and " << immediate_parent1->entityId() + << " when restoring CDFEM data structures after restart.\n"; + err_msg << "Possible parent elems:\n"; + for(auto && e : parent_elems) + { + err_msg << " " << debug_entity(mesh, e) << "\n"; + } + throw std::runtime_error(err_msg.str()); + } + + auto owner_id = mesh.identifier(owner_entity); + const Mesh_Element * owner = find_mesh_element(owner_id); + ThrowRequireMsg(owner, "Could not find Mesh_Element for owner element " << owner_id << " " << debug_entity(mesh, owner_entity)); + const double position = compute_child_position( + mesh, node_entity, immediate_parent0->entity(), immediate_parent1->entity()); + std::unique_ptr newNode = std::make_unique(owner, position, immediate_parent0, immediate_parent1); + SubElementNode * edgeNode = add_managed_node(std::move(newNode)); + edgeNode->set_entity(stk_bulk(), node_entity); + return edgeNode; +} + +void +CDMesh::restore_subelement_edge_nodes() +{ + stk::mesh::Selector selector = get_child_edge_node_part() & + (get_locally_owned_part() | get_globally_shared_part()); + const auto & mesh = stk_bulk(); + const auto & buckets = mesh.get_buckets(stk::topology::NODE_RANK, selector); + const auto parent_id_field = get_parent_node_ids_field(); + + std::vector< const stk::mesh::FieldBase *> fields(1, &parent_id_field.field()); + stk::mesh::communicate_field_data(mesh, fields); + + for(const auto & b_ptr : buckets) + { + for(unsigned i=0; i < b_ptr->size(); ++i) + { + build_subelement_edge_node((*b_ptr)[i]); + } + } +} + +void +CDMesh::restore_subelements() +{ + stk::mesh::Selector selector = get_locally_owned_part() & get_child_part(); + const auto & mesh = stk_bulk(); + const auto & buckets = mesh.get_buckets(stk::topology::ELEMENT_RANK, selector); + NodeVec subelem_nodes; + for(const auto & b_ptr : buckets) + { + const stk::topology & topo = b_ptr->topology(); + const unsigned num_nodes = topo.num_nodes(); + subelem_nodes.reserve(num_nodes); + for(const auto & elem : *b_ptr) + { + const stk::mesh::Entity parent = get_parent_element(elem); + ThrowRequire(mesh.is_valid(parent) && parent != elem); + + auto parent_mesh_elem = find_mesh_element(mesh.identifier(parent)); + ThrowRequire(parent_mesh_elem); + + subelem_nodes.clear(); + // TODO: May need to create subelement edge nodes somehow + const auto * elem_nodes = mesh.begin_nodes(elem); + ThrowAssert(mesh.num_nodes(elem) == num_nodes); + for(unsigned i=0; i < num_nodes; ++i) + { + auto node_id = mesh.identifier(elem_nodes[i]); + auto find_node = std::find_if(nodes.begin(), nodes.end(), + [node_id](const std::unique_ptr & compare) + { return compare->entityId() == node_id; }); + ThrowRequire(find_node != nodes.end()); + subelem_nodes.push_back(find_node->get()); + } + + std::unique_ptr subelem; + switch(topo) + { + case stk::topology::TRIANGLE_3_2D: + subelem = std::make_unique(subelem_nodes, std::vector{-1, -1, -1}, parent_mesh_elem); + break; + case stk::topology::TETRAHEDRON_4: + subelem = std::make_unique(subelem_nodes, std::vector{-1, -1, -1, -1}, parent_mesh_elem); + break; + default: + ThrowRuntimeError("At present only Tri3 and Tet4 topologies are supported for restart of CDFEM problems."); + } + ThrowAssert(subelem); + subelem->set_entity(stk_bulk(), elem); + const_cast(parent_mesh_elem)->add_subelement(std::move(subelem)); + } + } +} + +void +CDMesh::delete_cdfem_parent_elements() +{/* %TRACE[SPEC]% */ Tracespec trace__("Mesh::delete_cdfem_parent_elements(stk::mesh::BulkData & mesh)"); /* %TRACE% */ + // Percept messes up the cdfem child/parent parts, the active part, and the percept refined part for post-cdfem refinement. + // This is kind of an extreme work-around, but here we delete all of the cdfem parents prior to the post-cdfem refiment so that + // there is nothing to mess up. + + std::vector< stk::mesh::Entity> cdfem_parent_elements; + + stk::mesh::Selector selector = get_parent_part(); + stk::mesh::get_selected_entities( selector, stk_bulk().buckets( stk::topology::ELEMENT_RANK ), cdfem_parent_elements ); + + stk_bulk().modification_begin(); + delete_mesh_entities(stk_bulk(), cdfem_parent_elements); + stk_bulk().modification_end(); +} + +void +CDMesh::fixup_adapted_element_parts(stk::mesh::BulkData & mesh) +{/* %TRACE[SPEC]% */ Tracespec trace__("Mesh::fixup_adapted_element_parts(CDFEM_Support & cdfem_support)"); /* %TRACE% */ + // Fixup volume parts that currently can be messed up by adaptivity. + // There are two types of fixes: + // 1. CDFEM parent elements that are activated by Encore adaptivity (this is expected, but needs to be fixed). + // 2. Conformal elements that have somehow picked up the non-conformal part (this probably shouldn't happen). + + Phase_Support & phase_support = Phase_Support::get(mesh.mesh_meta_data()); + CDFEM_Support & cdfem_support = CDFEM_Support::get(mesh.mesh_meta_data()); + AuxMetaData & aux_meta = AuxMetaData::get(mesh.mesh_meta_data()); + stk::mesh::Selector cdfem_parent_selector = cdfem_support.get_parent_part(); + + stk::mesh::Selector locally_owned_selector(mesh.mesh_meta_data().locally_owned_part()); + std::vector entities; + + std::vector remove_parts; + stk::mesh::PartVector bucket_remove_parts; + const stk::mesh::BucketVector & buckets = mesh.get_buckets(stk::topology::ELEMENT_RANK, locally_owned_selector); + for (auto&& bucket_ptr : buckets) + { + unsigned num_volume_parts = 0; + stk::mesh::Part * extraneous_nonconformal_part = nullptr; + const stk::mesh::PartVector & bucket_parts = bucket_ptr->supersets(); + bucket_remove_parts.clear(); + for(auto&& part : bucket_parts) + { + if (part->primary_entity_rank() == stk::topology::ELEMENT_RANK && part->subsets().empty() && part->topology() != stk::topology::INVALID_TOPOLOGY) + { + ++num_volume_parts; + if (phase_support.is_nonconformal(part)) + { + extraneous_nonconformal_part = part; + } + } + } + if (num_volume_parts > 1) + { + bucket_remove_parts.push_back(extraneous_nonconformal_part); + } + if (cdfem_parent_selector(*bucket_ptr)) + { + bucket_remove_parts.push_back(&aux_meta.active_part()); + } + if (!bucket_remove_parts.empty()) + { + entities.insert(entities.end(), bucket_ptr->begin(), bucket_ptr->end()); + remove_parts.insert(remove_parts.end(), bucket_ptr->size(), bucket_remove_parts); + } + } + stk::mesh::PartVector empty; + std::vector add_parts(entities.size(), empty); + + // This seems like a bug. For some reason batch_change_entity_parts does not work the same as calling change_entity_parts within a full modification cycle. + //mesh.bulk().batch_change_entity_parts(entities, add_parts, remove_parts); + mesh.modification_begin(); + for(size_t i=0; iget_edge_interpolation_model()) + { + return; + } + + for (auto && node : mesh->nodes) + { + const krino::SubElementEdgeNode* edge_node = dynamic_cast(node.get()); + if (nullptr == edge_node) continue; + + NodeVec parents = edge_node->get_parents(); + const double ratio = edge_node->get_position(); + + stk::mesh::Entity node_obj = edge_node->entity(); + stk::mesh::Entity parent0_obj = parents[0]->entity(); + stk::mesh::Entity parent1_obj = parents[1]->entity(); + + const int num_ls_fields = mesh->num_ls_fields(); + for (int ls_index=0; ls_indexls_field(ls_index); + double & node_val = *field_data( ls_field.isovar , node_obj); + const double & parent0_val = *field_data( ls_field.isovar , parent0_obj); + const double & parent1_val = *field_data( ls_field.isovar , parent1_obj); + + const double interp_val = (1.-ratio) * parent0_val + ratio * parent1_val; + node_val = interp_val; + } + } +} + +void +CDMesh::stash_field_data(const int step_count, const CDMesh & new_mesh) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::stash_field_data(const int step_count)"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timer_stash_field_data); + my_stash_step_count = step_count; + clear_prolongation_data(); + + stash_nodal_field_data(new_mesh); + stash_elemental_field_data(); +} + +//-------------------------------------------------------------------------------- + +static void fill_nodes_of_elements_with_subelements_or_changed_phase(const stk::mesh::BulkData & mesh, + const std::vector> & newMeshElements, + const std::vector> & oldMeshElements, + std::set & nodesOfElements) +{ + nodesOfElements.clear(); + + for (auto && element : newMeshElements) + { + bool haveSubelementsOrChangedPhase = false; + if (element->have_subelements()) + { + haveSubelementsOrChangedPhase = true; + } + else + { + const Mesh_Element * oldElement = CDMesh::find_mesh_element(element->entityId(), oldMeshElements); + if (nullptr == oldElement || element->get_phase() != oldElement->get_phase()) + haveSubelementsOrChangedPhase = true; + } + + if (haveSubelementsOrChangedPhase) + for (auto && node : element->get_nodes()) + nodesOfElements.insert(node->entity()); + } +} + +static +void pack_shared_nodes_for_sharing_procs(const stk::mesh::BulkData & mesh, + const std::set & nodes, + stk::CommSparse &commSparse) +{ + std::vector nodeSharedProcs; + stk::pack_and_communicate(commSparse,[&]() + { + for (auto node : nodes) + { + if (mesh.bucket(node).shared()) + { + mesh.comm_shared_procs(node, nodeSharedProcs); + for (int procId : nodeSharedProcs) + commSparse.send_buffer(procId).pack(mesh.identifier(node)); + } + } + }); +} + +static +void unpack_shared_nodes(const stk::mesh::BulkData & mesh, + std::set & nodes, + stk::CommSparse &commSparse) +{ + stk::unpack_communications(commSparse, [&](int procId) + { + stk::CommBuffer & buffer = commSparse.recv_buffer(procId); + + while ( buffer.remaining() ) + { + stk::mesh::EntityId nodeId; + commSparse.recv_buffer(procId).unpack(nodeId); + stk::mesh::Entity node = mesh.get_entity(stk::topology::NODE_RANK, nodeId); + ThrowRequire(mesh.is_valid(node)); + nodes.insert(node); + } + }); +} + +static std::set get_nodes_of_elements_with_subelements_or_have_changed_phase(const stk::mesh::BulkData & mesh, const std::vector> & newMeshElements, + const std::vector> & oldMeshElements) +{ + std::set nodesOfElements; + fill_nodes_of_elements_with_subelements_or_changed_phase(mesh, newMeshElements, oldMeshElements, nodesOfElements); + + stk::CommSparse commSparse(mesh.parallel()); + pack_shared_nodes_for_sharing_procs(mesh, nodesOfElements, commSparse); + unpack_shared_nodes(mesh, nodesOfElements, commSparse); + + return nodesOfElements; +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::stash_nodal_field_data(const CDMesh & new_mesh) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::stash_nodal_field_data()"); /* %TRACE% */ + + // stash child nodes + { + stk::mesh::Selector selector = get_locally_owned_part() & get_child_part(); + + stk::mesh::BucketVector const& buckets = stk_bulk().get_buckets( stk::topology::ELEMENT_RANK, selector ); + for ( auto&& bucket_ptr : buckets ) + { + const stk::mesh::Bucket & b = *bucket_ptr; + const size_t length = b.size(); + for (size_t ielem = 0; ielem < length; ++ielem) + { + stk::mesh::Entity elem = b[ielem]; + const unsigned num_elem_nodes = stk_bulk().num_nodes(elem); + const stk::mesh::Entity* elem_nodes = stk_bulk().begin_nodes(elem); + + for (unsigned inode = 0; inode < num_elem_nodes; ++inode) + { + stk::mesh::Entity node = elem_nodes[inode]; + ThrowAssert((stk_bulk().bucket(node).member(get_active_part()))); + ProlongationNodeData *& node_data = my_prolong_node_map[stk_bulk().identifier(node)]; + if (nullptr == node_data) + { + const bool communicate_me_to_all_sharers = stk_bulk().bucket(node).member(get_globally_shared_part()); + node_data = new ProlongationNodeData(*this, node, communicate_me_to_all_sharers); + } + } + } + } + } + + // Stash all nodes of elements that have child elements or have changed phase. + // Due to hanging nodes, etc, this is more than just the cut elements. + for (auto&& node : get_nodes_of_elements_with_subelements_or_have_changed_phase(stk_bulk(), new_mesh.elements, elements)) + { + if (stk_bulk().bucket(node).member(get_active_part())) // Don't stash inactive midside nodes + { + ThrowAssert(stk_bulk().is_valid(node)); + ProlongationNodeData *& node_data = my_prolong_node_map[stk_bulk().identifier(node)]; + if (nullptr == node_data) + { + const bool communicate_me_to_all_sharers = stk_bulk().bucket(node).member(get_globally_shared_part()); + node_data = new ProlongationNodeData(*this, node, communicate_me_to_all_sharers); + } + } + } + + // stash all inter-block nodes + if (need_nodes_for_prolongation()) + { + stk::mesh::Selector active_not_ghost_selector(get_active_part() & (get_locally_owned_part() | get_globally_shared_part())); + const stk::mesh::BucketVector & buckets = stk_bulk().get_buckets(stk::topology::NODE_RANK, active_not_ghost_selector); + for (auto&& bucket_ptr : buckets) + { + unsigned num_conformal_parts = 0; + const stk::mesh::PartVector & node_parts = bucket_ptr->supersets(); + for(auto&& node_part_ptr : node_parts) + { + // This is designed to catch side with block_2 + block_1_air, block_1_air + block_1_solid, etc. + if (node_part_ptr->primary_entity_rank() == stk::topology::ELEMENT_RANK && + !my_phase_support.is_nonconformal(node_part_ptr) && + stk::io::is_part_io_part(*node_part_ptr)) + { + ++num_conformal_parts; + } + } + + if (num_conformal_parts > 1) + { + for (auto&& node : *bucket_ptr) + { + ProlongationNodeData *& node_data = my_prolong_node_map[stk_bulk().identifier(node)]; + if (nullptr == node_data) + { + node_data = new ProlongationNodeData(*this, node, false); + } + } + } + } + } + + // build facets if needed + if (need_facets_for_prolongation()) + { + stk::mesh::Selector active_locally_owned_selector(get_active_part() & get_locally_owned_part()); + + const stk::mesh::BucketVector & buckets = stk_bulk().get_buckets(stk_bulk().mesh_meta_data().side_rank(), active_locally_owned_selector); + for (auto&& bucket_ptr : buckets) + { + unsigned num_conformal_parts = 0; + const stk::mesh::PartVector & side_parts = bucket_ptr->supersets(); + for(auto&& side_part_ptr : side_parts) + { + // This is designed to catch side with block_2 + block_1_air, block_1_air + block_1_solid, etc. + if (side_part_ptr->primary_entity_rank() == stk::topology::ELEMENT_RANK && + !my_phase_support.is_nonconformal(side_part_ptr) && + stk::io::is_part_io_part(*side_part_ptr)) + { + ++num_conformal_parts; + } + } + + if (num_conformal_parts > 1) + { + for (auto&& side : *bucket_ptr) + { + ThrowAssert( stk_bulk().num_elements(side) > 0 ); + + ProlongationFacet * prolong_facet = new ProlongationFacet(*this, side); + my_prolong_facets.push_back(prolong_facet); + } + } + } + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::stash_elemental_field_data() const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::stash_elemental_field_data()"); /* %TRACE% */ + const FieldSet & element_fields = get_element_fields(); + if (element_fields.empty()) return; + + for (const auto & mesh_elem : elements) + { + const stk::mesh::EntityId elem_id = mesh_elem->entityId(); + stk::mesh::Entity elem = mesh_elem->entity(); + ThrowAssert(stk_bulk().is_valid(elem)); + + if(mesh_elem->have_subelements()) + { + std::vector conformal_subelems; + mesh_elem->get_subelements(conformal_subelems); + + const unsigned num_child = conformal_subelems.size(); + std::vector child_data(num_child); + std::vector< std::vector > child_intg_wts(num_child); + + for (unsigned j=0; jentityId(); + stk::mesh::Entity subelem_entity = stk_bulk().get_entity(stk::topology::ELEMENT_RANK, subelem_id); //EXPENSIVE! + ProlongationElementData * subelem_data = new ProlongationElementData(stk_bulk(), subelem_entity); + ThrowAssertMsg(0 == my_prolong_element_map.count(subelem_id), "Duplicate subelement entityId " << subelem_id); + my_prolong_element_map[subelem_id] = subelem_data; + subelem->set_prolongation_data(subelem_data); + child_data[j] = subelem_data; + + subelem->integration_weights(child_intg_wts[j]); + } + + const bool single_coincident_subelement = (num_child == 1); + if (!single_coincident_subelement) + { + ProlongationElementData * elem_data = new ProlongationElementData(stk_bulk(), child_data, child_intg_wts); + ThrowAssert(0 == my_prolong_element_map.count(elem_id)); + my_prolong_element_map[elem_id] = elem_data; + mesh_elem->set_prolongation_data(elem_data); + } + } + else + { + ProlongationElementData * elem_data = new ProlongationElementData(stk_bulk(), elem); + ThrowAssert(0 == my_prolong_element_map.count(elem_id)); + my_prolong_element_map[elem_id] = elem_data; + mesh_elem->set_prolongation_data(elem_data); + } + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::clear_prolongation_trees() const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::clear_prolongation_trees() const"); /* %TRACE% */ + my_phase_prolong_tree_map.clear(); +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::build_prolongation_trees() const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::build_prolongation_trees() const"); /* %TRACE% */ + + clear_prolongation_trees(); + + if (need_facets_for_prolongation()) + { + std::map,std::vector> phase_prolong_facet_map; + + for ( unsigned n=0; nget_common_fields()].push_back(prolong_facet); + } + + for (auto && entry : phase_prolong_facet_map) + { + const std::vector & fields = entry.first; + std::vector & facets = entry.second; + + my_phase_prolong_tree_map[fields] = std::make_unique>(facets, ProlongationFacet::get_bounding_box); + ThrowAssert(!my_phase_prolong_tree_map[fields]->empty()); + } + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::communicate_prolongation_facet_fields() const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::communicate_prolongation_facet_fields() const"); /* %TRACE% */ + + if (!need_facets_for_prolongation()) + return; + + const int num_procs = stk_bulk().parallel_size(); + if ( num_procs == 1 ) return; // Don't talk to yourself, it's embarrassing + const int me = stk_bulk().parallel_rank(); + + // formulate messages + stk::CommSparse comm_sparse(stk_bulk().parallel()); + + const size_t map_size = my_phase_prolong_tree_map.size(); + + for (int pass=0; pass<2; ++pass) + { + for ( int p=0; p & facet_fields = entry.first; + b.pack(facet_fields.size()); + for (unsigned field : facet_fields) + b.pack(field); + } + + ThrowAssert( pass == 0 || 0 == b.remaining() ); + } + + if (pass == 0) + { + comm_sparse.allocate_buffers(); + } + else + { + // send/receive + comm_sparse.communicate(); + } + } + + for ( int p=0; p facet_fields(num_fields); + for (size_t ifield = 0; ifield fieldOrdinals) +{ + const stk::mesh::FieldVector & all_fields = meta.get_fields(); + std::ostringstream os; + os << "Fields { "; + for (unsigned fieldOrdinal : fieldOrdinals) + os << all_fields[fieldOrdinal]->name() << " "; + os << "}"; + return os.str(); +} + +//-------------------------------------------------------------------------------- + +const ProlongationPointData * +CDMesh::find_prolongation_node(const SubElementNode & dst_node) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::find_prolongation_node(const SubElementNode & dst_node) const"); /* %TRACE% */ + + const Vector3d & dst_node_coords = dst_node.coordinates(); + const ProlongationPointData * src_data = nullptr; + + const std::vector required_fields = dst_node.prolongation_node_fields(*this); + + ThrowRequire(need_facets_for_prolongation()); + + const ProlongationFacet * nearest_prolong_facet = nullptr; + bool matching_empty_tree = false; + FacetDistanceQuery nearest_facet_query; + for (auto && entry : my_phase_prolong_tree_map) + { + const std::vector & tree_fields = entry.first; + SearchTree * facet_tree = entry.second.get(); + + if (std::includes(tree_fields.begin(), tree_fields.end(), required_fields.begin(), required_fields.end())) + { + if (nullptr == facet_tree) + { + matching_empty_tree = true; + continue; + } + std::vector nearest_prolong_facets; + facet_tree->find_closest_entities( dst_node_coords, nearest_prolong_facets ); + ThrowAssert(!nearest_prolong_facets.empty()); + + for (auto && prolong_facet : nearest_prolong_facets) + { + FacetDistanceQuery facet_query(*prolong_facet->get_facet(), dst_node_coords); + if (nearest_facet_query.empty() || facet_query.distance_squared() < nearest_facet_query.distance_squared()) + { + nearest_prolong_facet = prolong_facet; + nearest_facet_query = facet_query; + } + } + } + } + + if(nullptr != nearest_prolong_facet) + { + src_data = nearest_prolong_facet->get_prolongation_point_data(nearest_facet_query); + if (krinolog.shouldPrint(LOG_DEBUG)) + { + const std::vector & facet_nodes = nearest_prolong_facet->get_prolongation_nodes(); + krinolog << "Prolongation facet for " << dst_node.entityId() << " has nodes "; + for (auto&& node : facet_nodes) + { + krinolog << node->entityId() << " "; + } + krinolog << stk::diag::dendl; + } + } + + if( nullptr == src_data ) + { + if (matching_empty_tree) + { + my_missing_remote_prolong_facets = true; + if (krinolog.shouldPrint(LOG_DEBUG)) krinolog << "Found missing remote prolong facet for node for " << dst_node.entityId() << stk::diag::dendl; + return nullptr; + } + // Search for facet failed. Now try nodes. This will handle triple points. Something better that handles an actual edge search might be better in 3d. + const ProlongationNodeData * closest_node = nullptr; + double closest_dist2 = std::numeric_limits::max(); + for (auto && entry : my_prolong_node_map) + { + const ProlongationNodeData * node = entry.second; + const std::vector & tree_fields = node->get_fields(); + if (std::includes(tree_fields.begin(), tree_fields.end(), required_fields.begin(), required_fields.end())) + { + const double dist2 = (node->get_coordinates()-dst_node_coords).length_squared(); + if (dist2 < closest_dist2) + { + closest_node = node; + closest_dist2 = dist2; + } + } + } + if (nullptr != closest_node) + { + src_data = closest_node; + if (krinolog.shouldPrint(LOG_DEBUG)) krinolog << "Prolongation node for " << dst_node.entityId() << " is " << closest_node->entityId() << stk::diag::dendl; + } + } + + if (nullptr == src_data) + { + krinolog << "Failed to find prolongation node for node#" << dst_node.entityId() << stk::diag::dendl; + if (krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << " with required part fields=" << print_fields(stk_meta(), required_fields) << stk::diag::dendl; + krinolog << " with parts="; + const stk::mesh::PartVector & parts = stk_bulk().bucket(dst_node.entity()).supersets(); + for(stk::mesh::PartVector::const_iterator part_iter = parts.begin(); part_iter != parts.end(); ++part_iter) + { + const stk::mesh::Part * const part = *part_iter; + krinolog << "\"" << part->name() << "\"" << " "; + } + krinolog << stk::diag::dendl; + const unsigned num_dst_node_elements = stk_bulk().num_elements(dst_node.entity()); + const stk::mesh::Entity* dst_node_elements = stk_bulk().begin_elements(dst_node.entity()); + for (unsigned dst_node_elem_index=0; dst_node_elem_indexname() << "\"" << " "; + } + krinolog << stk::diag::dendl; + } + + krinolog << "Candidate prolongation facets:" << stk::diag::dendl; + for (auto && entry : my_phase_prolong_tree_map) + { + const std::vector & tree_fields = entry.first; + krinolog << " matching fields=" << std::includes(tree_fields.begin(), tree_fields.end(), required_fields.begin(), required_fields.end()) + << ", tree fields=" << print_fields(stk_meta(), tree_fields) + << stk::diag::dendl; + } + } + } + else if(krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Prolongation data for node#" << stk_bulk().identifier(dst_node.entity()) << " (" << dst_node.coordinates() << ")" + << " will be point at location (" << src_data->get_coordinates() << ")" << stk::diag::dendl; + + } + + return src_data; +} + +//-------------------------------------------------------------------------------- + +const SubElementNode * +CDMesh::find_node_with_common_ancestry(const SubElementNode * new_node) const +{ + return new_node->find_node_with_common_ancestry(*this); +} + +static bool entity_has_any_node_in_selector(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const stk::mesh::Selector & selector) +{ + const unsigned numNodes = mesh.num_nodes(entity); + stk::mesh::Entity const* entityNodes = mesh.begin_nodes(entity); + for (unsigned n=0; n +CDMesh::get_nonconformal_elements() const +{ + std::vector elems; + const auto & all_decomposed_blocks_selector = my_phase_support.get_all_decomposed_blocks_selector(); + stk::mesh::Selector selector = get_locally_owned_part() & (get_parent_part() | (get_active_part() & !get_child_part())); + stk::mesh::BucketVector const& buckets = stk_bulk().get_buckets(stk::topology::ELEMENT_RANK, selector); + + for (auto&& bucket : buckets) + { + const stk::topology topology = bucket->topology(); + if (Mesh_Element::is_supported_topology(topology)) + for (auto&& elem : *bucket) + if (entity_has_any_node_in_selector(stk_bulk(), elem, all_decomposed_blocks_selector)) + elems.push_back(elem); + } + + std::sort(elems.begin(), elems.end(), stk::mesh::EntityLess(stk_bulk())); + + return elems; +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::generate_nonconformal_elements() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::generate_nonconformal_elements()"); /* %TRACE% */ + ParallelThrowRequire(stk_bulk().parallel(), nodes.empty()); + ParallelThrowRequire(stk_bulk().parallel(), elements.empty()); + + const std::vector nonconformalElems = get_nonconformal_elements(); + elements.reserve(nonconformalElems.size()); + for(auto && elem : nonconformalElems) + { + auto mesh_elem = std::make_unique(*this, elem); + elements.emplace_back(std::move(mesh_elem)); + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::clear() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::clear()"); /* %TRACE% */ + + nodes.clear(); + elements.clear(); + + clear_prolongation_data(); + + mesh_node_map.clear(); + child_elements.clear(); +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::clear_prolongation_data() const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::clear()"); /* %TRACE% */ + for (auto && map_entry : my_prolong_node_map) + { + delete map_entry.second; + } + my_prolong_node_map.clear(); + for (auto && map_entry : my_prolong_element_map) + { + delete map_entry.second; + } + my_prolong_element_map.clear(); + + for (auto && prolong_facet : my_prolong_facets) + { + delete prolong_facet; + } + my_prolong_facets.clear(); + + clear_prolongation_trees(); +} + +bool +CDMesh::is_interface(const PhaseTag & phase, const InterfaceID interface) const +{ + return phase_matches_interface(my_cdfem_support, phase, interface); +} + +//-------------------------------------------------------------------------------- + +PhaseTag +CDMesh::determine_entity_phase(stk::mesh::Entity entity) const +{ + return determine_phase_for_entity(stk_bulk(), entity, my_phase_support); +} + +//-------------------------------------------------------------------------------- + +bool +CDMesh::elem_io_part_changed(const ElementObj & elem) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::verify_elem_part(const Mesh_Element * elem) const"); /* %TRACE% */ + const stk::mesh::Part & current_elem_io_part = find_element_part(stk_bulk(),elem.entity()); + const stk::mesh::Part * const conformal_elem_io_part = my_phase_support.find_conformal_io_part(current_elem_io_part, elem.get_phase()); + return (¤t_elem_io_part != conformal_elem_io_part || !stk_bulk().bucket(elem.entity()).member(get_active_part())); +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::determine_nonconformal_parts( + stk::mesh::Entity entity, + stk::mesh::PartVector & add_parts, + stk::mesh::PartVector & remove_parts) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::determine_nonconformal_parts(stk::mesh::Entity entity, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const"); /* %TRACE% */ + + add_parts.clear(); + remove_parts.clear(); + + const auto & all_decomposed_blocks_selector = my_phase_support.get_all_decomposed_blocks_selector(); + stk::mesh::EntityRank entity_rank = stk_bulk().entity_rank(entity); + const stk::mesh::PartVector & current_parts = stk_bulk().bucket(entity).supersets(); + for(stk::mesh::PartVector::const_iterator part_iter = current_parts.begin(); part_iter != current_parts.end(); ++part_iter) + { + stk::mesh::Part & part = **part_iter; + if( part.primary_entity_rank() == entity_rank && all_decomposed_blocks_selector(part) ) + { + stk::mesh::Part * nonconformal_io_part = const_cast(my_phase_support.find_nonconformal_part(part)); + if (nullptr != nonconformal_io_part && nonconformal_io_part != &part) + { + add_parts.push_back(nonconformal_io_part); + remove_parts.push_back(&part); + + for(stk::mesh::PartVector::const_iterator sup_it = part.supersets().begin(); sup_it != part.supersets().end(); ++sup_it) + { + stk::mesh::Part & superset = **sup_it; + if (!stk::mesh::is_auto_declared_part(superset)) + { + remove_parts.push_back(&superset); + } + } + } + } + } + + // Set to inactive + remove_parts.push_back(&aux_meta().active_part()); + + if (entity_rank == stk::topology::ELEMENT_RANK) + { + add_parts.push_back(&get_parent_part()); + remove_parts.push_back(&get_child_part()); + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::determine_conformal_parts( + const stk::mesh::PartVector & current_parts, + const stk::mesh::EntityRank entity_rank, + const PhaseTag & phase, + stk::mesh::PartVector & add_parts, + stk::mesh::PartVector & remove_parts) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::determine_conformal_parts(stk::mesh::Entity entity, const PhaseTag & phase, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const"); /* %TRACE% */ + + const auto & all_decomposed_blocks_selector = my_phase_support.get_all_decomposed_blocks_selector(); + for(stk::mesh::PartVector::const_iterator part_iter = current_parts.begin(); part_iter != current_parts.end(); ++part_iter) + { + stk::mesh::Part & part = **part_iter; + if( part.primary_entity_rank() == entity_rank && + (stk::io::is_part_io_part(part) || all_decomposed_blocks_selector(&part)) ) + { + stk::mesh::Part * conformal_elem_io_part = const_cast(my_phase_support.find_conformal_io_part(part, phase)); + if (nullptr != conformal_elem_io_part && conformal_elem_io_part != &part) + { + add_parts.push_back(conformal_elem_io_part); + remove_parts.push_back(&part); + + for(stk::mesh::PartVector::const_iterator sup_it = part.supersets().begin(); sup_it != part.supersets().end(); ++sup_it) + { + stk::mesh::Part & superset = **sup_it; + if (!stk::mesh::is_auto_declared_part(superset)) + { + remove_parts.push_back(&superset); + } + } + } + } + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::determine_conformal_parts( + stk::mesh::Entity entity, + const PhaseTag & phase, + stk::mesh::PartVector & add_parts, + stk::mesh::PartVector & remove_parts) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::determine_conformal_parts(stk::mesh::Entity entity, const PhaseTag & phase, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const"); /* %TRACE% */ + + add_parts.clear(); + remove_parts.clear(); + + ThrowAssert(stk_bulk().is_valid(entity)); + + stk::mesh::EntityRank entity_rank = stk_bulk().entity_rank(entity); + const stk::mesh::PartVector & current_parts = stk_bulk().bucket(entity).supersets(); + determine_conformal_parts(current_parts, entity_rank, phase, add_parts, remove_parts); +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::determine_child_conformal_parts( + stk::topology topology, + const stk::mesh::PartVector & parent_parts, + const PhaseTag & phase, + stk::mesh::PartVector & child_parts) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::determine_child_conformal_parts(stk::mesh::Entity entity, const PhaseTag & phase, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const"); /* %TRACE% */ + + child_parts.clear(); + + const auto & all_decomposed_blocks_selector = my_phase_support.get_all_decomposed_blocks_selector(); + stk::mesh::EntityRank entity_rank = topology.rank(); + for(stk::mesh::PartVector::const_iterator part_iter = parent_parts.begin(); part_iter != parent_parts.end(); ++part_iter) + { + stk::mesh::Part & part = **part_iter; + if( part.primary_entity_rank() == entity_rank && + (stk::io::is_part_io_part(part) || all_decomposed_blocks_selector(&part)) ) + { + stk::mesh::Part * conformal_elem_io_part = const_cast(my_phase_support.find_conformal_io_part(part, phase)); + if (nullptr != conformal_elem_io_part && !my_phase_support.is_interface(&part)) + { + child_parts.push_back(conformal_elem_io_part); + } + } + else if (stk::mesh::contain(my_attribute_parts, part)) + { + child_parts.push_back(&part); + } + } + + child_parts.push_back(&stk_meta().get_topology_root_part(topology)); + + if (entity_rank == stk::topology::ELEMENT_RANK) + { + child_parts.push_back(&get_child_part()); + } + + // Set to active + child_parts.push_back(&aux_meta().active_part()); +} + +//-------------------------------------------------------------------------------- + +bool +CDMesh::triangulate(const InterfaceGeometry & interfaceGeometry) +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::triangulate(InterfaceGeometry & interfaceGeometry)"); /* %TRACE% */ + bool made_changes = false; + for (auto && elem : elements) + { + made_changes |= elem->triangulate(*this, interfaceGeometry); + } + return made_changes; +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::cut_sharp_features() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::cut_sharp_features(void)"); /* %TRACE% */ + for (auto && elem : elements) + { + elem->cut_interior_intersection_points(*this); + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::set_phase_of_uncut_elements(const InterfaceGeometry & interfaceGeometry) +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::snap_nearby_intersections_to_nodes(void)"); /* %TRACE% */ + + const bool oneLSPerPhase = Phase_Support::has_one_levelset_per_phase(); + for (auto && entry : interfaceGeometry.get_phase_for_uncut_elements()) + { + Mesh_Element * elem = find_mesh_element(stk_bulk().identifier(entry.first)); + if (elem) + { + PhaseTag elemPhase; + if (oneLSPerPhase) + { + elemPhase.add(ls_field(entry.second).identifier, -1); + elem->set_phase(elemPhase); + } + else + { + ThrowRequire(1 == num_ls_fields()); + elemPhase.add(ls_field(0).identifier, entry.second); + elem->set_phase(elemPhase); + } + if (false) + krinolog << "Set phase for elem " << stk_bulk().identifier(entry.first) << ".\n" << elem->visualize(*this) << stk::diag::dendl; + } + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::snap_nearby_intersections_to_nodes(const InterfaceGeometry & interfaceGeometry, NodeToCapturedDomainsMap & domainsAtNodes) +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::snap_nearby_intersections_to_nodes(void)"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timer_snap); + + snap_to_node(stk_bulk(), interfaceGeometry, get_snapper(), domainsAtNodes); + for (auto && entry : domainsAtNodes) + { + const SubElementNode * node = get_mesh_node(stk_bulk().identifier(entry.first)); + if (node) + node->set_node_domains(entry.second); + } + + domainsAtNodes.clear(); // done using this +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::decompose() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::decompose(void)"); /* %TRACE% */ + + if (my_cdfem_support.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + cut_sharp_features(); + + // TODO: N^2 in number of phases + for (auto && interface : active_interface_ids()) + { + determine_node_signs(interface); + decompose_edges(interface); + determine_node_scores(interface); + handle_hanging_children(interface); + } + for (auto && elem : elements) + { + elem->build_quadratic_subelements(*this); + } + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << stk::diag::dendl; + + for (auto && elem : elements ) + { + if(krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Determining subelement phases for Mesh_Element local_id=" << " identifier=" << elem->entityId(); + krinolog << "\n"; + } + elem->determine_decomposed_elem_phase(*this); + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "\n"; + } + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << stk::diag::dendl; +} + +void +CDMesh::build_parallel_hanging_edge_nodes() +{ /* %TRACE[ON]% */ Trace trace__("krino::CDMesh::build_parallel_hanging_edge_nodes(void)"); /* %TRACE% */ + stk::mesh::BulkData & mesh = stk_bulk(); + + if (mesh.parallel_size() < 2) return; + + // Get all cut edges in the mesh that are parallel shared. Processing by edge nodes should be cheaper than processing + // by elements since we don't have to deal with duplicates. + std::vector shared_edge_nodes; + for (auto&& node : nodes) + { + const SubElementEdgeNode * edge_node = dynamic_cast( node.get() ); + if (nullptr != edge_node) + { + if (SubElementChildNodeAncestry::is_shared(mesh, edge_node)) + { + shared_edge_nodes.emplace_back(edge_node); + } + } + } + + std::vector sharing_procs; + std::vector edge_node_keys; + + stk::CommSparse comm_spec(mesh.parallel()); + + for (int phase=0;phase<2;++phase) + { + for (auto&& shared_edge_node : shared_edge_nodes) + { + shared_edge_node.get_parent_node_keys(edge_node_keys); + stk_bulk().shared_procs_intersection(edge_node_keys, sharing_procs); + + for (auto&& other_proc : sharing_procs) + { + if (other_proc != mesh.parallel_rank()) + { + shared_edge_node.pack_into_buffer(comm_spec.send_buffer(other_proc)); + } + } + } + + if ( phase == 0 ) + { + comm_spec.allocate_buffers(); + } + else + { + comm_spec.communicate(); + } + } + + for(int i = 0; i < mesh.parallel_size(); ++i) + { + if(i != mesh.parallel_rank()) + { + while(comm_spec.recv_buffer(i).remaining()) + { + SubElementChildNodeAncestry shared_child_node(comm_spec.recv_buffer(i)); + shared_child_node.build_missing_child_nodes(*this); + } + } + } +} + +void +CDMesh::determine_node_signs(const InterfaceID & interface) +{ + for (auto && node : nodes) + { + node->clear_node_sign(); + } + for (auto && elem : elements) + { + elem->determine_node_signs(*this, interface); + } + sync_node_signs_on_constrained_nodes(); + parallel_sync_node_signs_on_shared_nodes(); +} + +void +CDMesh::decompose_edges(const InterfaceID & interface) +{ + for (auto && elem : elements) + { + if(krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Decomposing Mesh_Element local_id=" << " identifier=" << elem->entityId(); + krinolog << "\n"; + } + elem->decompose(*this, interface); + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "\n"; + } +} + +void +CDMesh::determine_node_scores(const InterfaceID & interface) +{ + for (auto && node : nodes) + { + node->clear_node_score(); + } + for (auto && elem : elements) + { + elem->determine_node_scores(*this, interface); + } + sync_node_scores_on_constrained_nodes(); + parallel_sync_node_scores_on_shared_nodes(); +} + +template +std::vector> determine_owning_procs_of_nodes_in_ancestries(const stk::mesh::BulkData & mesh, const std::vector> & constrainedNodesAndData) +{ + std::vector edgeNodeKeys; + + std::vector> owningProcsOfNodesInAncestries; + owningProcsOfNodesInAncestries.reserve(constrainedNodesAndData.size()); + for (auto&& constrainedNodeAndData : constrainedNodesAndData) + { + owningProcsOfNodesInAncestries.emplace_back(); + std::vector & owningProcs = owningProcsOfNodesInAncestries.back(); + + const auto & nodeAncestry = constrainedNodeAndData.first; + nodeAncestry.get_parent_node_keys(edgeNodeKeys); + for (auto && edgeNodeKey : edgeNodeKeys) + owningProcs.push_back(mesh.parallel_owner_rank(mesh.get_entity(edgeNodeKey))); //Expensive? + + stk::util::sort_and_unique(owningProcs); + } + + return owningProcsOfNodesInAncestries; +} + +template +std::vector> determine_sharing_procs_of_nodes_in_ancestries(const stk::mesh::BulkData & mesh, const std::vector> & sharedNodesAndData) +{ + std::vector edgeNodeKeys; + + std::vector> sharingProcsOfNodesInAncestries; + sharingProcsOfNodesInAncestries.reserve(sharedNodesAndData.size()); + for (auto&& sharedNodeAndData : sharedNodesAndData) + { + sharingProcsOfNodesInAncestries.emplace_back(); + std::vector & sharingProcs = sharingProcsOfNodesInAncestries.back(); + + const auto & nodeAncestry = sharedNodeAndData.first; + nodeAncestry.get_parent_node_keys(edgeNodeKeys); + mesh.shared_procs_intersection(edgeNodeKeys, sharingProcs); + } + + return sharingProcsOfNodesInAncestries; +} + +template +void pack_node_data_for_node_ancestries(const stk::mesh::BulkData & mesh, const std::vector> & nodeAncestriesAndData, const std::vector> & destinationProcs, stk::CommSparse &commSparse) +{ + stk::pack_and_communicate(commSparse,[&]() + { + ThrowAssert(nodeAncestriesAndData.size() == destinationProcs.size()); + std::vector edgeNodeKeys; + + for (size_t i=0; i +void set_node_sign_or_score(const SubElementNode * node, const T & signOrScore) { ThrowRequireMsg(false, "Unsupported type in set_node_sign_or_score."); } + +template <> +void set_node_sign_or_score(const SubElementNode * node, const int & sign) { node->set_node_sign(sign); } + +template <> +void set_node_sign_or_score(const SubElementNode * node, const double & score) { node->set_node_score(score); } + +template +T get_node_sign_or_score(const SubElementNode * node) { ThrowRequireMsg(false, "Unsupported type in get_node_sign_or_score."); } + +template <> +int get_node_sign_or_score(const SubElementNode * node) { return node->get_node_sign(); } + +template <> +double get_node_sign_or_score(const SubElementNode * node) { return node->get_node_score(); } + +template +bool node_sign_or_score_is_set(const SubElementNode * node) { ThrowRequireMsg(false, "Unsupported type in node_sign_or_score_is_set."); return false; } + +template <> +bool node_sign_or_score_is_set(const SubElementNode * node) { return node->node_sign_is_set(); } + +template <> +bool node_sign_or_score_is_set(const SubElementNode * node) { return node->node_score_is_set(); } + +template +std::vector> gather_constrained_node_ancestries_and_sign_or_score(const std::vector> & nodes, + const std::unordered_map > & periodicNodeIDMap) +{ + std::vector> ancestriesAndNodeOrScore; + for (auto && node : nodes) + { + if (node_sign_or_score_is_set(node.get())) + { + SubElementChildNodeAncestry nodeAncestry(node.get()); + const auto & constrainedNodeAncestries = nodeAncestry.get_constrained_node_ancestries(periodicNodeIDMap); + for (auto && constrainedNodeAncestry : constrainedNodeAncestries) + ancestriesAndNodeOrScore.emplace_back(constrainedNodeAncestry,get_node_sign_or_score(node.get())); + } + } + return ancestriesAndNodeOrScore; +} + +template +std::vector> gather_shared_node_ancestries_and_sign_or_score(const stk::mesh::BulkData & mesh, + const std::vector> & nodes) +{ + // Get all cut edges in the mesh that are parallel shared. Processing by edge nodes should be cheaper than processing + // by elements since we don't have to deal with duplicates. + + std::vector> sharedNodeAncestriesAndSignOrScore; + for (auto && node : nodes) + if (node_sign_or_score_is_set(node.get()) && SubElementChildNodeAncestry::is_shared(mesh, node.get())) + sharedNodeAncestriesAndSignOrScore.emplace_back(node.get(),get_node_sign_or_score(node.get())); + + return sharedNodeAncestriesAndSignOrScore; +} + +template +void receive_node_sign_or_score(CDMesh & cdmesh, stk::CommSparse &commSparse) +{ + stk::unpack_communications(commSparse, [&commSparse, &cdmesh](int procId) + { + auto & buffer = commSparse.recv_buffer(procId); + SubElementChildNodeAncestry shared_edge_node(buffer); + T signOrScore = 0; + buffer.unpack(signOrScore); + + const SubElementNode * node = shared_edge_node.find_subelement_node(cdmesh); + if (node) + set_node_sign_or_score(node, signOrScore); + }); +} + +template +void sync_node_sign_or_score_on_local_constrained_nodes(CDMesh & cdmesh, const std::vector> & constrainedNodesAndSignOrScore, const std::vector> & owningProcsOfNodesInAncestries) +{ + ThrowAssert(constrainedNodesAndSignOrScore.size() == owningProcsOfNodesInAncestries.size()); + + for (size_t i=0; i +void sync_node_sign_or_score_on_constrained_nodes(CDMesh & cdmesh, + const stk::mesh::BulkData & mesh, + const std::vector> & nodes, + const std::unordered_map > & periodicNodeIDMap) +{ + if (stk::is_true_on_all_procs(cdmesh.stk_bulk().parallel(), periodicNodeIDMap.empty())) + return; + + const std::vector> constrainedNodeAncestriesAndSignOrScore = gather_constrained_node_ancestries_and_sign_or_score(nodes, periodicNodeIDMap); + const std::vector> owningProcsOfNodesInAncestries = determine_owning_procs_of_nodes_in_ancestries(mesh, constrainedNodeAncestriesAndSignOrScore); + + sync_node_sign_or_score_on_local_constrained_nodes(cdmesh, constrainedNodeAncestriesAndSignOrScore, owningProcsOfNodesInAncestries); + + if (mesh.parallel_size() < 2) return; + + stk::CommSparse commSparse(mesh.parallel()); + pack_node_data_for_node_ancestries(mesh, constrainedNodeAncestriesAndSignOrScore, owningProcsOfNodesInAncestries, commSparse); + receive_node_sign_or_score(cdmesh, commSparse); +} + +template +void sync_node_sign_or_score_on_shared_nodes(CDMesh & cdmesh, + const stk::mesh::BulkData & mesh, + const std::vector> & nodes) +{ + if (mesh.parallel_size() < 2) return; + + const std::vector> sharedNodeAncestriesAndSignOrScore = gather_shared_node_ancestries_and_sign_or_score(mesh, nodes); + const std::vector> sharingProcsOfNodesInAncestries = determine_sharing_procs_of_nodes_in_ancestries(mesh, sharedNodeAncestriesAndSignOrScore); + + stk::CommSparse commSparse(mesh.parallel()); + pack_node_data_for_node_ancestries(mesh, sharedNodeAncestriesAndSignOrScore, sharingProcsOfNodesInAncestries, commSparse); + receive_node_sign_or_score(cdmesh, commSparse); +} + +void +CDMesh::sync_node_signs_on_constrained_nodes() +{ + sync_node_sign_or_score_on_constrained_nodes(*this, stk_bulk(), nodes, my_periodic_node_id_map); +} + +void +CDMesh::sync_node_scores_on_constrained_nodes() +{ + sync_node_sign_or_score_on_constrained_nodes(*this, stk_bulk(), nodes, my_periodic_node_id_map); +} + +void +CDMesh::parallel_sync_node_signs_on_shared_nodes() +{ + sync_node_sign_or_score_on_shared_nodes(*this, stk_bulk(), nodes); +} + +void +CDMesh::parallel_sync_node_scores_on_shared_nodes() +{ + sync_node_sign_or_score_on_shared_nodes(*this, stk_bulk(), nodes); +} + +void +CDMesh::handle_hanging_children(const InterfaceID & interface) +{ /* %TRACE[ON]% */ Trace trace__("krino::CDMesh::handle_hanging_children(const InterfaceID & interface)"); /* %TRACE% */ + + build_parallel_hanging_edge_nodes(); + + for (auto && elem : elements) + { + if(krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Handling hanging children Mesh_Element identifier=" << elem->entityId(); + krinolog << "\n"; + } + elem->handle_hanging_children(*this, interface); + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "\n"; + } +} + +//-------------------------------------------------------------------------------- + +const SubElementMeshNode * +CDMesh::get_mesh_node( stk::mesh::EntityId node_id ) const +{ + NodeMap::const_iterator it = mesh_node_map.find( node_id ); + return ( it != mesh_node_map.end() ) ? it->second : nullptr; +} + +const SubElementMeshNode * +CDMesh::get_mesh_node(const SubElementNode * new_node) const +{ + const SubElementMeshNode * mesh_node = dynamic_cast( new_node ); + if (nullptr != mesh_node) + { + return get_mesh_node(new_node->entityId()); + } + return nullptr; +} + +SubElementMeshNode * +CDMesh::add_managed_mesh_node( std::unique_ptr node ) +{ + SubElementMeshNode * ptr = node.get(); + mesh_node_map.insert( NodeMap::value_type( node->entityId(), ptr ) ); + add_managed_node(std::move(node)); + return ptr; +} + +//-------------------------------------------------------------------------------- +void +CDMesh::check_isovariable_field_existence_on_decomposed_blocks(stk::mesh::BulkData & mesh, + const bool conformal_parts_require_field) +{ + CDMesh cdmesh(mesh, std::shared_ptr()); + cdmesh.check_isovariable_field_existence_on_decomposed_blocks(conformal_parts_require_field); +} + +namespace { +bool field_exists_on_part(const FieldRef field, const stk::mesh::Part & part) +{ + stk::mesh::Selector field_selector = AuxMetaData::get(part.mesh_meta_data()).selectField(field, stk::topology::ELEMENT_RANK); + return field_selector(part); +} +std::ostream & dump_fields_on_part(std::ostream & os, const stk::mesh::Part & part) +{ + const auto & meta = part.mesh_meta_data(); + for(auto && field : meta.get_fields(stk::topology::NODE_RANK)) + { + if(field_exists_on_part(*field, part)) + os << " * " << field->name() << "\n"; + } + return os; +} +} +void +CDMesh::check_isovariable_field_existence_on_decomposed_blocks(const bool conformal_parts_require_field) const +{ + const int num_ls = num_ls_fields(); + for(auto && part : stk_meta().get_mesh_parts()) + { + if(part->primary_entity_rank() != stk::topology::ELEMENT_RANK) continue; + + const auto nonconformal_part = my_phase_support.find_nonconformal_part(*part); + if(!nonconformal_part) continue; + + /* 4 cases we care about here: + * 1) Nonconformal part - Skip + * 2) Initial IO part (i.e. block_1) - Must have field + * 3) Active conformal part (either LS or active part for irreversible) - Must have field + * 4) Deactivated conformal part (irreversible only) - Skip + */ + if(my_phase_support.is_nonconformal(part)) continue; + + // If part is the initial IO part the phase will be empty + const auto & phase = my_phase_support.get_iopart_phase(*part); + + // Steady state problems that only do a single decomposition can + // get away with only defining the isovariable on the initial IO part + // so skip checking for the field in that case. + if(!conformal_parts_require_field && !phase.empty()) continue; + + for(int ls=0; ls < num_ls; ++ls) + { + const auto * LS = ls_field(ls).ptr; + const auto * death_spec = get_death_spec(ls); + const bool is_dead_block = death_spec && phase.contain(death_spec->get_deactivated_phase()); + const bool LS_uses_part = + my_phase_support.level_set_is_used_by_nonconformal_part(LS, nonconformal_part); + if(LS_uses_part && !is_dead_block) + { + const auto field = ls_field(ls).isovar; + if(!field_exists_on_part(field, *part)) + { + std::ostringstream err_msg; + err_msg << "CDFEM isovariable field " << field.name() << " is not defined on " + << part->name() << " that is supposed to be decomposed.\n" + << "Available fields on " << part->name() << " are:\n"; + dump_fields_on_part(err_msg, *part); + throw std::runtime_error(err_msg.str()); + } + } + } + } +} + +//-------------------------------------------------------------------------------- + +const SubElementNode * +CDMesh::create_mesh_node( + const Mesh_Element * owner, + const int lnn, + stk::mesh::Entity nodeEntity ) +{ + const stk::mesh::EntityId nodeId = stk_bulk().identifier(nodeEntity); + const SubElementMeshNode * subnode = get_mesh_node(nodeId); + + if ( nullptr == subnode ) + { + const Vector3d owner_coords = owner->get_node_parametric_coords( lnn ); + const double * global_coords_ptr = field_data(get_coords_field(), nodeEntity); + + Vector3d global_coords(global_coords_ptr, my_spatial_dim); + + std::unique_ptr meshNode = std::make_unique( owner, nodeEntity, nodeId, owner_coords, global_coords ); + subnode = add_managed_mesh_node(std::move(meshNode)); + } + + return subnode; +} + +//-------------------------------------------------------------------------------- + +const SubElementNode * +CDMesh::create_edge_node( const Mesh_Element * owner, + const SubElementNode * parent1, + const SubElementNode * parent2, + const double position) +{ + const SubElementNode * subnode = SubElementNode::common_child({parent1, parent2}); + + if (subnode == nullptr) + { + std::unique_ptr newNode = std::make_unique(owner, position, parent1, parent2); + subnode = add_managed_node(std::move(newNode)); + + parent1->add_child(subnode); + parent2->add_child(subnode); + } + + return subnode; +} + +//-------------------------------------------------------------------------------- + +const SubElementNode * +CDMesh::create_midside_node( const Mesh_Element * owner, + const SubElementNode * parent1, + const SubElementNode * parent2, + stk::mesh::Entity entity) +{ + std::pair parents = + parent1second; + } + else + { + std::unique_ptr newNode = (stk_bulk().is_valid(entity)) ? + std::make_unique(owner, parent1, parent2, entity, stk_bulk().identifier(entity)) : + std::make_unique(owner, parent1, parent2); + subnode = add_managed_node(std::move(newNode)); + my_midside_node_map[parents] = subnode; + } + + return subnode; +} + +//-------------------------------------------------------------------------------- + +const SubElementNode * +CDMesh::create_steiner_node( const Mesh_Element * owner, + const NodeVec & parents, + const std::vector & weights ) +{ + std::unique_ptr newNode = std::make_unique( owner, parents, weights ); + return add_managed_node(std::move(newNode)); +} + +//-------------------------------------------------------------------------------- + +const SubElementNode * +CDMesh::create_child_internal_or_face_node( const Mesh_Element * owner, + const NodeVec & parents, + const std::vector & weights ) +{ + const SubElementNode * subnode = SubElementNode::common_child(parents); + + if (subnode == nullptr) + { + std::unique_ptr newNode = std::make_unique( owner, parents, weights ); + subnode = add_managed_node(std::move(newNode)); + + for (auto && parent : parents) + parent->add_child(subnode); + } + + return subnode; +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::create_subelement_mesh_entities( + const Mesh_Element & elem, + const std::vector conformal_subelems) +{ + stk::mesh::Entity parent_elem = elem.entity(); + for (auto && subelem : conformal_subelems) + { + const stk::mesh::PartVector & parent_parts = stk_bulk().bucket(parent_elem).supersets(); + const stk::topology parent_topology = stk_bulk().bucket(parent_elem).topology(); + stk::mesh::PartVector subelem_parts; + determine_child_conformal_parts(parent_topology, parent_parts, subelem->get_phase(), subelem_parts); + + if (0 == subelem->entityId()) + { + const stk::mesh::EntityId new_id = my_entity_id_pool.get_EntityId(stk::topology::ELEMENT_RANK); + ThrowAssert(!stk_bulk().is_valid(stk_bulk().get_entity(stk::topology::ELEMENT_RANK, new_id))); + stk::mesh::Entity subelem_entity = stk_bulk().declare_element(new_id, subelem_parts); + subelem->set_entity( stk_bulk(), subelem_entity ); + ThrowAssert(stk_bulk().bucket(subelem_entity).topology() != stk::topology::INVALID_TOPOLOGY); + + const NodeVec & elem_nodes = subelem->get_nodes(); + for (unsigned n=0; nentity(); + stk_bulk().declare_relation( subelem_entity, node , n ); + } + } + else + { + stk::mesh::Entity subelem_entity = subelem->entity(); + stk_bulk().change_entity_parts(subelem_entity, subelem_parts, get_removable_parts(stk_bulk(), subelem_entity)); + } + } +} + +void +CDMesh::attach_existing_and_identify_missing_subelement_sides( + const Mesh_Element & elem, + const std::vector conformal_subelems, + std::vector & side_requests) +{ + stk::mesh::BulkData & stk_mesh = stk_bulk(); + const bool build_internal_sides = my_cdfem_support.use_internal_face_stabilization(); + + for (auto && subelem : conformal_subelems) + { + const stk::topology topology = subelem->topology(); + const stk::mesh::Entity * elem_nodes = stk_bulk().begin_nodes(subelem->entity()); + + for (unsigned s=0; s side_nodes(side_topology.num_nodes()); + topology.side_nodes(elem_nodes, s, side_nodes.data()); + + std::vector sides; + stk::mesh::get_entities_through_relations(stk_mesh, side_nodes, stk_meta().side_rank(), sides); + + if (sides.empty()) + { + stk::mesh::Entity parent_side = find_entity_by_ordinal(stk_bulk(), elem.entity(), stk_meta().side_rank(), subelem->parent_side_id(s)); + const bool have_parent_side = stk_bulk().is_valid(parent_side); + const bool is_internal_side = subelem->parent_side_id(s) == -1; + + if (have_parent_side || (is_internal_side && build_internal_sides)) + { + static stk::mesh::PartVector empty_parts; + const stk::mesh::PartVector & parent_parts = have_parent_side ? stk_bulk().bucket(parent_side).supersets() : empty_parts; + + // We have to make sure that pre-existing sideset parts are added to the side so that we + // can figure out the correct conformal side parts during the second modification pass. + stk::mesh::PartVector side_parts; + determine_child_conformal_parts(side_topology, parent_parts, subelem->get_phase(), side_parts); + if (is_internal_side) + { + side_parts.push_back(&get_internal_side_part()); + } + + side_requests.emplace_back(subelem->entity(), s, side_parts); + } + } + else + { + ThrowRequire(sides.size() == 1); + stk::mesh::Entity elem_side_entity = sides[0]; + attach_entity_to_elements(stk_bulk(), elem_side_entity); + } + } + } +} + +bool +CDMesh::check_element_side_parts() const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::check_element_side_parts()"); /* %TRACE% */ + // This method requires aura to work correctly. + if (!stk_bulk().is_automatic_aura_on()) + { + // Skip check if we don't have aura + return true; + } + + bool success = true; + stk::mesh::Selector active_locally_owned = aux_meta().active_locally_owned_selector(); + stk::mesh::BucketVector const& buckets = stk_bulk().get_buckets(stk::topology::ELEMENT_RANK, active_locally_owned); + + std::vector side_nodes; + + for (auto&& bucket : buckets) + { + const stk::topology topology = bucket->topology(); + const unsigned num_sides = topology.num_sides(); + for (auto&& elem : *bucket) + { + auto elem_nodes = stk_bulk().begin(elem, stk::topology::NODE_RANK); + for (unsigned s=0; s elems; + stk::mesh::get_entities_through_relations(stk_bulk(), side_nodes, stk::topology::ELEMENT_RANK, elems); + for(auto && touching_elem : elems) krinolog << debug_entity(stk_bulk(), touching_elem) << stk::diag::dendl; + + success = false; + } + } + } + } + + return success; +} + +std::vector get_conformal_volume_part_ordinals(const stk::mesh::BulkData & mesh, const Phase_Support & phaseSupport, stk::mesh::Entity entity) +{ + std::vector conformalVolumeParts; + + for(auto && part : mesh.bucket(entity).supersets()) + { + if (part->primary_entity_rank() == stk::topology::ELEMENT_RANK) + { + if (phaseSupport.is_conformal(part)) + { + conformalVolumeParts.push_back(part->mesh_meta_data_ordinal()); + } + } + } + + return conformalVolumeParts; +} + +bool have_multiple_conformal_volume_parts_in_common(const stk::mesh::BulkData & mesh, const Phase_Support & phaseSupport, const std::vector & sideNodes) +{ + const int numSideNodes = sideNodes.size(); + ThrowRequire(numSideNodes > 0); + + std::vector commonConformalVolumeParts = get_conformal_volume_part_ordinals(mesh, phaseSupport, sideNodes[0]); + + for (int n=1; n nodeConformalVolumeParts = get_conformal_volume_part_ordinals(mesh, phaseSupport, sideNodes[n]); + + std::vector workingSet; + workingSet.swap(commonConformalVolumeParts); + std::set_intersection(workingSet.begin(),workingSet.end(),nodeConformalVolumeParts.begin(),nodeConformalVolumeParts.end(),std::back_inserter(commonConformalVolumeParts)); + + if (commonConformalVolumeParts.empty()) + return false; + } + return true; +} + +void +CDMesh::add_possible_interface_sides(std::vector & sideRequests) const +{ + // This will add sides that *might be* interface sides. + // Because this probes the nodes, it will add "keyhole" sides that aren't actually on an interface + // This should be harmless, however, and avoids extra communication or requiring aura. + + stk::mesh::Selector active_locally_owned = aux_meta().active_locally_owned_selector(); + stk::mesh::BucketVector const& buckets = stk_bulk().get_buckets(stk::topology::ELEMENT_RANK, active_locally_owned); + + std::vector sideNodes; + + for (auto&& bucket : buckets) + { + const stk::topology topology = bucket->topology(); + const unsigned num_sides = topology.num_sides(); + for (auto&& elem : *bucket) + { + auto elem_nodes = stk_bulk().begin(elem, stk::topology::NODE_RANK); + for (unsigned s=0; s & side_nodes) const +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::check_element_side_parts(const std::vector & side_nodes)"); /* %TRACE% */ + + // This method requires aura. + ThrowRequire(stk_bulk().is_automatic_aura_on()); + + std::vector elems; + stk::mesh::get_entities_through_relations(stk_bulk(), side_nodes, stk::topology::ELEMENT_RANK, elems); + + std::vector conformal_volume_parts; + for (auto&& elem : elems) + { + if (!stk_bulk().bucket(elem).member(get_active_part())) + { + continue; + } + auto& elem_parts = stk_bulk().bucket(elem).supersets(); + for(auto&& part : elem_parts) + { + if (part->primary_entity_rank() == stk::topology::ELEMENT_RANK && my_phase_support.is_conformal(part)) + { + if (std::find(conformal_volume_parts.begin(), conformal_volume_parts.end(), part) == conformal_volume_parts.end()) + { + conformal_volume_parts.push_back(part); + } + } + } + } + + if (conformal_volume_parts.empty()) + { + return true; + } + + if (conformal_volume_parts.size() > 2) + { + krinolog << "Expected to find 1 or 2 conformal side parts when examining side nodes: "; + for (auto&& side_node : side_nodes) krinolog << stk_bulk().identifier(side_node) << " "; + krinolog << " but instead found the parts: "; + for (auto&& part : conformal_volume_parts) krinolog << part->name() << " "; + krinolog << stk::diag::dendl; + return false; + } + + std::vector side_phases(conformal_volume_parts.size()); + for (unsigned iphase = 0; iphase sides; + stk::mesh::get_entities_through_relations(stk_bulk(), side_nodes, stk_meta().side_rank(), sides); + + if (conformal_volume_parts.size() == 2 && side_phases[0] != side_phases[1]) + { + stk::mesh::PartVector conformal_side_parts; + const stk::mesh::Part * conformal_side_part = my_phase_support.find_interface_part(*conformal_volume_parts[0], *conformal_volume_parts[1]); + if (nullptr != conformal_side_part) conformal_side_parts.push_back(const_cast(conformal_side_part)); + conformal_side_part = my_phase_support.find_interface_part(*conformal_volume_parts[1], *conformal_volume_parts[0]); + if (nullptr != conformal_side_part) conformal_side_parts.push_back(const_cast(conformal_side_part)); + + if (!conformal_side_parts.empty()) + { + // Check that side exists and has conformal side parts + if (sides.size() != 1) + { + krinolog << "Expected to find 1 conformal side, but instead found " << sides.size() << " when examining side nodes: "; + for (auto&& side_node : side_nodes) krinolog << stk_bulk().identifier(side_node) << " "; + krinolog << " with conformal volume parts: "; + for (auto&& part : conformal_volume_parts) krinolog << part->name() << " "; + krinolog << stk::diag::dendl; + return false; + } + else + { + auto& side_bucket = stk_bulk().bucket(sides[0]); + if (!side_bucket.member_all(conformal_side_parts)) + { + krinolog << "Side " << stk_bulk().identifier(sides[0]) << " is missing at least one of the conformal side parts: "; + for (auto&& part : conformal_side_parts) krinolog << part->name() << " "; + krinolog << ", actual parts: "; + auto& side_parts = side_bucket.supersets(); + for (auto&& part : side_parts) krinolog << part->name() << " "; + krinolog << stk::diag::dendl; + return false; + } + } + } + } + else + { + // Check that if the side exists, then it does not have any interface sides + if (sides.size() > 1) + { + krinolog << "Expected to find 0 or 1 side, but instead found " << sides.size() << " when examining side nodes: "; + for (auto&& side_node : side_nodes) krinolog << stk_bulk().identifier(side_node) << " "; + krinolog << " with conformal volume parts: "; + for (auto&& part : conformal_volume_parts) krinolog << part->name() << " "; + krinolog << stk::diag::dendl; + return false; + } + + const stk::mesh::PartVector & existing_side_parts = stk_bulk().bucket(sides[0]).supersets(); + for(auto && sidePart : existing_side_parts) + { + if(sidePart->primary_entity_rank() == stk_meta().side_rank() && my_phase_support.is_interface(sidePart)) + { + krinolog << "Side " << stk_bulk().identifier(sides[0]) << " has an erroneous interface part " << sidePart->name() << "." << stk::diag::dendl; + return false; + } + } + } + + return true; +} + +void +CDMesh::update_element_side_parts() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::update_element_side_parts()"); /* %TRACE% */ + + // This method makes sure the correct conformal side parts are on the element sides + stk::mesh::Selector locally_owned = get_locally_owned_part(); + + std::vector< stk::mesh::Entity> sides; + stk::mesh::get_selected_entities( locally_owned, stk_bulk().buckets( stk_bulk().mesh_meta_data().side_rank() ), sides ); + + stk::mesh::PartVector add_parts; + stk::mesh::PartVector remove_parts; + + for (auto && side : sides) + { + determine_element_side_parts(side, add_parts, remove_parts); + stk_bulk().change_entity_parts(side, add_parts, remove_parts); + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "After changes: " << debug_entity(stk_bulk(), side); + } + } +} + +void +CDMesh::determine_element_side_parts(const stk::mesh::Entity side, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const +{ + if (krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Analyzing side " << stk_bulk().identifier(side) << " with nodes "; + const unsigned num_side_nodes = stk_bulk().num_nodes(side); + stk::mesh::Entity const* side_nodes = stk_bulk().begin_nodes(side); + for (unsigned n=0; n volume_parts; + std::vector conformal_volume_parts; + std::vector nonconformal_volume_parts; + const stk::mesh::PartVector & existing_side_parts = stk_bulk().bucket(side).supersets(); + for(stk::mesh::PartVector::const_iterator part_iter = existing_side_parts.begin(); part_iter != existing_side_parts.end(); ++part_iter) + { + const stk::mesh::Part * const side_part = *part_iter; + if (side_part->primary_entity_rank() == stk::topology::ELEMENT_RANK) + { + if (my_phase_support.is_conformal(side_part)) + { + conformal_volume_parts.push_back(side_part); + } + if (my_phase_support.is_nonconformal(side_part)) + { + nonconformal_volume_parts.push_back(side_part); + } + else if (stk::io::is_part_io_part(*side_part)) + { + volume_parts.push_back(side_part); + } + } + } + + ThrowRequire(volume_parts.size() <= 2); // Can be zero for inactive elements supporting a face + + if (conformal_volume_parts.empty()) + { + /* There are two possible cases where no conformal volume parts are found: + * 1) This side is part of a surface that does not touch any blocks that are being decomposed. + * Only the active parts for these sides should be updated. + * 2) This side is a parent side that should be deactivated and moved to the nonconformal part. + * These sides will have at least 1 nonconformal volume part from the parent volume element. + */ + if(nonconformal_volume_parts.empty()) + { + if(element_side_should_be_active(side)) + { + add_parts.push_back(&aux_meta().active_part()); + } + else + { + remove_parts.push_back(&aux_meta().active_part()); + } + } + else + { + determine_nonconformal_parts(side, add_parts, remove_parts); + } + } + + if (volume_parts.size() == 2) + { + add_parts.push_back(&get_block_boundary_part()); + } + else + { + remove_parts.push_back(&get_block_boundary_part()); + } + + if (conformal_volume_parts.empty()) + { + return; + } + + ThrowRequire(conformal_volume_parts.size() == 1 || conformal_volume_parts.size() == 2); + + std::vector side_phases(conformal_volume_parts.size()); + for (unsigned iphase = 0; iphase(my_phase_support.find_interface_part(*conformal_volume_parts[0], *conformal_volume_parts[1])); + if (nullptr != conformal_side0) add_parts.push_back(conformal_side0); + stk::mesh::Part * conformal_side1 = const_cast(my_phase_support.find_interface_part(*conformal_volume_parts[1], *conformal_volume_parts[0])); + if (nullptr != conformal_side1) add_parts.push_back(conformal_side1); + } + + for (auto && side_phase : side_phases) + { + determine_conformal_parts(existing_side_parts, stk_meta().side_rank(), side_phase, add_parts, remove_parts); + } + + if(element_side_should_be_active(side)) + { + add_parts.push_back(&aux_meta().active_part()); + } + else + { + remove_parts.push_back(&aux_meta().active_part()); + } +} + +bool +CDMesh::element_side_should_be_active(const stk::mesh::Entity side) const +{ + auto num_elems = stk_bulk().num_connectivity(side, stk::topology::ELEMENT_RANK); + const auto * touching_elems = stk_bulk().begin(side, stk::topology::ELEMENT_RANK); + auto & active_part = aux_meta().active_part(); + bool active = false; + for(unsigned i=0; i < num_elems; ++i) + { + if(stk_bulk().bucket(touching_elems[i]).member(active_part)) + { + active = true; + break; + } + } + return active; +} +void +CDMesh::handle_single_coincident_subelement(const Mesh_Element & elem, const SubElement * subelem, std::vector & side_requests) +{ + stk::mesh::Entity elem_entity = elem.entity(); + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "single coincident subelement for elem #" << stk_bulk().identifier(elem_entity) << " with phase " << subelem->get_phase() << stk::diag::dendl; + subelem->set_entity( stk_bulk(), elem_entity ); + stk::mesh::PartVector add_parts; + stk::mesh::PartVector remove_parts; + determine_conformal_parts(elem_entity, subelem->get_phase(), add_parts, remove_parts); + + add_parts.push_back(&get_active_part()); + remove_parts.push_back(&get_parent_part()); + + stk_bulk().change_entity_parts(elem_entity, add_parts, remove_parts); + + std::vector subelem_vec(1, subelem); + attach_existing_and_identify_missing_subelement_sides(elem, subelem_vec, side_requests); +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::generate_sorted_child_elements() +{ + child_elements.clear(); + + for (const auto & elem : elements) + { + if (elem->have_subelements()) + { + std::vector conformal_subelems; + elem->get_subelements(conformal_subelems); + + for (auto && subelem : conformal_subelems) + { + child_elements.push_back(subelem); + } + } + } + + std::sort(child_elements.begin(),child_elements.end(), ElementObj::is_less); +} + +//-------------------------------------------------------------------------------- + +const SubElement * +CDMesh::find_child_element(stk::mesh::Entity elem_mesh_obj) const +{ + // Ugh + CDMesh *const mesh = const_cast(this); + + if (child_elements.empty()) + { + mesh->generate_sorted_child_elements(); + } + + const stk::mesh::EntityId elem_id = stk_bulk().identifier(elem_mesh_obj); + auto lb_cmp = [](const ElementObj * elem, stk::mesh::EntityId target_id) { return elem->entityId() < target_id; }; + auto first = std::lower_bound(child_elements.begin(),child_elements.end(), elem_id, lb_cmp); + + if (first != child_elements.end() && (*first)->entityId() == elem_id) + { + return dynamic_cast(*first); + } + return nullptr; +} + +//-------------------------------------------------------------------------------- + +stk::mesh::Entity +CDMesh::get_parent_element(stk::mesh::Entity elem_entity) const +{ + std::set parent_elem_node_set; + + const stk::mesh::Entity * elem_nodes = stk_bulk().begin_nodes(elem_entity); + const unsigned num_base_elem_nodes = stk_bulk().bucket(elem_entity).topology().base().num_nodes(); + + for (unsigned inode=0; inode parent_elem_nodes(parent_elem_node_set.begin(), parent_elem_node_set.end()); + std::vector parent_elems; + stk::mesh::get_entities_through_relations(stk_bulk(), parent_elem_nodes, stk::topology::ELEMENT_RANK, parent_elems); + + ThrowAssert(parent_elems.size() <= 1); + + if (parent_elems.empty()) + { + krinolog << "Did not find parent element for element \n" << debug_entity(stk_bulk(), elem_entity) << stk::diag::dendl; + return stk::mesh::Entity(); + } + else + { + return parent_elems.front(); + } +} + +//-------------------------------------------------------------------------------- + +bool +CDMesh::get_parent_child_coord_transformation(stk::mesh::Entity elem_mesh_obj, double * dParentdChild) const +{ + ThrowAssert(my_cdfem_support.use_nonconformal_element_size()); + + const SubElement * subelem = find_child_element(elem_mesh_obj); + + if (nullptr == subelem) + { + krinolog << "did not find element " << stk_bulk().identifier(elem_mesh_obj) << stk::diag::dendl; + return false; + } + + subelem->get_owner_coord_transform(dParentdChild); + return true; +} + +void +CDMesh::get_parent_nodes_and_weights(stk::mesh::Entity child, stk::mesh::Entity & parent0, stk::mesh::Entity & parent1, double & position) const +{ + // Really slow! + auto id = stk_bulk().identifier(child); + auto find_existing = std::find_if(nodes.begin(), nodes.end(), + [id](const std::unique_ptr & compare) + { return compare->entityId() == id; }); + ThrowAssert(find_existing != nodes.end()); + ThrowAssert(dynamic_cast(find_existing->get()) != nullptr); + const krino::SubElementEdgeNode& edge_node = dynamic_cast(*find_existing->get()); + krino::NodeVec edge_node_parents = edge_node.get_parents(); + position = edge_node.get_position(); + parent0 = edge_node_parents[0]->entity(); + parent1 = edge_node_parents[1]->entity(); +} + +//-------------------------------------------------------------------------------- + +std::function build_get_element_volume_function(const CDMesh & cdmesh) +{ + auto get_element_size = + [&cdmesh](stk::mesh::Entity elem) + { + stk::mesh::Entity volumeElement = cdmesh.get_cdfem_support().use_nonconformal_element_size() ? cdmesh.get_parent_element(elem) : elem; + ThrowRequire(cdmesh.stk_bulk().is_valid(volumeElement)); + const double elemVolume = ElementObj::volume( cdmesh.stk_bulk(), volumeElement, cdmesh.get_coords_field() ); + return elemVolume; + }; + return get_element_size; +} + +Vector3d get_side_average_cdfem_displacement(const stk::mesh::BulkData& mesh, + const FieldRef cdfemDisplacementsField, + stk::mesh::Entity side) +{ + const int spatialDim = mesh.mesh_meta_data().spatial_dimension(); + + Vector3d avg{Vector3d::ZERO}; + int numNodes = 0; + for (auto node : StkMeshEntities{mesh.begin_nodes(side), mesh.end_nodes(side)}) + { + double * cdfemDispPtr = field_data(cdfemDisplacementsField, node); + if(nullptr != cdfemDispPtr) + { + const Vector3d cdfemDisp(cdfemDispPtr, spatialDim); + avg += cdfemDisp; + ++numNodes; + } + } + if (numNodes > 0) + avg /= numNodes; + + return avg; +} + +Vector3d get_side_normal(const stk::mesh::BulkData& mesh, + const FieldRef coordsField, + stk::mesh::Entity side) +{ + const auto * sideNodes = mesh.begin_nodes(side); + const stk::topology sideTopology = mesh.bucket(side).topology(); + if (sideTopology == stk::topology::TRIANGLE_3 || sideTopology == stk::topology::TRIANGLE_6) + { + const Vector3d v0(field_data(coordsField, sideNodes[0])); + const Vector3d v1(field_data(coordsField, sideNodes[1])); + const Vector3d v2(field_data(coordsField, sideNodes[2])); + return Cross(v1-v0,v2-v0).unit_vector(); + } + else if (sideTopology == stk::topology::LINE_2 || sideTopology == stk::topology::LINE_3) + { + const Vector3d v0(field_data(coordsField, sideNodes[0]), 2); + const Vector3d v1(field_data(coordsField, sideNodes[1]), 2); + return crossZ(v1-v0).unit_vector(); + } + ThrowRequireMsg(false, "Unsupported topology " << sideTopology); + + return Vector3d::ZERO; +} + +double get_side_cdfem_cfl(const stk::mesh::BulkData& mesh, + const FieldRef cdfemDisplacementsField, + const FieldRef coordsField, + const stk::mesh::Selector & elementSelector, + const std::function & get_element_volume, + stk::mesh::Entity side) +{ + const Vector3d sideCDFEMDisplacement = get_side_average_cdfem_displacement(mesh, cdfemDisplacementsField, side); + const Vector3d sideNormal = get_side_normal(mesh, coordsField, side); + const double sideNormalDisplacement = Dot(sideCDFEMDisplacement, sideNormal); + + double minElemVolume = 0.; + for (auto elem : StkMeshEntities{mesh.begin_elements(side), mesh.end_elements(side)}) + { + if (elementSelector(mesh.bucket(elem))) + { + const double elemVol = get_element_volume(elem); + if (minElemVolume == 0. || elemVol < minElemVolume) + minElemVolume = elemVol; + } + } + double sideCFL = 0.; + if (minElemVolume > 0.) + { + const double invDim = 1.0 / mesh.mesh_meta_data().spatial_dimension(); + const double lengthScale = std::pow(minElemVolume, invDim); + sideCFL = sideNormalDisplacement / lengthScale; + } + return sideCFL; +} + +double CDMesh::compute_cdfem_cfl() const +{ + stk::diag::TimeBlock timer__(my_timer_compute_CFL); + + const stk::mesh::Selector interfaceSideSelector = my_phase_support.get_all_conformal_surfaces_selector(); + const stk::mesh::Selector elementSelector = selectUnion(my_phase_support.get_conformal_parts()) & get_active_part() & get_locally_owned_part(); + + auto get_element_volume = build_get_element_volume_function(*this); + + double cfl = 0.; + for( auto&& bucket : stk_bulk().get_buckets(stk_bulk().mesh_meta_data().side_rank(), interfaceSideSelector) ) + { + for (auto && side : *bucket) + { + const double sideCFL = get_side_cdfem_cfl(stk_bulk(), get_cdfem_displacements_field(), get_coords_field(), elementSelector, get_element_volume, side); + if (sideCFL > 0.) + cfl = std::max(cfl, sideCFL); + } + } + + const double localCFL = cfl; + stk::all_reduce_max(stk_bulk().parallel(), &localCFL, &cfl, 1); + + return cfl; +} + + +void +CDMesh::update_adaptivity_parent_entities() +{ + if (my_cdfem_support.get_interface_maximum_refinement_level() <= 0) + { + return; + } + + stk::mesh::BulkData& stk_mesh = stk_bulk(); + + stk::mesh::Part & refine_inactive_part = get_refinement_inactive_part(stk_meta(), stk::topology::ELEMENT_RANK); + stk::mesh::Selector adaptive_parent_locally_owned_selector = get_locally_owned_part() & refine_inactive_part; + + stk::mesh::PartVector add_parts; + stk::mesh::PartVector remove_parts; + + std::vector parents; + stk::mesh::get_selected_entities( adaptive_parent_locally_owned_selector, stk_mesh.buckets( stk::topology::ELEMENT_RANK ), parents ); + + for( auto&& parent : parents ) + { + std::vector leaf_children; + get_refinement_leaf_children(stk_mesh, parent, leaf_children); + std::set child_element_parts; + for (auto&& child : leaf_children) + { + const stk::mesh::Part & child_element_part = find_element_part(stk_mesh, child); + child_element_parts.insert(&child_element_part); + } + ThrowRequire(!child_element_parts.empty()); + + if (child_element_parts.size() > 1 || my_phase_support.is_nonconformal(*child_element_parts.begin())) + { + determine_nonconformal_parts(parent, add_parts, remove_parts); + const auto& add_it = std::find(add_parts.begin(), add_parts.end(), &get_parent_part()); + if (add_it != add_parts.end()) + { + add_parts.erase(add_it); + } + } + else if (child_element_parts.size() == 1 && !my_phase_support.is_nonconformal(*child_element_parts.begin())) + { + const PhaseTag & parent_phase = my_phase_support.get_iopart_phase(**child_element_parts.begin()); + determine_conformal_parts(parent, parent_phase, add_parts, remove_parts); + remove_parts.push_back(&get_parent_part()); + remove_parts.push_back(&get_child_part()); + } + stk_mesh.change_entity_parts(parent, add_parts, remove_parts); + } +} + +void +CDMesh::update_uncut_element(const Mesh_Element & elem) +{ + const stk::mesh::Entity elem_entity = elem.entity(); + stk::mesh::BulkData & stk_mesh = stk_bulk(); + if (stk_mesh.bucket(elem_entity).member(get_parent_part()) || elem_io_part_changed(elem)) + { + stk::mesh::PartVector add_parts; + stk::mesh::PartVector remove_parts; + determine_conformal_parts(elem_entity, elem.get_phase(), add_parts, remove_parts); + add_parts.push_back(&get_active_part()); + remove_parts.push_back(&get_parent_part()); + remove_parts.push_back(&get_child_part()); + + stk_mesh.change_entity_parts(elem_entity, add_parts, remove_parts); + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::create_node_entities() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::create_node_entities(void)"); /* %TRACE% */ + stk::mesh::BulkData& stk_mesh = stk_bulk(); + + std::vector node_parents; + std::vector node_requests; + std::vector higher_order_node_requests; + for (auto && node : nodes) + { + stk::mesh::Entity & node_entity = node->entity(); + if (!stk_bulk().is_valid(node_entity)) + { + node->get_parent_entities(node_parents); + if (nullptr == dynamic_cast(node.get())) + node_requests.push_back(ChildNodeRequest(node_parents, &node_entity)); + else + higher_order_node_requests.push_back(ChildNodeRequest(node_parents, &node_entity)); + } + } + + stk::mesh::PartVector node_parts = {&aux_meta().active_part(), + &get_child_edge_node_part(), + &stk_meta().get_topology_root_part(stk::topology::NODE) + }; + batch_create_child_nodes(stk_mesh, node_requests, node_parts, my_aux_meta.get_assert_32bit_flag(), my_aux_meta.get_force_64bit_flag()); + + stk::mesh::PartVector higher_order_node_parts = {&aux_meta().active_part(), + &stk_meta().get_topology_root_part(stk::topology::NODE) + }; + batch_create_child_nodes(stk_mesh, higher_order_node_requests, higher_order_node_parts, my_aux_meta.get_assert_32bit_flag(), my_aux_meta.get_force_64bit_flag()); + + for (auto && node_request : node_requests) + { + stk::mesh::Entity new_node = *node_request.child; + if (stk_mesh.bucket(new_node).member(stk_meta().locally_owned_part())) + { + if (get_parent_node_ids_field().valid()) + { + if (node_request.parents.size() != 2) + krinolog << "Created Steiner node that cannot be restored on restart: " << stk_mesh.identifier(new_node) << " " << node_request.parents.size() << stk::diag::dendl; + store_edge_node_parent_ids(stk_bulk(), get_parent_node_ids_field(), new_node, stk_bulk().identifier(*node_request.parents.front()), stk_bulk().identifier(*node_request.parents.back())); + } + } + } + + // Since batch_create_child_nodes took pointers to the entities, the entityIds were not updated, Ugh. + for (auto&& node : nodes) + { + node->set_entityId_from_entity(stk_mesh); + if(krinolog.shouldPrint(LOG_DEBUG)) + { + if (!node->is_mesh_node()) + krinolog << "NODE ID : " << node->entityId() << ": ancestry: [" << node->get_ancestry() << "]" << stk::diag::dendl; + } + } +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::create_element_and_side_entities(std::vector & side_requests) +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::create_element_and_side_entities(void)"); /* %TRACE% */ + + // Count how many we need to set pool size + unsigned num_local_subelems = 0; + for (auto && elem : elements) + { + if (elem->have_subelements()) + { + std::vector conformal_subelems; + elem->get_subelements(conformal_subelems); + num_local_subelems += conformal_subelems.size(); + } + } + + my_entity_id_pool.reserve(stk::topology::ELEMENT_RANK, num_local_subelems, my_aux_meta.get_assert_32bit_flag(), my_aux_meta.get_force_64bit_flag()); + + stk::mesh::PartVector add_parts; + stk::mesh::PartVector remove_parts; + + for (const auto & elem : elements) + { + if (elem->have_subelements()) + { + std::vector conformal_subelems; + elem->get_subelements(conformal_subelems); + + // check for corner case of a single subelement that is coincident with parent + if (elem->is_single_coincident()) + { + handle_single_coincident_subelement(*elem, conformal_subelems[0], side_requests); + } + else + { + create_subelement_mesh_entities(*elem, conformal_subelems); + attach_existing_and_identify_missing_subelement_sides(*elem, conformal_subelems, side_requests); + + determine_nonconformal_parts(elem->entity(), add_parts, remove_parts); + stk_bulk().change_entity_parts(elem->entity(), add_parts, remove_parts); + } + } + else + { + update_uncut_element(*elem); + } + } + + update_adaptivity_parent_entities(); +} + +//-------------------------------------------------------------------------------- + +void +CDMesh::prolongation() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::prolongation(void)"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timer_prolongation); + + BoundingBox proc_target_bbox; + for (auto && node : nodes) + { + proc_target_bbox.accommodate(node->coordinates()); + } + if(nodes.empty()) + { + proc_target_bbox.accommodate(Vector3d::ZERO); + } + + const bool guess_and_check_proc_padding = + my_old_mesh->stash_step_count() >= 0 && + stk_bulk().parallel_size() > 1; + double proc_padding = 0.0; + if (guess_and_check_proc_padding) + { + const double max_elem_size = compute_maximum_element_size(stk_bulk()); + proc_padding = 3.0*max_elem_size; + proc_target_bbox.pad(proc_padding); + } + else + { + proc_target_bbox.pad_epsilon(); + } + + bool done = false; + while (!done) + { + done = true; + + std::vector proc_target_bboxes; + BoundingBox::gather_bboxes( proc_target_bbox, proc_target_bboxes ); + + const size_t facet_precomm_size = my_old_mesh->my_prolong_facets.size(); + ProlongationFacet::communicate(*my_old_mesh, my_old_mesh->my_prolong_facets, my_old_mesh->my_prolong_node_map, proc_target_bboxes); + + my_old_mesh->build_prolongation_trees(); + + my_old_mesh->communicate_prolongation_facet_fields(); + + const stk::mesh::Part & active_part = get_active_part(); + + // update nodal fields + my_missing_remote_prolong_facets = false; + for (auto && node : nodes) + { + if(!(node->is_prolonged()) && stk_bulk().bucket(node->entity()).member(active_part)) + { + node->prolongate_fields(*this); + } + } + + const double max_cdfem_displacement = get_maximum_cdfem_displacement(); + if (guess_and_check_proc_padding && (my_missing_remote_prolong_facets || max_cdfem_displacement > proc_padding)) + { + const double growth_multiplier = 1.5; + krinolog << "Must redo ghosting for prolongation. New size = " << growth_multiplier*max_cdfem_displacement <<"\n"; + proc_target_bbox.pad(growth_multiplier*max_cdfem_displacement-proc_padding); + proc_padding = growth_multiplier*max_cdfem_displacement; + done = false; + + for (size_t i = facet_precomm_size; i < my_old_mesh->my_prolong_facets.size(); i++ ) + delete my_old_mesh->my_prolong_facets[i]; + my_old_mesh->my_prolong_facets.resize(facet_precomm_size); + + for (auto && node : nodes) + { + node->set_prolonged_flag(false); + } + } + } + + rebase_cdfem_displacements(); + + // prolongate element fields + for (const auto & elem : elements) + { + if (elem->have_subelements()) + { + std::vector conformal_subelems; + elem->get_subelements(conformal_subelems); + + for (auto && subelem : conformal_subelems) + { + subelem->prolongate_fields(*this); + } + } + else + { + elem->prolongate_fields(*this); + } + } + + // We might want to check what causes any parallel discrepencies, but sync everything here + const stk::mesh::FieldVector & all_fields = stk_bulk().mesh_meta_data().get_fields(); + const std::vector const_fields(all_fields.begin(), all_fields.end()); + for (auto && f : all_fields) + { + f->sync_to_host(); + f->modify_on_host(); + } + stk::mesh::communicate_field_data(stk_bulk(), const_fields); +} + +void +CDMesh::rebase_cdfem_displacements() +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::rebase_cdfem_displacements(void)"); /* %TRACE% */ + // rebase cdfem mesh displacements such that STATE_OLD is zero + const FieldRef cdfem_displacements_field = get_cdfem_displacements_field(); + if (cdfem_displacements_field.valid()) + { + const unsigned field_length = cdfem_displacements_field.length(); + std::vector stk_fields(cdfem_displacements_field.number_of_states()); + for ( unsigned is = 0 ; is < cdfem_displacements_field.number_of_states(); ++is ) + { + const stk::mesh::FieldState state = static_cast(is); + stk_fields[is] = cdfem_displacements_field.field_state(state); + } + + std::vector< stk::mesh::Entity> objs; + stk::mesh::get_selected_entities( stk::mesh::selectField(cdfem_displacements_field), stk_bulk().buckets( stk::topology::NODE_RANK ), objs ); + + std::vector old_displacement(field_length); + + const unsigned len = objs.size(); + for ( unsigned iObj(0); iObj < len; ++iObj ) + { + stk::mesh::Entity node = objs[iObj]; + + const double * old_data = field_data( stk_fields[stk::mesh::StateOld], node); + ThrowRequire(nullptr != old_data); + for (unsigned d=0; d( stk_fields[is], node); + ThrowRequire(nullptr != displacement); + for (unsigned d=0; d(cdfem_displacements_field, *b); + ThrowAssert(nullptr != cdfem_displacements); + + const unsigned field_length = cdfem_displacements_field.length(*b); + + const int num_nodes = b->size(); + for(int n=0; nprimary_entity_rank() == stk::topology::ELEMENT_RANK) + { + volume_conformal_parts.push_back(conformal_part); + } + else if (my_phase_support.is_interface(conformal_part)) + { + interfacial_conformal_parts.push_back(conformal_part); + } + } + + print_volume_or_surface_area(stk_bulk(), stk::topology::ELEMENT_RANK, get_active_part(), volume_conformal_parts); + print_volume_or_surface_area(stk_bulk(), stk_meta().side_rank(), get_active_part(), interfacial_conformal_parts); +} + +void +CDMesh::debug_output() const +{ + for (const auto & elem : elements) + debug_elem_parts_and_relations(stk_bulk(), *elem); + krinolog << stk::diag::dendl; + + for (auto && node : nodes) + debug_nodal_parts_and_fields(stk_bulk(), node.get()); + krinolog << stk::diag::dendl; + + debug_sides(stk_bulk(), get_active_part()); +} + +const Mesh_Element * CDMesh::find_mesh_element(stk::mesh::EntityId elemId, const std::vector> & searchElements) +{ + auto lb_cmp = [](const std::unique_ptr & elem, stk::mesh::EntityId target_id) { return elem->entityId() < target_id; }; + auto first = std::lower_bound(searchElements.begin(),searchElements.end(), elemId, lb_cmp); + + if (first != searchElements.end() && (*first)->entityId() == elemId) + { + return (*first).get(); + } + + return nullptr; +} + +} // namespace krino + + diff --git a/packages/krino/krino/krino_lib/Akri_CDMesh.hpp b/packages/krino/krino/krino_lib/Akri_CDMesh.hpp new file mode 100644 index 000000000000..39155fa144c0 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDMesh.hpp @@ -0,0 +1,333 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_CDMesh_h +#define Akri_CDMesh_h + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +template +class CompleteDecompositionFixture; +class SubElement; +class ElementObj; +class Mesh_Element; +class SubElementNode; +class SubElementMeshNode; +class ProlongationNodeData; +class ProlongationPointData; +class ProlongationElementData; +class ProlongationFacet; +class InterpolationEdge; +class LevelSet; +class Phase_Support; +class AuxMetaData; +struct SideRequest; +class InterfaceGeometry; + +typedef std::vector< const SubElementNode * > NodeVec; +typedef std::set< const SubElementNode * > NodeSet; +typedef std::vector< const ProlongationFacet * > ProlongFacetVec; +typedef std::unordered_map EntityProlongationNodeMap; +typedef std::unordered_map EntityProlongationElementMap; + +enum CDMeshStatus +{ + MESH_MODIFIED = 1, + COORDINATES_MAY_BE_MODIFIED = 2 +}; + +class CDMesh { +public: + + CDMesh( stk::mesh::BulkData & mesh, const std::shared_ptr & old_mesh); + + virtual ~CDMesh(); + + static void check_isovariable_field_existence_on_decomposed_blocks(stk::mesh::BulkData & mesh, + const bool conformal_parts_require_field); + static bool decomposition_needs_update(const InterfaceGeometry & interfaceGeometry, + const std::vector> & periodic_node_pairs); + static void handle_possible_failed_time_step( stk::mesh::BulkData & mesh, const int step_count ); + static int decompose_mesh( stk::mesh::BulkData & mesh, + const InterfaceGeometry & interfaceGeometry, + const int step_count, + const std::vector> & periodic_node_pairs ); + static void nonconformal_adaptivity(stk::mesh::BulkData & mesh, const InterfaceGeometry & interfaceGeometry); + static void mark_interface_elements_for_adaptivity(stk::mesh::BulkData & mesh, const InterfaceGeometry & interfaceGeometry, const std::string & marker_field_name, const int num_refinements); + void delete_cdfem_parent_elements(); + static void fixup_adapted_element_parts(stk::mesh::BulkData & mesh); + static void rebuild_from_restart_mesh(stk::mesh::BulkData & mesh); + void rebuild_after_rebalance(); + + CDMesh* get_old_mesh() { return my_old_mesh.get(); } + const CDMesh* get_old_mesh() const { return my_old_mesh.get(); } + static CDMesh* get_new_mesh() { return the_new_mesh.get(); } + + static void update_cdfem_constrained_nodes(); + void snap_and_update_fields_and_captured_domains(const InterfaceGeometry & interfaceGeometry, + NodeToCapturedDomainsMap & nodesToCapturedDomains) const; + + void communicate_prolongation_facet_fields() const; + const ProlongationPointData * find_prolongation_node(const SubElementNode & node) const; + const SubElementNode * find_node_with_common_ancestry(const SubElementNode * new_node) const; + + bool is_interface(const PhaseTag & phase, const InterfaceID interface) const; + PhaseTag determine_entity_phase(stk::mesh::Entity obj) const; + + void check_isovariable_field_existence_on_decomposed_blocks(const bool conformal_parts_require_field) const; + + std::vector get_nonconformal_elements() const; + void generate_nonconformal_elements(); + + bool triangulate(const InterfaceGeometry & interfaceGeometry); //return value indicates if any changes were made + void decompose(); + void cut_sharp_features(); + void snap_nearby_intersections_to_nodes(const InterfaceGeometry & interfaceGeometry, NodeToCapturedDomainsMap & domainsAtNodes); + void set_phase_of_uncut_elements(const InterfaceGeometry & interfaceGeometry); + + int spatial_dim() const { return my_spatial_dim; } + + const Phase_Support & get_phase_support() const { return my_phase_support; } + const CDFEM_Support & get_cdfem_support() const { return my_cdfem_support; } + CDFEM_Support & get_cdfem_support() { return my_cdfem_support; } + bool need_nodes_for_prolongation() const { return INTERPOLATION != get_prolongation_model() && my_stash_step_count >= 0; } + bool need_facets_for_prolongation() const { return ALE_NEAREST_POINT == get_prolongation_model() && my_stash_step_count >= 0; } + Prolongation_Model get_prolongation_model() const { return my_cdfem_support.get_prolongation_model(); } + Edge_Interpolation_Model get_edge_interpolation_model() const { return my_cdfem_support.get_edge_interpolation_model(); } + int num_ls_fields() const { return my_cdfem_support.num_ls_fields(); } + int get_ls_index(const LevelSet * ls) const { return my_cdfem_support.get_ls_index(ls); } + const LS_Field & ls_field(int i) const { return my_cdfem_support.ls_field(i); } + const std::vector & ls_fields() const { return my_cdfem_support.ls_fields(); } + const std::vector & all_interface_ids() const; + std::vector active_interface_ids() const; + // This should really only be used for unit test purposes. + void add_interface_id(const InterfaceID id) { crossing_keys.push_back(id); } + + const FieldRef get_coords_field() const { return my_cdfem_support.get_coords_field(); } + const FieldRef get_cdfem_displacements_field() const { return my_cdfem_support.get_cdfem_displacements_field(); } + const FieldSet & get_ale_prolongation_fields() const { return my_cdfem_support.get_ale_prolongation_fields(); } + const FieldSet & get_interpolation_fields() const { return my_cdfem_support.get_interpolation_fields(); } + const FieldSet & get_zeroed_fields() const { return my_cdfem_support.get_zeroed_fields(); } + const FieldSet & get_element_fields() const { return my_cdfem_support.get_element_fields(); } + const CDFEM_Snapper & get_snapper() const { return my_cdfem_support.get_snapper(); } + + const CDFEM_Inequality_Spec * get_death_spec(int ls_index) const { return my_cdfem_support.get_death_spec(ls_index); } + + stk::mesh::Part & get_parent_part() const { return my_cdfem_support.get_parent_part(); } + stk::mesh::Part & get_child_part() const { return my_cdfem_support.get_child_part(); } + stk::mesh::Part & get_internal_side_part() const { return my_cdfem_support.get_internal_side_part(); } + stk::mesh::Part & get_active_part() const; + stk::mesh::Part & get_locally_owned_part() const; + stk::mesh::Part & get_globally_shared_part() const; + stk::mesh::Part & get_block_boundary_part() const; + + // methods for finding or creating subelement nodes + const SubElementNode * create_mesh_node( const Mesh_Element * owner, + const int lnn, + stk::mesh::Entity nodeEntity ); + const SubElementNode * create_steiner_node( const Mesh_Element * owner, + const NodeVec & parents, + const std::vector & weights ); + const SubElementNode * create_child_internal_or_face_node( const Mesh_Element * owner, + const NodeVec & parents, + const std::vector & weights ); + const SubElementNode * create_edge_node( const Mesh_Element * owner, + const SubElementNode * parent1, const SubElementNode * parent2, + const double position); + const SubElementNode * create_midside_node( const Mesh_Element * owner, + const SubElementNode * parent1, const SubElementNode * parent2, stk::mesh::Entity entity = stk::mesh::Entity()); + void create_node_obj( const SubElementNode * node, NodeVec & parents ); + + void determine_node_signs(const InterfaceID & interface); + void decompose_edges(const InterfaceID & interface); + void determine_node_scores(const InterfaceID & interface); + + const AuxMetaData& aux_meta() const { return my_aux_meta; } + AuxMetaData& aux_meta() { return my_aux_meta; } + const stk::mesh::MetaData& stk_meta() const { return my_meta; } + stk::mesh::MetaData& stk_meta() { return my_meta; } + const stk::mesh::BulkData& stk_bulk() const { return my_meta.mesh_bulk_data(); } + stk::mesh::BulkData& stk_bulk() { return my_meta.mesh_bulk_data(); } + + void add_periodic_node_pair(stk::mesh::Entity node1, stk::mesh::Entity node2); + + // just search for existing mesh node and return NULL if not found + const SubElementMeshNode * get_mesh_node( stk::mesh::EntityId node_id ) const; + // just search for existing mesh node and return NULL if not found, starting with subelement node (possibly from another mesh) + const SubElementMeshNode * get_mesh_node(const SubElementNode * new_node) const; + + bool check_element_side_parts() const; + + const SubElement * find_child_element(stk::mesh::Entity elem_mesh_obj) const; + static const Mesh_Element * find_mesh_element(stk::mesh::EntityId elemId, const std::vector > & searchElements); + const Mesh_Element * find_mesh_element(stk::mesh::EntityId elemId) const { return find_mesh_element(elemId, elements); } + Mesh_Element * find_mesh_element(stk::mesh::EntityId elemId) { return const_cast(find_mesh_element(elemId, elements)); } // Scott Meyers says this is the right way + bool get_parent_child_coord_transformation(stk::mesh::Entity elem_mesh_obj, double * dParentdChild) const; + void get_parent_nodes_and_weights(stk::mesh::Entity child, stk::mesh::Entity & parent0, stk::mesh::Entity & parent1, double & position) const; + stk::mesh::Entity get_parent_element(stk::mesh::Entity elem_mesh_obj) const; + + double compute_cdfem_cfl() const; + + int stash_step_count() const { return my_stash_step_count; } + const ProlongationNodeData * fetch_prolong_node(stk::mesh::EntityId node_id) const { EntityProlongationNodeMap::const_iterator it = my_prolong_node_map.find(node_id); return ( (it == my_prolong_node_map.end()) ? NULL : (it->second) ); } + const ProlongationElementData * fetch_prolong_element(stk::mesh::EntityId elem_id) const { EntityProlongationElementMap::const_iterator it = my_prolong_element_map.find(elem_id); return ( (it == my_prolong_element_map.end()) ? NULL : (it->second) ); } + + void debug_output() const; + void print_conformal_volumes_and_surface_areas() const; + std::vector> & get_elements() { return elements; } + const std::vector> & get_elements() const { return elements; } + + // FIXME: Make these private. + SubElementNode * add_managed_node(std::unique_ptr node); + // expose vector of nodes + std::vector > nodes; + // expose vector of elements + std::vector > elements; + // expose vector of sorted child elements + std::vector child_elements; + +private: + //: Default constructor not allowed + CDMesh(); + + template + friend class CompleteDecompositionFixture; + + template + friend class AnalyticDecompositionFixture; + + void build_parallel_hanging_edge_nodes(); + void handle_hanging_children(const InterfaceID & interface); + void parallel_sync_nodes_on_interface(); + + void sync_node_signs_on_constrained_nodes(); + void parallel_sync_node_signs_on_shared_nodes(); + void sync_node_scores_on_constrained_nodes(); + void parallel_sync_node_scores_on_shared_nodes(); + + bool decomposition_has_changed(const InterfaceGeometry & interfaceGeometry); + bool elem_io_part_changed(const ElementObj & elem) const; + void determine_nonconformal_parts(stk::mesh::Entity entity, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const; + void determine_conformal_parts(stk::mesh::Entity entity, const PhaseTag & phase, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const; + void determine_conformal_parts(const stk::mesh::PartVector & current_parts, const stk::mesh::EntityRank entity_rank, const PhaseTag & phase, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const; + void determine_child_conformal_parts(stk::topology topology, const stk::mesh::PartVector & parent_parts, const PhaseTag & phase, stk::mesh::PartVector & child_parts) const; + void determine_element_side_parts(const stk::mesh::Entity side, stk::mesh::PartVector & add_parts, stk::mesh::PartVector & remove_parts) const; + bool element_side_should_be_active(const stk::mesh::Entity side) const; + void build_and_stash_old_mesh(const int stepCount); + void stash_field_data(const int step_count, const CDMesh & new_mesh) const; + void stash_nodal_field_data(const CDMesh & new_mesh) const; + void stash_elemental_field_data() const; + + void clear_prolongation_trees() const; + void build_prolongation_trees() const; + + void clear(); + void clear_prolongation_data() const; + + Mesh_Element * create_mesh_element(stk::mesh::Entity mesh_obj); + SubElementMeshNode * add_managed_mesh_node( std::unique_ptr node ); + + bool modify_mesh(); + void update_adaptivity_parent_entities(); + void handle_single_coincident_subelement(const Mesh_Element & elem, const SubElement * subelem, std::vector & side_requests); + void create_subelement_mesh_entities(const Mesh_Element & elem, + const std::vector conformal_subelems); + void attach_existing_and_identify_missing_subelement_sides(const Mesh_Element & elem, + const std::vector conformal_subelems, + std::vector & side_requests); + void update_uncut_element(const Mesh_Element & elem); + + void get_unused_old_child_elements(std::vector & unused_old_child_elems); + void set_entities_for_identical_nodes(); + bool set_entities_for_existing_child_elements(); + void create_new_mesh_entities(); + void create_node_entities(); + void create_element_and_side_entities(std::vector & side_requests); + + void prolongation(); + void rebase_cdfem_displacements(); + double get_maximum_cdfem_displacement() const; + + void add_possible_interface_sides(std::vector & sideRequests) const; + bool check_element_side_parts(const std::vector & side_nodes) const; + void update_element_side_parts(); + + void parallel_communicate_elemental_death_fields() const; + + void generate_sorted_child_elements(); + void cache_node_ids(); + + stk::mesh::Part & get_child_edge_node_part() const { return my_cdfem_support.get_child_edge_node_part(); } + FieldRef get_parent_node_ids_field() const { return my_cdfem_support.get_parent_node_ids_field(); } + + void rebuild_child_part(); + void rebuild_parent_and_active_parts_using_nonconformal_and_child_parts(); + void restore_subelement_edge_nodes(); + void restore_subelements(); + const SubElementNode * build_subelement_edge_node(const stk::mesh::Entity node_entity); + + stk::mesh::MetaData& my_meta; + AuxMetaData& my_aux_meta; + EntityIdPool my_entity_id_pool; + + const int my_spatial_dim; + CDFEM_Support & my_cdfem_support; + Phase_Support & my_phase_support; + typedef std::unordered_map NodeMap; + NodeMap mesh_node_map; + + std::shared_ptr my_old_mesh; + static std::shared_ptr the_new_mesh; + + std::unordered_map > my_periodic_node_id_map; + + mutable int my_stash_step_count; + mutable std::map,std::unique_ptr>> my_phase_prolong_tree_map; + + mutable bool my_missing_remote_prolong_facets; + mutable EntityProlongationNodeMap my_prolong_node_map; + mutable EntityProlongationElementMap my_prolong_element_map; + mutable ProlongFacetVec my_prolong_facets; + + mutable std::vector crossing_keys; + + mutable stk::diag::Timer my_timer_decompose; + mutable stk::diag::Timer my_timer_decomposition_has_changed; + mutable stk::diag::Timer my_timer_snap; + mutable stk::diag::Timer my_timer_stash_field_data; + mutable stk::diag::Timer my_timer_modify_mesh; + mutable stk::diag::Timer my_timer_prolongation; + mutable stk::diag::Timer my_timer_compute_CFL; + stk::mesh::PartVector my_attribute_parts; + + mutable std::map,const SubElementNode*> my_midside_node_map; +}; + +} // namespace krino + +#endif // Akri_CDMesh_h diff --git a/packages/krino/krino/krino_lib/Akri_CDMesh_Debug.cpp b/packages/krino/krino/krino_lib/Akri_CDMesh_Debug.cpp new file mode 100644 index 000000000000..eccd3249c341 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDMesh_Debug.cpp @@ -0,0 +1,229 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include + +namespace krino { + +void +debug_elem_parts_and_relations(const stk::mesh::BulkData & mesh, const Mesh_Element & elem) +{ + const stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + stk::mesh::Entity elem_obj = elem.entity(); + + krinolog << "Elem: id=" << mesh.identifier(elem_obj) << "\n"; + krinolog << " Mesh parts="; + for(auto && elem_part : mesh.bucket(elem_obj).supersets()) + { + krinolog << "\"" << elem_part->name() << "\"" << " "; + } + krinolog << "\n"; + + krinolog << " Nodes: "; + for (auto && elem_node : elem.get_nodes()) + { + krinolog << elem_node->entityId() << " "; + } + krinolog << "\n"; + + const stk::topology topology = elem.coord_topology(); + std::vector side_nodes; + for (unsigned s = 0; s < topology.num_sides(); ++s) + { + stk::mesh::Entity side_obj = find_entity_by_ordinal(mesh, elem_obj, meta.side_rank(), s); + if (!mesh.is_valid(side_obj)) continue; + krinolog << " Elem side: side=" << s << ", id=" << mesh.identifier(side_obj); + krinolog << ", side node ids: "; + const stk::topology side_topology = topology.side_topology(s); + const stk::mesh::Entity * elem_nodes_ptr = mesh.begin_nodes(elem_obj); + side_nodes.resize(side_topology.num_nodes()); + topology.side_nodes(elem_nodes_ptr, s, side_nodes.begin()); + for (unsigned n=0; nname() << "\"" << " "; + } + krinolog << "\n"; + } + + if (elem.have_subelements()) + { + std::vector conformal_subelems; + elem.get_subelements(conformal_subelems); + + for (auto && subelem : conformal_subelems) + { + stk::mesh::Entity subelem_obj = subelem->entity(); + if (!mesh.is_valid(subelem_obj)) + { + // single conformal subelement + if (1 == conformal_subelems.size()) + { + subelem_obj = elem.entity(); + } + if (!mesh.is_valid(subelem_obj)) + { + // For debugging intermediate stage before subelement mesh objects are determined. + krinolog << " Subelem: id=-1" << "\n"; + continue; + } + } + + krinolog << " Subelem: id=" << mesh.identifier(subelem_obj) << "\n"; + krinolog << " Mesh parts="; + for(auto && subelem_part : mesh.bucket(subelem_obj).supersets()) + { + krinolog << "\"" << subelem_part->name() << "\"" << " "; + } + krinolog << "\n"; + + krinolog << " SubElemNodes: "; + for (auto && sub_node : subelem->get_nodes()) + { + krinolog << sub_node->entityId() << " "; + } + krinolog << "\n"; + + for (unsigned s = 0; s < topology.num_sides(); ++s) + { + stk::mesh::Entity side_obj = find_entity_by_ordinal(mesh, subelem_obj, meta.side_rank(), s); + if (!mesh.is_valid(side_obj)) continue; + krinolog << " Subelem side: side=" << s << ", id=" << mesh.identifier(side_obj); + krinolog << ", side node ids: "; + const stk::topology side_topology = topology.side_topology(s); + const stk::mesh::Entity * elem_nodes_ptr = mesh.begin_nodes(subelem_obj); + side_nodes.resize(side_topology.num_nodes()); + topology.side_nodes(elem_nodes_ptr, s, side_nodes.begin()); + for (unsigned n=0; nname() << "\"" << " "; + } + krinolog << "\n"; + } + } + } +} + +void +debug_nodal_parts_and_fields(const stk::mesh::BulkData & mesh, const SubElementNode * node) +{ + stk::mesh::Entity node_obj = node->entity(); + if (!mesh.is_valid(node_obj)) + { + krinolog << "Node: identifier=nullptr\n"; + return; + } + + std::string type = "UNKNOWN"; + if (nullptr != dynamic_cast(node)) type = "internal"; + if (nullptr != dynamic_cast(node)) type = "edge"; + if (nullptr != dynamic_cast(node)) type = "mesh"; + + krinolog << "Node: identifier=" << mesh.identifier(node_obj) << ", type=" << type << "\n"; + if (mesh.mesh_meta_data().spatial_dimension() == 2) + { + krinolog << " coords =" << "(" << node->coordinates()[0] << ", " << node->coordinates()[1] << ")" << "\n"; + } + else if (mesh.mesh_meta_data().spatial_dimension() == 3) + { + krinolog << " coords =" << "(" << node->coordinates()[0] << ", " << node->coordinates()[1] << ", " << node->coordinates()[2] << ")" << "\n"; + } + krinolog << " Mesh parts="; + for(auto && node_part : mesh.bucket(node_obj).supersets()) + { + krinolog << "\"" << node_part->name() << "\"" << " "; + } + krinolog << "\n"; + + for ( auto && stk_field : mesh.mesh_meta_data().get_fields() ) + { + const FieldRef field(stk_field); + + if( field.entity_rank()!=stk::topology::NODE_RANK || !field.type_is() ) continue; + + const unsigned field_length = field.length(); + + double * data = field_data(field, node_obj); + if (nullptr == data) + { + krinolog << " Field: field_name=" << field.name() << ", field_state=" << field.state() << " -> NOT DEFINED." << "\n"; + } + else + { + if (1 == field_length) + { + krinolog << " Field: field_name=" << field.name() << ", field_state=" << field.state() << ", value=" << *data << "\n"; + } + else + { + for (unsigned i=0; i objs; + stk::mesh::get_entities( mesh, mesh.mesh_meta_data().side_rank(), objs ); + + const unsigned len = objs.size(); + for ( unsigned iObj(0); iObj < len; ++iObj ) { + + stk::mesh::Entity side = objs[iObj]; + + krinolog << "side, id=" << mesh.identifier(side) << ", active = " << mesh.bucket(side).member(active_part) << ", num elem = " << mesh.num_elements(side) << ", ioparts="; + const stk::mesh::PartVector & side_parts = mesh.bucket(side).supersets(); + for(stk::mesh::PartVector::const_iterator part_iter = side_parts.begin(); part_iter != side_parts.end(); ++part_iter) + { + const stk::mesh::Part * const part = *part_iter; + if (stk::io::is_part_io_part(*part)) krinolog << part->name() << " "; + } + krinolog << "\n"; + const unsigned num_side_elems = mesh.num_elements(side); + const stk::mesh::Entity* side_elems = mesh.begin_elements(side); + for (unsigned side_elem_index=0; side_elem_index +#include + +namespace krino { + void debug_elem_parts_and_relations(const stk::mesh::BulkData & mesh, const Mesh_Element & elem); + void debug_nodal_parts_and_fields(const stk::mesh::BulkData & mesh, const SubElementNode * node); + void debug_sides(const stk::mesh::BulkData & mesh, stk::mesh::Part & active_part); +} + +#endif /* KRINO_INCLUDE_AKRI_CDMESH_DEBUG_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.cpp b/packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.cpp new file mode 100644 index 000000000000..840750152089 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.cpp @@ -0,0 +1,367 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" +namespace krino { + +namespace { + void set_refine_if_not_reached_max_refine(stk::mesh::Entity elem, + const int interface_max_refine_level, + FieldRef elem_marker, + FieldRef refine_level_field, + FieldRef transition_element_field) + { + int & marker = *field_data(elem_marker, elem); + const int refine_level = *field_data(refine_level_field, elem); + const int transition_element = *field_data(transition_element_field, elem); + + if (refine_level >= transition_element+interface_max_refine_level ) + { + marker = Refinement_Marker::NOTHING; + } + else + { + marker = Refinement_Marker::REFINE; + } + } +} + +static bool node_is_snapped_to_interface(stk::mesh::Entity node, + const InterfaceID & interface, + const std::unordered_map> & nodesSnappedInterfaces) +{ + auto nodeSnappedInterfaces = nodesSnappedInterfaces.find(node); + const bool nodeIsSnapped = nodeSnappedInterfaces != nodesSnappedInterfaces.end() && + nodeSnappedInterfaces->second.find(interface) != nodeSnappedInterfaces->second.end(); + return nodeIsSnapped; +} + +void +refine_edges_with_multiple_unsnapped_crossings(const stk::mesh::BulkData& mesh, + const std::vector & edgeIntersections, + const int interface_max_refine_level, + FieldRef elem_marker_field, + FieldRef refine_level_field, + FieldRef transition_element_field, + const std::unordered_map> & nodesSnappedInterfaces) +{ + // Refine any edge with multiple crossings that are not snapped to the nodes + std::set> edgesWithUnsnappedCrossings; + for (auto && edgeIntersection : edgeIntersections) + { + const EdgeIntersection edge(edgeIntersection); + + const InterfaceID & interface = edge.interface; + const bool node1IsSnapped = node_is_snapped_to_interface(edge.nodes[0], interface, nodesSnappedInterfaces); + const bool node2IsSnapped = node_is_snapped_to_interface(edge.nodes[1], interface, nodesSnappedInterfaces); + if (!node1IsSnapped && !node2IsSnapped) + { + const auto insertion = edgesWithUnsnappedCrossings.insert(edge.nodes); + const bool alreadyInSet = !insertion.second; + if (alreadyInSet) + { + std::vector edge_elems; + stk::mesh::get_entities_through_relations(mesh, {edge.nodes[0], edge.nodes[1]}, + stk::topology::ELEMENT_RANK, edge_elems); + for (auto && elem : edge_elems) + { + set_refine_if_not_reached_max_refine(elem, interface_max_refine_level, elem_marker_field, refine_level_field, transition_element_field); + } + } + } + } +} + +void +refine_edges_with_nodes_with_multiple_snapped_interfaces(const stk::mesh::BulkData& mesh, + const std::vector & edgeIntersections, + const int interface_max_refine_level, + FieldRef elem_marker_field, + FieldRef refine_level_field, + FieldRef transition_element_field, + const std::unordered_map> & node_snapped_interfaces) +{ + for (auto && edgeIntersection : edgeIntersections) + { + const EdgeIntersection edge(edgeIntersection); + + auto node1_snapped_interfaces = node_snapped_interfaces.find(edge.nodes[0]); + auto node2_snapped_interfaces = node_snapped_interfaces.find(edge.nodes[1]); + const unsigned num_node1_interfaces = (node1_snapped_interfaces != node_snapped_interfaces.end()) ? + node1_snapped_interfaces->second.size() : 0; + const unsigned num_node2_interfaces = (node2_snapped_interfaces != node_snapped_interfaces.end()) ? + node2_snapped_interfaces->second.size() : 0; + if (num_node1_interfaces > 1 || num_node2_interfaces > 1) + { + std::vector edge_elems; + stk::mesh::get_entities_through_relations(mesh, {edge.nodes[0], edge.nodes[1]}, + stk::topology::ELEMENT_RANK, edge_elems); + for (auto && elem : edge_elems) + { + set_refine_if_not_reached_max_refine(elem, interface_max_refine_level, elem_marker_field, refine_level_field, transition_element_field); + } + } + } +} + +void +determine_which_interfaces_snap_to_each_node_and_unsnappable_nodes(const stk::mesh::BulkData& mesh, + const std::vector & edgeIntersections, + const std::vector & active_interface_ids, + const CDFEM_Snapper & snapper, + FieldRef node_marker_field, + std::unordered_map> & node_snapped_interfaces, + std::set & unsnappable_nodes) +{ + for (auto && interface : active_interface_ids) + { + stk::mesh::field_fill(0, node_marker_field); + for (auto && edgeIntersection : edgeIntersections) + { + const EdgeIntersection edge(edgeIntersection); + + int & node1_marker = *field_data(node_marker_field, edge.nodes[0]); + int & node2_marker = *field_data(node_marker_field, edge.nodes[1]); + if (snapper.snap_lo(edge.crossingLocation)) + { + if (parts_are_compatible_for_snapping(mesh, edge.nodes[0], edge.nodes[1])) + node1_marker = 2; + else + node1_marker = std::max(node1_marker, 1); + } + if (snapper.snap_hi(edge.crossingLocation)) + { + if (parts_are_compatible_for_snapping(mesh, edge.nodes[1], edge.nodes[0])) + node2_marker = 2; + else + node2_marker = std::max(node2_marker, 1); + } + } + stk::mesh::parallel_max(mesh, {&node_marker_field.field()}); + + for(const auto & b_ptr : mesh.buckets( stk::topology::NODE_RANK )) + { + stk::mesh::Bucket & b = *b_ptr; + int * node_marker = field_data(node_marker_field, b); + for(unsigned i=0; i < b.size(); ++i) + { + if (node_marker[i] == 1) + { + unsnappable_nodes.insert(b[i]); + } + else if (node_marker[i] == 2) + { + node_snapped_interfaces[b[i]].insert(interface); + } + } + } + } +} + +void +resolve_fine_features(const stk::mesh::BulkData& mesh, + const std::vector & edgeIntersections, + const std::vector & active_interface_ids, + const CDFEM_Snapper & snapper, + const int interface_max_refine_level, + FieldRef elem_marker_field, + FieldRef node_marker_field, + FieldRef refine_level_field, + FieldRef transition_element_field) +{ + std::unordered_map> node_snapped_interfaces; + std::set unsnappable_nodes; + + determine_which_interfaces_snap_to_each_node_and_unsnappable_nodes(mesh, edgeIntersections, active_interface_ids, snapper, node_marker_field, node_snapped_interfaces, unsnappable_nodes); + + // Attempt at minimally aggressive. Works pretty well and has has lower element counts. + refine_edges_with_multiple_unsnapped_crossings(mesh, edgeIntersections, interface_max_refine_level, elem_marker_field, refine_level_field, transition_element_field, node_snapped_interfaces); + //refine_edges_with_unsnappable_nodes(interface_max_refine_level, elem_marker_field, refine_level_field, transition_element_field, unsnappable_nodes); + refine_edges_with_nodes_with_multiple_snapped_interfaces(mesh, edgeIntersections, interface_max_refine_level, elem_marker_field, refine_level_field, transition_element_field, node_snapped_interfaces); +} + +void +mark_nearest_node_on_cut_edges(const stk::mesh::BulkData& mesh, + const std::vector & edgeIntersections, + FieldRef node_marker_field) +{ + const double overlap = 0.25; + + stk::mesh::field_fill(0, node_marker_field); + + for (auto && edgeIntersection : edgeIntersections) + { + const EdgeIntersection edge(edgeIntersection); + + if (edge.crossingLocation < 0.5+overlap) + *field_data(node_marker_field, edge.nodes[0]) = 1; + if (edge.crossingLocation > 0.5-overlap) + *field_data(node_marker_field, edge.nodes[1]) = 1; + } + stk::mesh::parallel_max(mesh, {&node_marker_field.field()}); +} + +void +mark_interface_elements_for_adaptivity(const stk::mesh::BulkData& mesh, + const InterfaceGeometry & interfaceGeometry, + const std::vector & active_interface_ids, + const CDFEM_Snapper & snapper, + const AuxMetaData& aux_meta, + const CDFEM_Support & cdfem_support, + const FieldRef coords_field, + const std::string & marker_field_name, + const int num_refinements) +{ +/* %TRACE[SPEC]% */ Tracespec trace__("CDMesh::mark_interface_elements_for_adaptivity(const std::string & marker_field_name, const int num_refinements)"); /* %TRACE% */ + + // This refinement strategy cuts elements by the user-specified number of adapt levels + // before the conformal decomposition. + + const FieldRef elem_marker = aux_meta.get_field(stk::topology::ELEMENT_RANK, marker_field_name, stk::mesh::StateNew); + const FieldRef refine_level_field = aux_meta.get_field(stk::topology::ELEMENT_RANK, "refine_level"); + const std::string transition_field_name = (mesh.mesh_meta_data().spatial_dimension() == 2) ? + "transition_element" : "transition_element_3"; + const FieldRef transition_element_field = aux_meta.get_field(stk::topology::ELEMENT_RANK, transition_field_name); + + const stk::mesh::Selector active_selector(cdfem_support.get_active_part()); + const stk::mesh::Selector locally_owned_selector(cdfem_support.get_locally_owned_part()); + const stk::mesh::Selector parent_or_child_selector = cdfem_support.get_child_part() | cdfem_support.get_parent_part(); + const int interface_min_refine_level = cdfem_support.get_interface_minimum_refinement_level(); + std::vector entities; + std::vector children; + + const NodeToCapturedDomainsMap nodesToCapturedDomains; + const std::vector edgeIntersections = interfaceGeometry.get_edge_intersection_points(mesh, nodesToCapturedDomains); + + FieldRef node_marker_field = aux_meta.get_field(stk::topology::NODE_RANK, marker_field_name, stk::mesh::StateNew); + mark_nearest_node_on_cut_edges(mesh, edgeIntersections, node_marker_field); + + std::vector min_edge_lengths(cdfem_support.get_interface_maximum_refinement_level()+1, std::numeric_limits::max()); + std::vector max_edge_lengths(cdfem_support.get_interface_maximum_refinement_level()+1, 0.0); + + const std::vector debugElementIds{}; + + stk::mesh::get_selected_entities( locally_owned_selector, mesh.buckets( stk::topology::ELEMENT_RANK ), entities ); + for( auto&& elem : entities ) + { + int & marker = *field_data(elem_marker, elem); + + bool has_crossing = false; + const stk::topology stk_topology = mesh.bucket(elem).topology(); + const unsigned num_nodes = stk_topology.num_nodes(); + const stk::mesh::Entity * const elem_nodes = mesh.begin(elem, stk::topology::NODE_RANK); + for(unsigned i=0; i < num_nodes; ++i) + { + const int node_marker = *field_data(node_marker_field, elem_nodes[i]); + if(node_marker) + { + has_crossing = true; + break; + } + } + + const int target_refine_level = has_crossing ? interface_min_refine_level : 0; + const int refine_level = *field_data(refine_level_field, elem); + const int transition_element = *field_data(transition_element_field, elem); + + if (!transition_element) + { + std::vector edge_nodes; + for (unsigned i = 0; i < stk_topology.num_edges(); ++i) + { + edge_nodes.resize(stk_topology.edge_topology(i).num_nodes()); + stk_topology.edge_nodes(elem_nodes, i, edge_nodes.data()); + + const Vector3d edge_node1_coords(field_data(coords_field, edge_nodes[0]), mesh.mesh_meta_data().spatial_dimension()); + const Vector3d edge_node2_coords(field_data(coords_field, edge_nodes[1]), mesh.mesh_meta_data().spatial_dimension()); + const double length = (edge_node2_coords-edge_node1_coords).length(); + + min_edge_lengths[refine_level] = std::min(min_edge_lengths[refine_level], length); + max_edge_lengths[refine_level] = std::max(max_edge_lengths[refine_level], length); + } + } + + if (!debugElementIds.empty() && std::find(debugElementIds.begin(), debugElementIds.end(), mesh.identifier(elem)) != debugElementIds.end()) + krinolog << "Considering refinement of element " << mesh.identifier(elem) << " " << has_crossing << " " << target_refine_level << " " << refine_level << " " << transition_element << stk::diag::dendl; + + if (refine_level < target_refine_level+transition_element) + { + marker = Refinement_Marker::REFINE; + } + else if (num_refinements < interface_min_refine_level && + refine_level > target_refine_level+transition_element) + { + // Stop coarsening after num_levels of refinement to avoid infinite looping + // from the interface position moving between elements because of snapping changes + // with refinement + marker = Refinement_Marker::COARSEN; + } + else + { + marker = Refinement_Marker::NOTHING; + } + } + + { + for (int i=0; i interface_min_refine_level) + { + resolve_fine_features(mesh, edgeIntersections, active_interface_ids, snapper, interface_max_refine_level, elem_marker, node_marker_field, refine_level_field, transition_element_field); + } +} + +void +refine_edges_with_unsnappable_nodes(const stk::mesh::BulkData& mesh, + const std::vector & edgeIntersections, + const CDFEM_Snapper & snapper, + const int interface_max_refine_level, + FieldRef elem_marker_field, + FieldRef refine_level_field, + FieldRef transition_element_field, + std::set unsnappable_nodes) +{ + for (auto && edgeIntersection : edgeIntersections) + { + const EdgeIntersection edge(edgeIntersection); + + if ((snapper.snap_lo(edge.crossingLocation) && unsnappable_nodes.find(edge.nodes[0]) != unsnappable_nodes.end()) || + (snapper.snap_hi(edge.crossingLocation) && unsnappable_nodes.find(edge.nodes[1]) != unsnappable_nodes.end())) + { + krinolog << "Refining unsnappable edge " << mesh.identifier(edge.nodes[0]) << " " << mesh.identifier(edge.nodes[1]) << " " << debug_output(mesh, edgeIntersection) << stk::diag::dendl; + std::vector edge_elems; + stk::mesh::get_entities_through_relations(mesh, {edge.nodes[0], edge.nodes[1]}, + stk::topology::ELEMENT_RANK, edge_elems); + for (auto && elem : edge_elems) + { + set_refine_if_not_reached_max_refine(elem, interface_max_refine_level, elem_marker_field, refine_level_field, transition_element_field); + } + } + } +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.hpp b/packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.hpp new file mode 100644 index 000000000000..0f7725594ad9 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDMesh_Refinement.hpp @@ -0,0 +1,31 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_CDMESH_REFINEMENT_H_ +#define KRINO_INCLUDE_AKRI_CDMESH_REFINEMENT_H_ +#include +#include +#include +#include + +namespace krino { + +void +mark_interface_elements_for_adaptivity(const stk::mesh::BulkData& mesh, + const InterfaceGeometry & interfaceGeometry, + const std::vector & active_interface_ids, + const CDFEM_Snapper & snapper, + const AuxMetaData& aux_meta, + const CDFEM_Support & cdfem_support, + const FieldRef coords_field, + const std::string & marker_field_name, + const int num_refinements); + +} + +#endif /* KRINO_INCLUDE_AKRI_CDMESH_REFINEMENT_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_CDMesh_Utils.cpp b/packages/krino/krino/krino_lib/Akri_CDMesh_Utils.cpp new file mode 100644 index 000000000000..717cf7a0f255 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDMesh_Utils.cpp @@ -0,0 +1,143 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +namespace krino { + +bool +parts_are_compatible_for_snapping(const stk::mesh::BulkData & mesh, stk::mesh::Entity possible_snap_node, stk::mesh::Entity fixed_node) +{ + const stk::mesh::PartVector & possible_snap_node_parts = mesh.bucket(possible_snap_node).supersets(); + const stk::mesh::PartVector & fixed_node_parts = mesh.bucket(fixed_node).supersets(); + for (auto && possible_snap_node_part : possible_snap_node_parts) + { + if ((possible_snap_node_part->primary_entity_rank() == stk::topology::ELEMENT_RANK || + possible_snap_node_part->primary_entity_rank() == mesh.mesh_meta_data().side_rank()) && + !stk::mesh::is_auto_declared_part(*possible_snap_node_part) && + possible_snap_node_part->name().compare(0,7,"refine_") != 0 && + !stk::mesh::contain(fixed_node_parts, *possible_snap_node_part)) + { + return false; + } + } + return true; +} + +static stk::mesh::Part * get_nonconformal_part(const Phase_Support & phaseSupport, stk::mesh::Part * part) +{ + return const_cast(phaseSupport.find_nonconformal_part(*part)); +} + +static bool is_part_to_check(const Phase_Support & phaseSupport, const AuxMetaData & auxMeta, const stk::mesh::Part & part) +{ + const stk::mesh::Part & exposedBoundaryPart = auxMeta.exposed_boundary_part(); + return part.primary_entity_rank() != stk::topology::INVALID_RANK && + (&part == &exposedBoundaryPart || stk::io::is_part_io_part(part)) && + part.name().compare(0,7,"refine_") != 0 && + !phaseSupport.is_interface(&part); +} + +static stk::mesh::PartVector get_nonconformal_parts_to_check(const AuxMetaData & auxMeta, const Phase_Support & phaseSupport, const stk::mesh::PartVector & inputParts) +{ + stk::mesh::PartVector partsToCheck; + partsToCheck.reserve(inputParts.size()); + for (auto && part : inputParts) + if (is_part_to_check(phaseSupport, auxMeta, *part)) + partsToCheck.push_back(get_nonconformal_part(phaseSupport, part)); + stk::util::sort_and_unique(partsToCheck, stk::mesh::PartLess()); + return partsToCheck; +} + +bool +parts_are_compatible_for_snapping_when_ignoring_phase(const stk::mesh::BulkData & mesh, const AuxMetaData & auxMeta, const Phase_Support & phaseSupport, stk::mesh::Entity possibleSnapNode, stk::mesh::Entity fixedNode) +{ + const stk::mesh::PartVector & possibleSnapNodeParts = mesh.bucket(possibleSnapNode).supersets(); + const stk::mesh::PartVector nonconformalPartsToCheck = get_nonconformal_parts_to_check(auxMeta, phaseSupport, mesh.bucket(fixedNode).supersets()); + for (auto && possibleSnapNodePart : possibleSnapNodeParts) + { + if (is_part_to_check(phaseSupport, auxMeta, *possibleSnapNodePart)) + { + stk::mesh::Part * nonconformalPart = get_nonconformal_part(phaseSupport, possibleSnapNodePart); + if (!stk::mesh::contain(nonconformalPartsToCheck, *nonconformalPart)) + return false; + } + } + return true; +} + +bool phase_matches_interface(const CDFEM_Support & cdfemSupport, const PhaseTag & phase, const InterfaceID interface) +{ + if(cdfemSupport.num_ls_fields() > 1 && Phase_Support::has_one_levelset_per_phase()) + { + return (phase.contain(cdfemSupport.ls_field(interface.first_ls()).identifier, -1) && + phase.contain(cdfemSupport.ls_field(interface.second_ls()).identifier, -1)); + } + return (phase.contain(cdfemSupport.ls_field(interface.first_ls()).identifier, -1) && + phase.contain(cdfemSupport.ls_field(interface.first_ls()).identifier, +1)); +} + +bool determine_phase_from_parts(PhaseTag & phase, const stk::mesh::PartVector & parts, const Phase_Support & phaseSupport) +{ + ThrowAssert(phase.empty()); + bool has_conformal_ioparts = false; + + for (auto && part : parts) + { + if (part->primary_entity_rank() != stk::topology::ELEMENT_RANK || // limit ourselves to phase-specific volumes + !(stk::io::is_part_io_part(*part) || + phaseSupport.is_nonconformal(part))) + continue; + + const PhaseTag & iopart_phase = phaseSupport.get_iopart_phase(*part); + + if (!iopart_phase.empty()) + has_conformal_ioparts = true; + + phase.add(iopart_phase); + } + + return (has_conformal_ioparts); +} + +PhaseTag determine_phase_for_entity(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const Phase_Support & phaseSupport) +{ + PhaseTag phase; + const stk::mesh::PartVector & parts = mesh.bucket(entity).supersets(); + determine_phase_from_parts(phase, parts, phaseSupport); + return phase; +} + +bool node_is_on_interface(const stk::mesh::BulkData & mesh, const CDFEM_Support & cdfemSupport, const Phase_Support & phaseSupport, stk::mesh::Entity node, const InterfaceID & interface) +{ + const PhaseTag nodePhase = determine_phase_for_entity(mesh, node, phaseSupport); + return phase_matches_interface(cdfemSupport, nodePhase, interface); +} + +bool nodes_are_on_any_interface(const stk::mesh::BulkData & mesh, const Phase_Support & phaseSupport, const stk::mesh::Bucket & nodeBucket) +{ + auto side_rank = mesh.mesh_meta_data().side_rank(); + for(auto && part : nodeBucket.supersets()) + if(part->primary_entity_rank() == side_rank && stk::io::is_part_io_part(*part) && phaseSupport.is_interface(part)) + return true; + return false; +} + +bool node_is_on_any_interface(const stk::mesh::BulkData & mesh, const Phase_Support & phaseSupport, const stk::mesh::Entity node) +{ + return nodes_are_on_any_interface(mesh, phaseSupport, mesh.bucket(node)); +} + +} + + diff --git a/packages/krino/krino/krino_lib/Akri_CDMesh_Utils.hpp b/packages/krino/krino/krino_lib/Akri_CDMesh_Utils.hpp new file mode 100644 index 000000000000..a3007222587b --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_CDMesh_Utils.hpp @@ -0,0 +1,34 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_CDMESH_UTILS_H_ +#define KRINO_INCLUDE_AKRI_CDMESH_UTILS_H_ +#include + +namespace krino { + +class CDFEM_Support; +class PhaseTag; +class InterfaceID; +class AuxMetaData; +class Phase_Support; + +bool parts_are_compatible_for_snapping(const stk::mesh::BulkData & mesh, stk::mesh::Entity possible_snap_node, stk::mesh::Entity fixed_node); +bool parts_are_compatible_for_snapping_when_ignoring_phase(const stk::mesh::BulkData & mesh, const AuxMetaData & auxMeta, const Phase_Support & phaseSupport, stk::mesh::Entity possible_snap_node, stk::mesh::Entity fixed_node); +bool phase_matches_interface(const CDFEM_Support & cdfemSupport, const PhaseTag & phase, const InterfaceID interface); +bool determine_phase_from_parts(PhaseTag & phase, const stk::mesh::PartVector & parts, const Phase_Support & phaseSupport); +PhaseTag determine_phase_for_entity(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const Phase_Support & phaseSupport); +bool nodes_are_on_any_interface(const stk::mesh::BulkData & mesh, const Phase_Support & phaseSupport, const stk::mesh::Bucket & nodeBucket); +bool node_is_on_any_interface(const stk::mesh::BulkData & mesh, const Phase_Support & phaseSupport, const stk::mesh::Entity node); +bool node_is_on_interface(const stk::mesh::BulkData & mesh, const CDFEM_Support & cdfemSupport, const Phase_Support & phaseSupport, stk::mesh::Entity node, const InterfaceID & interface); + +} + + + +#endif /* KRINO_INCLUDE_AKRI_CDMESH_UTILS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_Composite_Surface.cpp b/packages/krino/krino/krino_lib/Akri_Composite_Surface.cpp new file mode 100644 index 000000000000..941580cc6e98 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Composite_Surface.cpp @@ -0,0 +1,108 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include + +namespace krino{ + +Composite_Surface::Composite_Surface(const std::string & sn) + : SurfaceThatTakesAdvantageOfNarrowBandAndThereforeMightHaveWrongSign(), + my_name(sn), + my_composition_method(MINIMUM_SIGNED_DISTANCE) {} + +Composite_Surface::~Composite_Surface() {} + +void +Composite_Surface::prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) +{ /* %TRACE[ON]% */ Trace trace__("krino::Composite_Surface::prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length)"); /* %TRACE% */ + + for ( auto&& surface : my_subsurfaces ) + { + surface->prepare_to_compute(time, point_bbox, truncation_length); + } + + if (truncation_length > 0.) + { + BoundingBox padded_point_bbox = point_bbox; + padded_point_bbox.pad(truncation_length); + + SurfaceAutoVec nearby_surfaces; + for ( auto&& surface : my_subsurfaces ) + { + // keep nearby or moving surfaces + if (FACETED_SURFACE == surface->type() || + nullptr != surface->get_transformation() || + point_bbox.intersects(surface->get_bounding_box())) + { + nearby_surfaces.emplace_back(std::move(surface)); + } + } + nearby_surfaces.swap(my_subsurfaces); + } + + int subsurfaces_might_produce_wrong_sign = false; + for ( auto&& surface : my_subsurfaces ) + { + if (surface->truncated_distance_may_have_wrong_sign()) + { + subsurfaces_might_produce_wrong_sign = true; + break; + } + } + int local_subsurfaces_might_produce_wrong_sign = subsurfaces_might_produce_wrong_sign; + stk::all_reduce_max(stk::EnvData::parallel_comm(), &local_subsurfaces_might_produce_wrong_sign, &subsurfaces_might_produce_wrong_sign, 1); + my_subsurfaces_might_produce_wrong_sign = subsurfaces_might_produce_wrong_sign; +} + +double +Composite_Surface::truncated_point_signed_distance(const Vector3d &x, const double narrow_band, const double far_field_value) const +{ /* %TRACE% */ /* %TRACE% */ + + if (my_composition_method == MINIMUM_SIGNED_DISTANCE) + { + ThrowRequireMsg(far_field_value >= narrow_band, "Composite surfaces have a specific requirement for far_field_value due to min/max operations."); + double dist = (narrow_band == 0.) ? std::numeric_limits::max() : far_field_value; + for ( auto&& surface : my_subsurfaces ) + { + dist = std::min(dist, surface->truncated_point_signed_distance(x, narrow_band, far_field_value)); + } + return dist; + } + else + { + ThrowRequire(my_composition_method == MAXIMUM_SIGNED_DISTANCE); + ThrowRequireMsg(far_field_value <= -narrow_band, "Composite surfaces have a specific requirement for far_field_value due to min/max operations."); + double dist = (narrow_band == 0.) ? -std::numeric_limits::max() : far_field_value; + for ( auto&& surface : my_subsurfaces ) + { + dist = std::max(dist, surface->truncated_point_signed_distance(x, narrow_band, far_field_value)); + } + return dist; + } +} + +BoundingBox +Composite_Surface::get_bounding_box() +{ + BoundingBox bbox; + for (auto && subsurf : my_subsurfaces) + { + bbox.accommodate(subsurf->get_bounding_box()); + } + return bbox; +} + + +} // namespace krino + + + diff --git a/packages/krino/krino/krino_lib/Akri_Composite_Surface.hpp b/packages/krino/krino/krino_lib/Akri_Composite_Surface.hpp new file mode 100644 index 000000000000..92dfe0ba31c0 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Composite_Surface.hpp @@ -0,0 +1,64 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Composite_Surface_h +#define Akri_Composite_Surface_h + +#include + +namespace krino { + +class SurfaceTree; +class Transformation; + +class Composite_Surface : public SurfaceThatTakesAdvantageOfNarrowBandAndThereforeMightHaveWrongSign { + +public: + Composite_Surface(const std::string & sn); + virtual ~Composite_Surface(); + + enum CompositionMethod{ MINIMUM_SIGNED_DISTANCE, MAXIMUM_SIGNED_DISTANCE }; + + virtual Surface_Type type() const override { return COMPOSITE_SURFACE; } + virtual void prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) override; + virtual size_t storage_size() const override { size_t tot_size = 0; for (auto && surf : my_subsurfaces) tot_size += surf->storage_size(); return tot_size; } + + // query/modify subsurfaces + void add( Surface * surf ) { my_subsurfaces.emplace_back(surf); } + void reserve(unsigned size_) { my_subsurfaces.reserve(size_); } + unsigned size() const { return my_subsurfaces.size(); } + Surface * operator()( const unsigned index ) const { return my_subsurfaces[index].get(); } + void clear() { my_subsurfaces.clear(); } + void swap(Composite_Surface & other) { my_subsurfaces.swap(other.my_subsurfaces); } + SurfaceAutoVec & get_surfaces() { return my_subsurfaces; } + const SurfaceAutoVec & get_surfaces() const { return my_subsurfaces; } + + virtual void set_transformation(Transformation * trans) override { Surface::set_transformation(trans); for (auto && subsurf : my_subsurfaces) subsurf->set_transformation(trans);} + void set_composition_method(const CompositionMethod composition_method) { my_composition_method = composition_method; } + virtual BoundingBox get_bounding_box() override; + virtual double truncated_point_signed_distance(const Vector3d &x, const double truncation_length, const double far_field_value) const override; + double point_signed_distance_with_narrow_band(const Vector3d &x, const double narrow_band) const + { + // Special to this class, this version of point_signed_distance automatically determines the far_field_value from the composition_method + return truncated_point_signed_distance(x, narrow_band, get_signed_narrow_band_size(narrow_band)); + } + + virtual bool truncated_distance_may_have_wrong_sign() const override { return my_subsurfaces_might_produce_wrong_sign; } + double get_signed_narrow_band_size(const double pos_narrow_band) const { return (my_composition_method == MINIMUM_SIGNED_DISTANCE) ? pos_narrow_band : -pos_narrow_band; } + +protected: + std::string my_name; + CompositionMethod my_composition_method; + SurfaceAutoVec my_subsurfaces; + bool my_subsurfaces_might_produce_wrong_sign = true; +}; + +} // namespace krino + + +#endif // Akri_Composite_Surface_h diff --git a/packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.cpp b/packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.cpp new file mode 100644 index 000000000000..0c5b01db83b9 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.cpp @@ -0,0 +1,122 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino{ + +void +Compute_Surface_Distance::calculate( + const stk::mesh::BulkData & mesh, + const stk::diag::Timer &parent_timer, + const stk::mesh::Field& coordinates, + const stk::mesh::Field& distance, + const stk::mesh::Selector & surface_selector, + const double narrowBandSize, + const double farFieldValue) +{ + calculate(mesh, parent_timer, coordinates, distance, mesh.mesh_meta_data().universal_part(), surface_selector, narrowBandSize, farFieldValue); +} + +static BoundingBox compute_nodal_bounding_box(const stk::mesh::BulkData & mesh, + const stk::mesh::Field& coordinates, + const stk::mesh::Field& distance, + const stk::mesh::Selector & volume_selector) +{ + const int spatial_dimension = mesh.mesh_meta_data().spatial_dimension(); + + // The bounding box for that contains all the nodes on that proc + BoundingBox nodeBbox; + for ( auto && bucket : mesh.get_buckets(stk::topology::NODE_RANK, volume_selector & stk::mesh::selectField(distance)) ) + { + const size_t length = bucket->size(); + double *coord = stk::mesh::field_data(coordinates, *bucket); + + for (size_t n = 0; n < length; ++n) + nodeBbox.accommodate( Vector3d(coord+n*spatial_dimension, spatial_dimension) ); + } + + return nodeBbox; +} + +static void compute_distance_to_facets(const stk::mesh::BulkData & mesh, + const MeshSurface & facet_list, + const stk::mesh::Field& coordinates, + const stk::mesh::Field& distance, + const stk::mesh::Selector & volume_selector, + const double narrowBandSize, + const double userFarFieldValue) +{ + const int spatial_dimension = mesh.mesh_meta_data().spatial_dimension(); + const double farFieldValue = (userFarFieldValue > 0.0) ? userFarFieldValue : narrowBandSize; // Use farFieldValue, if specified, otherwise, use narrowBandSize + for ( auto && bucket : mesh.get_buckets(stk::topology::NODE_RANK, volume_selector & stk::mesh::selectField(distance)) ) + { + const size_t length = bucket->size(); + double *dist = stk::mesh::field_data(distance, *bucket); + double * coord = stk::mesh::field_data(coordinates, *bucket); + + for (size_t n = 0; n < length; ++n) + { + ThrowAssert(&(dist[n]) != NULL); + + const Vector3d xvec(coord+n*spatial_dimension, spatial_dimension); + dist[n] = facet_list.point_unsigned_distance(xvec, narrowBandSize, farFieldValue); + } + } +} + +void print_facet_info(const MeshSurface & facet_list, stk::ParallelMachine parallel) +{ + constexpr int vec_size = 3; + std::array local_sizes, global_min, global_max; + local_sizes[0] = facet_list.storage_size(); + local_sizes[1] = facet_list.size(); + local_sizes[2] = facet_list.size()+facet_list.nonlocal_size(); + + stk::all_reduce_min( parallel, local_sizes.data(), global_min.data(), vec_size ); + stk::all_reduce_max( parallel, local_sizes.data(), global_max.data(), vec_size ); + + krinolog << "Compute Surface Distance: "<< stk::diag::dendl; + krinolog << " Local facet count: min=" << global_min[1] << ", max=" << global_max[1] << stk::diag::dendl; + krinolog << " Total facet count: min=" << global_min[2] << ", max=" << global_max[2] << stk::diag::dendl; + krinolog << " Memory usage (mb): min=" << global_min[0]/(1024.0*1024.0) << ", max=" << global_max[0]/(1024.0*1024.0) << stk::diag::dendl; +} + + +void +Compute_Surface_Distance::calculate( + const stk::mesh::BulkData & mesh, + const stk::diag::Timer &parent_timer, + const stk::mesh::Field& coordinates, + const stk::mesh::Field& distance, + const stk::mesh::Selector & volume_selector, + const stk::mesh::Selector & surface_selector, + const double narrowBandSize, + const double farFieldValue) +{ /* %TRACE[ON]% */ Trace trace__("krino::Compute_Surface_Distance::compute_surface_distance(void)"); /* %TRACE% */ + + stk::diag::Timer timer( "Compute Surface Distance", parent_timer ); + stk::diag::TimeBlock timer_(timer); + + MeshSurface facet_list(mesh.mesh_meta_data(), coordinates, surface_selector, +1); + const BoundingBox nodeBbox = compute_nodal_bounding_box(mesh, coordinates, distance, volume_selector); + facet_list.prepare_to_compute(0.0, nodeBbox, narrowBandSize); // Setup including communication of facets that are within this processors narrow band + + print_facet_info(facet_list, mesh.parallel()); + + compute_distance_to_facets(mesh, facet_list, coordinates, distance, volume_selector, narrowBandSize, farFieldValue); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.hpp b/packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.hpp new file mode 100644 index 000000000000..6795cd4002f5 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Compute_Surface_Distance.hpp @@ -0,0 +1,42 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Compute_Surface_Distance_h +#define Akri_Compute_Surface_Distance_h + +#include +#include +#include + +namespace krino { + +class Compute_Surface_Distance { +public: + static void calculate( + const stk::mesh::BulkData & mesh, + const stk::diag::Timer &parent_timer, + const stk::mesh::Field& coordinates, + const stk::mesh::Field& distance, + const stk::mesh::Selector & surface_selector, + const double narrowBandSize = 0.0, + const double farFieldValue = 0.0); + + static void calculate( + const stk::mesh::BulkData & mesh, + const stk::diag::Timer &parent_timer, + const stk::mesh::Field& coordinates, + const stk::mesh::Field& distance, + const stk::mesh::Selector & volume_selector, + const stk::mesh::Selector & surface_selector, + const double narrowBandSize = 0.0, + const double farFieldValue = 0.0); + }; + +} // namespace krino + +#endif // Akri_Compute_Surface_Distance_h diff --git a/packages/krino/krino/krino_lib/Akri_ContourElement.cpp b/packages/krino/krino/krino_lib/Akri_ContourElement.cpp new file mode 100644 index 000000000000..9ec313d5897e --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ContourElement.cpp @@ -0,0 +1,556 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino{ + +ContourElement::ContourElement( const stk::mesh::BulkData & mesh, + stk::mesh::Entity in_mesh_obj, + const FieldRef coords_field, + const FieldRef dist_field, + const double iso_dist ) + : my_mesh( mesh ), + my_entity( in_mesh_obj ), + my_spatial_dim( mesh.mesh_meta_data().spatial_dimension() ), + my_coords_master_elem(MasterElementDeterminer::getMasterElement(mesh.bucket(in_mesh_obj), coords_field)), + my_dist_master_elem(MasterElementDeterminer::getMasterElement(mesh.bucket(in_mesh_obj), dist_field)), + my_coords_field( coords_field ), + my_dist_field( dist_field ), + my_length_scale(-1.0), + my_edge_linear_tolerance(-1.0), + my_edge_nonlinear_tolerance(-1.0) +{ /* %TRACE% */ /* %TRACE% */ + + // + // Gather obj's coordinates, distance, and velocity into ArrayContainer's. + // + + const int dim = my_spatial_dim; + + const int npe_coords = my_coords_master_elem.get_topology().num_nodes(); + const int npe_dist = my_dist_master_elem.get_topology().num_nodes(); + + my_coords.resize( dim, npe_coords ); + my_dist.resize( npe_dist ); + + const stk::mesh::Entity* elem_nodes = mesh.begin_nodes(my_entity); + + for ( int i = 0; i < npe_coords; ++i ) + { + double * var = field_data( my_coords_field, elem_nodes[i]); + for ( int d = 0; d < dim; d++ ) + { + my_coords(d,i) = var[d]; + } + } + + for ( int i = 0; i < npe_dist; ++i ) + { + double * var = field_data( my_dist_field, elem_nodes[i]); + my_dist(i) = *var - iso_dist; + } + + // the interpolate master element methods require the transpose?!? + my_coords_transpose.resize( npe_coords, dim ); + for (int i = 0; i < npe_coords; i++) + { + for (int d = 0; d < dim; d++) + { + my_coords_transpose(i,d) = my_coords(d,i); + } + } +} + +ContourElement::~ContourElement() {} + +std::ostream & +ContourElement::put( std::ostream& os ) const +{ /* %TRACE% */ /* %TRACE% */ + const int dim = my_spatial_dim; + + os << "Element description:" << std::endl + << " Coordinates topolology is " << coord_topology().name() + << ", Distance topology is " << dist_topology().name() << std::endl; + + int lnn = 0; + int coord_counter = 0; + int dist_counter = 0; + const unsigned num_nodes = my_mesh.num_nodes(my_entity); + const stk::mesh::Entity* nodes = my_mesh.begin_nodes(my_entity); + for (unsigned node_index=0; node_index(my_coords_field, node); + if ( NULL != var ) + { + Vector3d coords(Vector3d::ZERO); + for ( int d = 0; d < dim; d++ ) + coords[d] = my_coords(d,coord_counter); + os << ", coords = (" << coords[0] << "," + << coords[1] << "," + << coords[2] << ")"; + coord_counter++; + } + + var = field_data(my_dist_field, node); + if ( NULL != var ) + { + os << ", dist = " << my_dist(dist_counter++); + } + os << std::endl; + } + return os ; +} + +bool +ContourElement::have_interface_sides() const +{ + ThrowAssert(my_base_subelement); + return my_base_subelement->have_interface_sides(); +} + +double +ContourElement::distance( const Vector3d & p_coords ) const + { /* %TRACE% */ /* %TRACE% */ + double dist; + my_dist_master_elem.interpolate_point(my_spatial_dim, p_coords.data(), 1, my_dist.ptr(), &dist); + return dist; + } + +Vector3d +ContourElement::coordinates( const Vector3d & p_coords ) const + { /* %TRACE% */ /* %TRACE% */ + Vector3d coords(Vector3d::ZERO); + my_coords_master_elem.interpolate_point(my_spatial_dim, p_coords.data(), my_spatial_dim, my_coords.ptr(), coords.data()); + return coords; + } + +double +ContourElement::determinant( const Vector3d & p_coords ) const + { /* %TRACE% */ /* %TRACE% */ + double detJ, detJ_error; + + const int num_coord_dofs = coord_topology().num_nodes(); + + // temporary data + static sierra::ArrayContainer d_shapef_coords; + + // reserve enough space for results + d_shapef_coords.resize(my_spatial_dim, num_coord_dofs, 1); + + my_coords_master_elem.shape_fcn_deriv(1, p_coords.data(), d_shapef_coords.ptr()); + my_coords_master_elem.determinant( + my_spatial_dim, // Number of coordinate dimensions + 1, // Number of target points + num_coord_dofs, // Number of coord shape functions + d_shapef_coords.ptr(),// Mesh shape function derivatives + 1, // Number of elements + my_coords.ptr(), // Mesh coordinate values + &detJ, // Determinant of the transformation Jacobian for each element (output) + &detJ_error ); // Determinant error (output) + return detJ; + } + +Vector3d +ContourElement::continuous_distance_gradient( const Vector3d & p_coords ) const + { /* %TRACE% */ /* %TRACE% */ + // gather continuous distance gradient + sierra::ArrayContainer grad_distance; + const int npe = dist_topology().num_nodes(); + grad_distance.resize( my_spatial_dim, npe ); + stk::mesh::FieldBase* field_ptr = my_mesh.mesh_meta_data().get_field(stk::topology::NODE_RANK, "CONT_GRAD"); + ThrowRequireMsg(nullptr != field_ptr, "Field CONT_GRAD not found."); + const FieldRef contGradField(field_ptr); + const stk::mesh::Entity* elem_nodes = my_mesh.begin_nodes(my_entity); + for ( int i = 0; i < npe; ++i ) + { + double * var = field_data(contGradField, elem_nodes[i]); + for(int d = 0; d < my_spatial_dim; ++d) + grad_distance(d,i) = var[d]; + } + + Vector3d grad_dist(Vector3d::ZERO); + my_vel_master_elem->interpolate_point(my_spatial_dim, p_coords.data(), my_spatial_dim, grad_distance.ptr(), grad_dist.data()); + return grad_dist; + } + +Vector3d +ContourElement::distance_gradient( const Vector3d & p_coords ) const + { /* %TRACE% */ /* %TRACE% */ + const int dim = my_spatial_dim; + const int num_coord_dofs = my_coords.dimension(); + const int num_dist_dofs = my_dist.dimension(); + + // temporary data + static sierra::ArrayContainer d_shapef_coords; + static sierra::ArrayContainer d_shapef_dist; + static sierra::ArrayContainer grad_op; + + // reserve enough space for results + d_shapef_coords.resize(dim, num_coord_dofs); + d_shapef_dist.resize(dim, num_dist_dofs); + grad_op.resize(dim, num_dist_dofs); + Vector3d grad_dist( Vector3d::ZERO ); + double det_J; + + // pointers to array data + const double * intg_pt_loc_ptr = p_coords.data(); + double * grad_dist_ptr = grad_dist.data(); + + double gradop_error; + + my_coords_master_elem.shape_fcn_deriv(1, intg_pt_loc_ptr, d_shapef_coords.ptr()); + my_dist_master_elem.shape_fcn_deriv(1, intg_pt_loc_ptr, d_shapef_dist.ptr()); + + my_dist_master_elem.gradient_operator( + dim, // Number of coordinate dimensions + 1, // Number of target points + num_coord_dofs, // Number of coord shape functions + d_shapef_coords.ptr(),// Mesh shape function derivatives + num_dist_dofs, // Number of dof shape functions + d_shapef_dist.ptr(), // Dof shape function derivatives + 1, // Number of elements + my_coords.ptr(), // Mesh coordinate values + grad_op.ptr(), // Gradient operator values (output) + &det_J, // Determinant of the transformation Jacobian for each element (output) + &gradop_error ); // Gradop error (output) + + my_dist_master_elem.scalar_gradient( + 1, // Number of target points + 1, // Number of elements + grad_op.ptr(), // Gradient operator values + &det_J, // Determinant of the transformation Jacobian for each element + my_dist.ptr(), // nodal distance + grad_dist_ptr ); // Gradient of distance at integration pts (output) + return( grad_dist ); + } + +void +ContourElement::compute_distance_gradient( const sierra::Array & intg_pt_locations, + sierra::ArrayContainer & grad_dist ) const + { /* %TRACE% */ /* %TRACE% */ + const int dim = intg_pt_locations.dimension(); + const int num_intg_pts = intg_pt_locations.dimension(); + + const int num_coord_dofs = my_coords.dimension(); + const int num_dist_dofs = my_dist.dimension(); + + // temporary data + static sierra::ArrayContainer d_shapef_coords; + static sierra::ArrayContainer d_shapef_dist; + static sierra::ArrayContainer det_J; + static sierra::ArrayContainer grad_op; + + // reserve enough space for results + d_shapef_coords.resize(dim, num_coord_dofs, num_intg_pts); + d_shapef_dist.resize(dim, num_dist_dofs, num_intg_pts); + det_J.resize(num_intg_pts); + grad_op.resize(dim, num_dist_dofs, num_intg_pts); + grad_dist.resize(dim, num_intg_pts); + + double gradop_error; + + my_coords_master_elem.shape_fcn_deriv(num_intg_pts, intg_pt_locations.ptr(), d_shapef_coords.ptr()); + my_dist_master_elem.shape_fcn_deriv(num_intg_pts, intg_pt_locations.ptr(), d_shapef_dist.ptr()); + + my_dist_master_elem.gradient_operator( + dim, // Number of coordinate dimensions + num_intg_pts, // Number of target points + num_coord_dofs, // Number of coord shape functions + d_shapef_coords.ptr(), // Mesh shape function derivatives + num_dist_dofs, // Number of dof shape functions + d_shapef_dist.ptr(), // Dof shape function derivatives + 1, // Number of elements + my_coords.ptr(), // Mesh coordinate values + grad_op.ptr(), // Gradient operator values (output) + det_J.ptr(), // Determinant of the transformation Jacobian for each element (output) + &gradop_error ); // Gradop error (output) + + my_dist_master_elem.scalar_gradient( + num_intg_pts, // Number of target points + 1, // Number of elements + grad_op.ptr(), // Gradient operator values + det_J.ptr(), // Determinant of the transformation Jacobian for each element + my_dist.ptr(), // nodal distance + grad_dist.ptr() ); // Gradient of distance at integration pts (output) + } + +void +ContourElement::compute_subelement_decomposition(const double in_length_scale, const double in_edge_linear_tolerance, const double in_edge_nonlinear_tolerance) const + { /* %TRACE% */ /* %TRACE% */ + my_length_scale = in_length_scale; + my_edge_linear_tolerance = in_edge_linear_tolerance; + my_edge_nonlinear_tolerance = in_edge_nonlinear_tolerance; + + stk::topology topology = dist_topology(); + const int num_sides = topology.num_sides(); + std::vector side_ids(num_sides); + for (int i=0; i( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::HEXAHEDRON_27 == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::TETRAHEDRON_4 == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::TETRAHEDRON_10 == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::WEDGE_6 == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::QUADRILATERAL_4_2D == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::QUADRILATERAL_9_2D == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::TRIANGLE_3_2D == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + else if (stk::topology::TRIANGLE_6_2D == topology) + { + my_base_subelement = std::make_unique( my_dist_p_coords, side_ids, this ); + } + ThrowErrorMsgIf(!my_base_subelement, "Element with topology " << topology.name() << " not supported."); + + if ( krinolog.shouldPrint(LOG_SUBELEMENT) ) + { + if ( krinolog.shouldPrint(LOG_DEBUG) ) + { + dump_subelement_details(); + } + else + { + dump_subelement_structure(); + } + } + } + +double +ContourElement::volume() const + { /* %TRACE% */ /* %TRACE% */ + + sierra::Array intg_pt_locations; + sierra::Array intg_weights; + sierra::ArrayContainer determinants; + + std_intg_pts( intg_pt_locations, intg_weights, determinants ); + + double vol = 0.0; + + const int num_intg_pts = intg_pt_locations.dimension(); + + for ( int ip = 0; ip < num_intg_pts; ++ip ) + { + vol += intg_weights(ip) * determinants(ip); + } + return vol; + } + +double +ContourElement::average_edge_length() const +{ /* %TRACE% */ /* %TRACE% */ + const stk::topology Top = coord_topology(); + int num_edges = Top.num_edges(); + + double sum_edge_lengths = 0.0; + + for ( int edge = 0; edge < num_edges; edge++ ) + { + const unsigned * const lnn = get_edge_node_ordinals(Top, edge); + + double sqr_length = 0.0; + for ( int d = 0; d < my_spatial_dim; d++ ) sqr_length += (my_coords(d,lnn[0]) - my_coords(d,lnn[1])) * + (my_coords(d,lnn[0]) - my_coords(d,lnn[1])); + sum_edge_lengths += std::sqrt(sqr_length); + } + + return sum_edge_lengths/num_edges; +} + +double +ContourElement::elem_size() const + { /* %TRACE% */ /* %TRACE% */ + const double vol = volume(); + const double vol_root = 1.0 / my_spatial_dim; + const double h = pow( vol, vol_root ); + return h; + } + +void +ContourElement::dump_subelement_structure() const + { /* %TRACE% */ /* %TRACE% */ + ThrowErrorMsgIf(!my_base_subelement, "\ncompute_subelement_decomposition(...) must be called prior to calling dump_subelement_structure()."); + + krinolog << "***********************************************" << stk::diag::dendl; + krinolog << *this; + krinolog << "Subelement structure:" << stk::diag::dendl; + my_base_subelement->dump_structure(); + krinolog << "***********************************************" << stk::diag::dendl; + } + +void +ContourElement::dump_subelement_details() const + { /* %TRACE% */ /* %TRACE% */ + ThrowErrorMsgIf(!my_base_subelement, "\ncompute_subelement_decomposition(...) must be called prior to calling dump_subelement_details()."); + + krinolog << "***********************************************" << stk::diag::dendl; + krinolog << *this; + krinolog << "Subelement details:" << stk::diag::dendl; + my_base_subelement->dump_details(); + krinolog << "***********************************************" << stk::diag::dendl; + } + +int +ContourElement::build_subelement_facets( Faceted_Surface & facets ) +{ + ThrowErrorMsgIf(!my_base_subelement, "\ncompute_subelement_decomposition(...) must be called prior to calling build_subelement_facets(...)."); + return my_base_subelement->build_facets( facets ); +} + +int +ContourElement::gather_intg_pts( const int intg_pt_sign, + sierra::ArrayContainer & intg_pt_locations, + sierra::ArrayContainer & intg_weights, + sierra::ArrayContainer & determinants, + const bool map_to_real_coords ) +{ + ThrowErrorMsgIf(0 == intg_pt_sign && !map_to_real_coords, + "\nSubelement decomposition can currently only provide the surface integration with the overall determinant."); + ThrowErrorMsgIf(!my_base_subelement, + "\ncompute_subelement_decomposition(...) must be called prior to calling gather_intg_pts(...)."); + + const int num_intg_pts = my_base_subelement->num_intg_pts(intg_pt_sign); + + intg_pt_locations.resize(my_spatial_dim,num_intg_pts); + intg_weights.resize(num_intg_pts); + determinants.resize(num_intg_pts); + + my_base_subelement->gather_intg_pts( intg_pt_sign, + intg_pt_locations, + intg_weights, + determinants ); + + if ( 0 != intg_pt_sign && map_to_real_coords ) + { + // + // Include the determinant from element to real space + // + const int num_coord_dofs = coord_topology().num_nodes(); + + // temporary data + static sierra::ArrayContainer d_shapef_coords; + static sierra::ArrayContainer det_J; + + // reserve enough space for results + d_shapef_coords.resize(my_spatial_dim, num_coord_dofs, num_intg_pts); + det_J.resize(num_intg_pts); + + double det_J_error; + + my_coords_master_elem.shape_fcn_deriv(num_intg_pts, intg_pt_locations.ptr(), d_shapef_coords.ptr()); + my_coords_master_elem.determinant( + my_spatial_dim, // Number of coordinate dimensions + num_intg_pts, // Number of target points + num_coord_dofs, // Number of coord shape functions + d_shapef_coords.ptr(),// Mesh shape function derivatives + 1, // Number of elements + my_coords.ptr(), // Mesh coordinate values + det_J.ptr(), // Determinant of the transformation Jacobian for each element (output) + &det_J_error ); // Determinant error (output) + for ( int i=0; i & intg_pt_locations, + sierra::Array & intg_weights, + sierra::ArrayContainer & det_J, + const MasterElement & me ) const + { + const int num_intg_pts = me.num_intg_pts(); + const double * intg_pt_loc_ptr = me.intg_pt_locations(); + const double * intg_wt_ptr = me.intg_weights(); + + intg_pt_locations.set(intg_pt_loc_ptr,my_spatial_dim,num_intg_pts); + intg_weights.set(intg_wt_ptr,num_intg_pts); + det_J.resize( num_intg_pts ); + + double det_J_error; + + if ( me.get_topology() == my_coords_master_elem.get_topology() ) + { + my_coords_master_elem.determinant( my_spatial_dim, 1, my_coords.ptr(), det_J.ptr(), &det_J_error ); + } + else + { + const int num_coord_dofs = coord_topology().num_nodes(); + sierra::ArrayContainer d_shapef_coords(my_spatial_dim, num_coord_dofs, num_intg_pts); + my_coords_master_elem.shape_fcn_deriv(num_intg_pts, intg_pt_locations.ptr(), d_shapef_coords.ptr()); + my_coords_master_elem.determinant( + my_spatial_dim, // Number of coordinate dimensions + num_intg_pts, // Number of target points + num_coord_dofs, // Number of coord shape functions + d_shapef_coords.ptr(),// Mesh shape function derivatives + 1, // Number of elements + my_coords.ptr(), // Mesh coordinate values + det_J.ptr(), // Determinant of the transformation Jacobian for each element (output) + &det_J_error ); // Determinant error (output) + } + + return( num_intg_pts ); + } + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_ContourElement.hpp b/packages/krino/krino/krino_lib/Akri_ContourElement.hpp new file mode 100644 index 000000000000..f7ee798316ca --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ContourElement.hpp @@ -0,0 +1,130 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_ContourElement_h +#define Akri_ContourElement_h + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace krino { + +class ContourSubElement; + +class ContourElement { +public: + + ContourElement( const stk::mesh::BulkData & mesh, + stk::mesh::Entity mesh_obj, + const FieldRef coords_field, + const FieldRef dist_field, + const double iso_dist = 0.0 ); + ~ContourElement(); // Definition must in implementation file because SubElement is incomplete + + std::ostream & put( std::ostream& os ) const; + friend std::ostream & operator << ( std::ostream &os , const ContourElement &s ) { + return s.put(os); + } + + const MasterElement & coord_master_elem() const { return my_coords_master_elem; } + const MasterElement & dist_master_elem() const { return my_dist_master_elem; } + const MasterElement & vel_master_elem() const { return *my_vel_master_elem; } + + const stk::topology coord_topology() const { return my_coords_master_elem.get_topology(); } + const stk::topology dist_topology() const { return my_dist_master_elem.get_topology(); } + const stk::topology vel_topology() const { return my_vel_master_elem->get_topology(); } + + bool dist_is_linear() const { return dist_topology() == stk::topology::TRIANGLE_3_2D || dist_topology() == stk::topology::TETRAHEDRON_4; } + + stk::mesh::Entity entity() const { return my_entity; } + stk::mesh::EntityId elem_id() const { return my_mesh.identifier(my_entity); } + bool have_interface_sides() const; + + Vector3d coordinates( const Vector3d & p_coords ) const; + double distance( const Vector3d & p_coords ) const; + const PointVec & nodal_parametric_coordinates() const { return my_dist_p_coords; } + double determinant( const Vector3d & p_coords ) const; + + Vector3d continuous_distance_gradient( const Vector3d & p_coords ) const; + Vector3d distance_gradient( const Vector3d & p_coords ) const; + void compute_distance_gradient( const sierra::Array & intg_pt_locations, + sierra::ArrayContainer & grad_dist ) const; + + void dump_subelement_structure( void ) const; + void dump_subelement_details( void ) const; + const ContourSubElement * get_base_subelement() const { return my_base_subelement.get(); } + + int build_subelement_facets( Faceted_Surface & facets ); + + int gather_intg_pts( const int intg_pt_sign, + sierra::ArrayContainer & intg_pt_locations, + sierra::ArrayContainer & intg_weights, + sierra::ArrayContainer & determinants, + const bool map_to_real_coords ); + + int std_intg_pts( sierra::Array & intg_pt_locations, + sierra::Array & intg_weights, + sierra::ArrayContainer & determinants, + const MasterElement & me ) const; + int std_intg_pts( sierra::Array & intg_pt_locations, + sierra::Array & intg_weights, + sierra::ArrayContainer & determinants ) const { return std_intg_pts(intg_pt_locations, intg_weights, determinants, my_coords_master_elem); } + + int spatial_dim() const { return my_spatial_dim; } + double length_scale() const { return my_length_scale; } + double edge_linear_tolerance() const { return my_edge_linear_tolerance; } + double edge_nonlinear_tolerance() const { return my_edge_nonlinear_tolerance; } + + double volume() const; + double elem_size() const; + double average_edge_length() const; + + void compute_subelement_decomposition(const double length_scale, const double edge_linear_tolerance = 1.e-4, const double edge_nonlinear_tolerance = 1.0e-2) const; + +private: + const stk::mesh::BulkData & my_mesh; + stk::mesh::Entity my_entity; + const int my_spatial_dim; + + const MasterElement & my_coords_master_elem; + const MasterElement & my_dist_master_elem; + const MasterElement * my_vel_master_elem; + + const FieldRef my_coords_field; + const FieldRef my_dist_field; + const FieldRef my_vel_field; + + sierra::ArrayContainer my_coords; + sierra::ArrayContainer my_coords_transpose; + sierra::ArrayContainer my_dist; + sierra::ArrayContainer my_vel; + sierra::ArrayContainer my_vel_transpose; + + mutable PointVec my_dist_p_coords; + mutable double my_length_scale; + mutable double my_edge_linear_tolerance; + mutable double my_edge_nonlinear_tolerance; + mutable std::unique_ptr my_base_subelement; + + //: Default constructor not allowed + ContourElement(); + +}; + +} // namespace krino + +#endif // Akri_ContourElement_h diff --git a/packages/krino/krino/krino_lib/Akri_ContourSubElement.cpp b/packages/krino/krino/krino_lib/Akri_ContourSubElement.cpp new file mode 100644 index 000000000000..2ef3e52bf7c1 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ContourSubElement.cpp @@ -0,0 +1,2565 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace krino{ + +bool ContourSubElement::is_more(const Vector3d & x, const Vector3d & y) +{ + // note that in the case of x==y, this will return false + if (utility::is_more(x[0],y[0]) || (!utility::is_less(x[0],y[0]) && + (utility::is_more(x[1],y[1]) || (!utility::is_less(x[1],y[1]) && + (utility::is_more(x[2],y[2])))))) + { + return true; + } + return false; +} + +ContourSubElement::ContourSubElement( const stk::topology topo, + const PointVec & coords, + const std::vector & side_ids, + const ContourElement *in_owner, + const int in_subelement_depth, + const int subelement_sign ) + : my_master_element( MasterElementDeterminer::getMasterElement(topo) ), + my_side_master_element( MasterElementDeterminer::getMasterElement(topo.side_topology()) ), + my_coords( coords ), + my_side_ids( side_ids ), + my_owner( in_owner ), + my_subelement_depth( in_subelement_depth ), + my_sign( subelement_sign ) +{ /* %TRACE% */ /* %TRACE% */ + my_num_nodes = my_coords.size(); + ThrowAssert( (unsigned)my_num_nodes == topology().num_nodes() ); + my_num_sides = my_side_ids.size(); + ThrowAssert( (unsigned)my_num_sides == topology().num_sides() ); + + // compute distance at each node + my_dist.clear(); + my_dist.reserve(my_num_nodes); + for ( int i = 0; i < my_num_nodes; i++ ) + { + double nodal_dist = my_owner->distance( my_coords[i] ); + my_dist.push_back(nodal_dist); + } +} + +int +ContourSubElement::build_facets( Faceted_Surface & facets ) +{ /* %TRACE% */ /* %TRACE% */ + int start_size = facets.size(); + + if ( !my_subelements.empty() ) + { + for ( auto && subelem : my_subelements ) + { + subelem->build_facets( facets ); + } + } + else + { + for ( int iside = 0; iside < my_num_sides; iside++ ) + { + // side_ids == -2 indicates that sides is on interface + if ( my_side_ids[iside] == -2 ) + { + const int subelem_num_facets = side_facets( facets, iside ); + if ( krinolog.shouldPrint(LOG_SUBELEMENT) ) + { + krinolog << "Subelement with area = " << side_relative_area(iside) + << " added " << subelem_num_facets << " facets." << std::endl; + } + } + } + } + + int added_facets = facets.size() - start_size; + return added_facets; +} + +void +ContourSubElement::dump_structure() const +{ /* %TRACE% */ /* %TRACE% */ + + if ( !my_subelements.empty() ) + { + for ( int i = 0; i < my_subelement_depth; i++ ) + { + krinolog << " "; + } + krinolog << "my subelement type = " << topology().name() << ", my # of subelements = " << my_subelements.size() << std::endl; + for ( auto && subelem : my_subelements ) + { + subelem->dump_structure(); + } + } + else + { + for ( int i = 0; i < my_subelement_depth; i++ ) + { + krinolog << " "; + } + krinolog << "my subelement type = " << topology().name() << std::endl; + } +} + +void +ContourSubElement::dump_details() const +{ /* %TRACE% */ /* %TRACE% */ + if ( !my_subelements.empty() ) + { + krinolog << "my subelement type = " << topology().name() << ", my # of subelements = " << my_subelements.size() << std::endl; + for ( auto && subelem : my_subelements ) + { + subelem->dump_details(); + } + } + else + { + krinolog << "--------------------begin subelement definition--------------------" << std::endl; + krinolog << *this; + krinolog << "---------------------end subelement definition---------------------" << std::endl; + } +} + +int +ContourSubElement::side_facets( Faceted_Surface & facets, int side ) const +{ /* %TRACE% */ /* %TRACE% */ + const std::string & owner_type = my_owner->dist_topology().name(); + const std::string & sub_type = topology().name(); + ThrowRuntimeError("Subelement decomposition for subelement of type '" << sub_type + << "' which was generated from owning element of type '" << owner_type + << "' is missing the capability to generate conformal facets."); + return -1; +} + +std::ostream & +ContourSubElement::put( std::ostream& os ) const +{ /* %TRACE% */ /* %TRACE% */ + os << "Subelement description:" << std::endl; + os << " type = " << topology().name() + << ", relative volume = " << relative_volume() + << ", parametric_quality = " << parametric_quality() + << ", physical_quality = " << physical_quality() << std::endl; + for ( int i = 0; i < my_num_nodes; i++ ) + { + Vector3d x = my_owner->coordinates( my_coords[i] ); + os << " coords[" << i << "] = (" + << my_coords[i][0] << "," + << my_coords[i][1] << "," + << my_coords[i][2] << ")" + << ", x = (" + << x[0] << "," + << x[1] << "," + << x[2] << ")" + << ", dist = " << my_dist[i] + << ", sign = " << -1 + 2*LevelSet::sign_change(my_dist[i],-1.) << std::endl; + } + for ( int i = 0; i < my_num_sides; i++ ) + { + os << " side_ids[" << i << "] = " << my_side_ids[i] + << ", side_relative_area[" << i << "] = " << side_relative_area(i) + << ", side_quality[" << i << "] = " << side_quality(i) << std::endl; + } + // matlab visualization + os << " matlabvertices = ["; + for ( int i = 0; i < my_num_nodes; i++ ) + { + os << my_coords[i][0] << " " + << my_coords[i][1] << " " + << my_coords[i][2] << "; "; + } + os << "];" << std::endl; + os << " physical space matlabvertices = ["; + for ( int i = 0; i < my_num_nodes; i++ ) + { + Vector3d x = my_owner->coordinates( my_coords[i] ); + os << x[0] << " " + << x[1] << " " + << x[2] << "; "; + } + os << "];" << std::endl; + + return os ; +} + +double +ContourSubElement::relative_volume() const +{ /* %TRACE% */ /* %TRACE% */ + // This is a relative volume compared to the owner volume. + // Actually this is a relative volume if the "parametric" volume of the element is unity. + // Otherwise, it is off by a factor. + const int nelem = 1; + const int dim = spatial_dim(); + const int nint = my_master_element.num_intg_pts(); + std::vector coords(my_num_nodes * dim, 0.); + std::vector det_J(nint, 0.); + double error = 0.; + + // integration weights + const double * intg_weights = my_master_element.intg_weights(); + + // load coords + int count = 0; + for ( int i = 0; i < my_num_nodes; i++ ) + { + for ( int j = 0; j < dim; j++ ) + { + coords[count++] = my_coords[i][j]; + } + } + + // determinant at integration points + my_master_element.determinant( dim, nelem, coords.data(), det_J.data(), &error ); + + double elem_volume = 0.; + for ( int ip = 0; ip < nint; ip++ ) + { + elem_volume += det_J[ip] * intg_weights[ip]; + } + + return elem_volume; +} + +double +ContourSubElement::side_relative_area( const int side ) const +{ /* %TRACE% */ /* %TRACE% */ + // This is a relative volume compared to the owner volume. + // Actually this is a relative volume if the "parametric" volume of the element is unity. + // Otherwise, it is off by a factor. + const int nelem = 1; + const int dim = spatial_dim(); + const int nint = my_side_master_element.num_intg_pts(); + const stk::topology Top = topology(); + const stk::topology sideTop = Top.side_topology(side); + const int side_num_nodes = sideTop.num_nodes(); + const unsigned * const lnn = get_side_node_ordinals(Top, side); + + std::vector coords(side_num_nodes * dim, 0.); + std::vector det_J(nint, 0.); + double error = 0.; + + // integration weights + const double * intg_weights = my_side_master_element.intg_weights(); + + // load coords + int count = 0; + for ( int i = 0; i < side_num_nodes; i++ ) + { + for ( int j = 0; j < dim; j++ ) + { + coords[count++] = my_coords[lnn[i]][j]; + } + } + + // determinant at integration points + my_side_master_element.determinant( dim, nelem, coords.data(), det_J.data(), &error ); + + double elem_side_area = 0.; + for ( int ip = 0; ip < nint; ip++ ) + { + elem_side_area += det_J[ip] * intg_weights[ip]; + } + + return elem_side_area; +} + +bool +ContourSubElement::have_interface_sides() const +{ + if ( !my_subelements.empty()) + { + for ( auto && subelem : my_subelements ) + { + if (subelem->have_interface_sides()) + { + return true; + } + } + } + else + { + for ( int iside = 0; iside < my_num_sides; iside++ ) + { + // side_ids == -2 indicates that sides is on interface + if ( my_side_ids[iside] == -2 ) + { + return true; + } + } + } + return false; +} + +ContourSubElement::~ContourSubElement() +{ /* %TRACE% */ /* %TRACE% */ + for ( auto && subelem : my_subelements ) + delete subelem; +} + +int +ContourSubElement::num_intg_pts(const int intg_pt_sign) +{ /* %TRACE% */ /* %TRACE% */ + int num_pts = 0; + + if ( !my_subelements.empty() ) + { + for ( auto && subelem : my_subelements ) + { + num_pts += subelem->num_intg_pts(intg_pt_sign); + } + } + else + { + if ( 0 == intg_pt_sign ) // interface points + { + for ( int iside = 0; iside < my_num_sides; iside++ ) + { + // side_ids == -2 indicates that side is on interface + if ( my_side_ids[iside] == -2 ) + { + num_pts += my_side_master_element.num_intg_pts(); + } + } + } + else // volume points + { + if ( intg_pt_sign != my_sign ) + { + return(0); + } + else + { + return ( my_master_element.num_intg_pts() ); + } + } + } + + return num_pts; +} + +int +ContourSubElement::gather_intg_pts( const int intg_pt_sign, + sierra::ArrayContainer & intg_pt_locations, + sierra::ArrayContainer & intg_weights, + sierra::ArrayContainer & determinant, + int index ) +{ /* %TRACE% */ /* %TRACE% */ + if ( !my_subelements.empty() ) + { + for ( auto && subelem : my_subelements ) + { + index = subelem->gather_intg_pts( intg_pt_sign, + intg_pt_locations, + intg_weights, + determinant, + index ); + } + } + else + { + if ( 0 == intg_pt_sign ) // interface points + { + for ( int iside = 0; iside < my_num_sides; iside++ ) + { + // side_ids == -2 indicates that side is on interface + if ( my_side_ids[iside] == -2 ) + { + const int nelem = 1; + const int dim = spatial_dim(); + const stk::topology Top = topology(); + const stk::topology sideTop = Top.side_topology(iside); + const int side_num_intg_pts = my_side_master_element.num_intg_pts(); + const int side_num_nodes = sideTop.num_nodes(); + const unsigned * const lnn = get_side_node_ordinals(Top, iside ); + double error; + + // temp arrays + sierra::ArrayContainer coords(dim,side_num_nodes); + sierra::ArrayContainer det_J(side_num_intg_pts); + + // load coords + for ( int i = 0; i < side_num_nodes; i++ ) + { + Vector3d coordinates = my_owner->coordinates( my_coords[lnn[i]] ); + for ( int d = 0; d < dim; d++ ) coords(d,i) = coordinates[d]; + } + + // determinant at integration points + my_side_master_element.determinant( dim, nelem, coords.ptr(), det_J.ptr(), &error ); + + // integration weights + const double * intg_wts_ptr = my_side_master_element.intg_weights(); + const sierra::Array intg_wts(intg_wts_ptr,side_num_intg_pts); + + // basis fns at integration point locations + const double * bf_ptr = my_side_master_element.shape_fcn(); + const sierra::Array bf(bf_ptr,side_num_nodes,side_num_intg_pts); + + for ( int ip = 0; ip < side_num_intg_pts; ++ip) + { + determinant(index) = det_J(ip); + intg_weights(index) = intg_wts(ip); + + Vector3d xi(Vector3d::ZERO); + for ( int i = 0; i < side_num_nodes; i++ ) + xi += bf(i,ip) * my_coords[lnn[i]]; + + for ( int d = 0; d < dim; ++d ) + intg_pt_locations(d,index) = xi[d]; + + index++; + } + } + } + } + else // volume points + { + ThrowAssert(-1 == intg_pt_sign || 1 == intg_pt_sign); + + if ( intg_pt_sign != my_sign ) + { + return(index); + } + + const int nelem = 1; + const int dim = spatial_dim(); + const int vol_num_intg_pts = my_master_element.num_intg_pts(); + + // temp arrays + sierra::ArrayContainer coords(dim,my_num_nodes); + sierra::ArrayContainer det_J(vol_num_intg_pts); + + double error; + + // integration weights + const double * intg_wts_ptr = my_master_element.intg_weights(); + const sierra::Array intg_wts(intg_wts_ptr,vol_num_intg_pts); + + // load coords + for ( int i = 0; i < my_num_nodes; i++ ) + { + for ( int d = 0; d < dim; d++ ) coords(d,i) = my_coords[i][d]; + } + + // determinant at integration points + my_master_element.determinant( dim, nelem, coords.ptr(), det_J.ptr(), &error ); + + // basis fns at integration point locations + const double * bf_ptr = my_master_element.shape_fcn(); + const sierra::Array bf(bf_ptr,my_num_nodes,vol_num_intg_pts); + + for ( int ip = 0; ip < vol_num_intg_pts; ++ip) + { + determinant(index) = det_J(ip); + intg_weights(index) = intg_wts(ip); + + Vector3d xi(Vector3d::ZERO); + for ( int i = 0; i < my_num_nodes; i++ ) + xi += bf(i,ip) * my_coords[i]; + + for ( int d = 0; d < dim; d++ ) + intg_pt_locations(d,index) = xi[d]; + + index++; + } + } + } + return(index); +} + +double +ContourSubElement::parametric_quality() const +{ /* %TRACE% */ /* %TRACE% */ + const int nelem = 1; + const int nint = my_master_element.num_intg_pts(); + const int dim = spatial_dim(); + std::vector coords(my_num_nodes * dim, 0.); + std::vector det_J(nint, 0.); + double error = 0.; + double sub_quality = 0.; + + // load coords + int count = 0; + for ( int i = 0; i < my_num_nodes; i++ ) + { + for ( int j = 0; j < dim; j++ ) + { + coords[count++] = my_coords[i][j]; + } + } + + // determinant at integration points + my_master_element.determinant( dim, nelem, coords.data(), det_J.data(), &error ); + + double min_det_J = 0., sum_det_J = 0.; + for ( int ip = 0; ip < nint; ip++ ) + { + if ( ip == 0 || det_J[ip] < min_det_J ) + min_det_J = det_J[ip]; + sum_det_J += std::fabs( det_J[ip] ); + } + + if ( sum_det_J < std::pow(std::numeric_limits::epsilon(),1./dim) ) + sub_quality = 1.; // element too small to consider + else + sub_quality = min_det_J * nint / sum_det_J; + + return sub_quality; +} + +double +ContourSubElement::physical_quality() const +{ /* %TRACE% */ /* %TRACE% */ + const int nelem = 1; + const int nint = my_master_element.num_intg_pts(); + const int dim = spatial_dim(); + std::vector coords(my_num_nodes * dim, 0.); + std::vector det_J(nint, 0.); + double error = 0.; + double sub_quality = 0.; + + // load coords + int count = 0; + for ( int i = 0; i < my_num_nodes; i++ ) + { + const Vector3d phys_coords = my_owner->coordinates(my_coords[i]); + for ( int j = 0; j < dim; j++ ) + { + coords[count++] = phys_coords[j]; + } + } + + // determinant at integration points + my_master_element.determinant( dim, nelem, coords.data(), det_J.data(), &error ); + + double min_det_J = 0., sum_det_J = 0.; + for ( int ip = 0; ip < nint; ip++ ) + { + if ( ip == 0 || det_J[ip] < min_det_J ) + min_det_J = det_J[ip]; + sum_det_J += std::fabs( det_J[ip] ); + } + + if ( sum_det_J < std::pow(std::numeric_limits::epsilon(),1./dim) * std::pow(my_owner->length_scale(),1.*dim) ) + sub_quality = 1.; // element too small to consider + else + sub_quality = min_det_J * nint / sum_det_J; + + return sub_quality; +} + +double +ContourSubElement::side_quality(const int side) const +{ /* %TRACE% */ /* %TRACE% */ + const int nelem = 1; + const int nint = my_side_master_element.num_intg_pts(); + const stk::topology Top = topology(); + const stk::topology sideTop = Top.side_topology(side); + const int side_num_nodes = sideTop.num_nodes(); + const unsigned * const lnn = get_side_node_ordinals(Top, side); + const int dim = spatial_dim(); + std::vector coords(side_num_nodes * dim, 0.); + std::vector det_J(nint, 0.); + double error = 0.; + double quality = 0.; + + // load coords on side + int count = 0; + for ( int i = 0; i < side_num_nodes; i++ ) + { + for ( int j = 0; j < dim; j++ ) + { + coords[count++] = my_coords[lnn[i]][j]; + } + } + + // determinant at integration points + my_side_master_element.determinant( dim, nelem, coords.data(), det_J.data(), &error ); + + double min_det_J = 0., sum_det_J = 0.; + for ( int ip = 0; ip < nint; ip++ ) + { + if ( ip == 0 || det_J[ip] < min_det_J ) + min_det_J = det_J[ip]; + sum_det_J += std::fabs( det_J[ip] ); + } + + if ( sum_det_J < std::pow(std::numeric_limits::epsilon(),1./dim) ) + quality = 1.; // element too small to consider + else + quality = min_det_J * nint / sum_det_J; + + return quality; +} + +double +ContourSubElement::find_quadratic_crossing( double d0, + double d1, + double d2 ) +{ /* %TRACE% */ /* %TRACE% */ + const double epsilon = std::numeric_limits::epsilon()*std::sqrt(d0*d0 + d1*d1 + d2*d2); + if ( std::fabs(d0) < epsilon ) return 0.0; + if ( std::fabs(d1) < epsilon ) return 1.0; + if ( std::fabs(d2) < epsilon ) return 0.5; + + ThrowAssert(d0*d1 < 0.0 && (d0*d2 < 0.0 || d1*d2 < 0.0)); // Insist on one and only one crossing + + const double a = 2.0*(d0 - 2.0*d2 + d1); + const double b = -3.0*d0 - d1 + 4.0*d2; + const double c = d0; + const int sign_b = ( b < 0.0 ) ? -1 : 1; + const double q = -0.5*(b + sign_b*std::sqrt(b*b-4.0*a*c)); + + const int sign_a = ( a < 0.0 ) ? -1 : 1; + + if (q*sign_a > 0.0 && q*sign_a < a*sign_a) + { + ThrowAssert(!(c*(( q < 0.0 ) ? -1 : 1) > 0.0 && c*(( q < 0.0 ) ? -1 : 1) < q*(( q < 0.0 ) ? -1 : 1))); // Insist on only one crossing + return (q/a); + } + else + { + ThrowAssert(c*(( q < 0.0 ) ? -1 : 1) > 0.0 && c*(( q < 0.0 ) ? -1 : 1) < q*(( q < 0.0 ) ? -1 : 1)); + return (c/q); + } +} + +ContourSubElement_Quad_4::ContourSubElement_Quad_4( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ) + : ContourSubElement( stk::topology::QUADRILATERAL_4_2D, + coords, + side_ids, + in_owner, + 0, /* in_subelement_depth*/ + 0 /* subelement_sign=0 for now, correct this below if this element is entirely on one side */ ) +{ /* %TRACE% */ /* %TRACE% */ + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return; + } + + non_conformal_decomposition(); +} + +int +ContourSubElement_Quad_4::non_conformal_decomposition() +{ /* %TRACE% */ /* %TRACE% */ + int success = true; // optimism + + // create 4, 3-noded, adaptive triangles + my_subelements.reserve(4); + ContourSubElement *sub = NULL; + PointVec sub_coords(3,Vector3d::ZERO); + std::vector sub_ids(3); + + Vector3d center = 0.25*(my_coords[0]+my_coords[1]+my_coords[2]+my_coords[3]); + + // triangle #1 + sub_coords[0] = my_coords[0]; + sub_coords[1] = my_coords[1]; + sub_coords[2] = center; + sub_ids[0] = my_side_ids[0]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // triangle #2 + sub_coords[0] = my_coords[1]; + sub_coords[1] = my_coords[2]; + sub_coords[2] = center; + sub_ids[0] = my_side_ids[1]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // triangle #3 + sub_coords[0] = my_coords[2]; + sub_coords[1] = my_coords[3]; + sub_ids[0] = my_side_ids[2]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // triangle #4 + sub_coords[0] = my_coords[3]; + sub_coords[1] = my_coords[0]; + sub_coords[2] = center; + sub_ids[0] = my_side_ids[3]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + return success; +} + +ContourSubElement_Quad_9::ContourSubElement_Quad_9( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement *in_owner ) + : ContourSubElement( stk::topology::QUADRILATERAL_9_2D, + coords, + side_ids, + in_owner, + 0, /* in_subelement_depth*/ + 0 /* subelement_sign=0 for now, correct this below if this element is entirely on one side */ ) +{ /* %TRACE% */ /* %TRACE% */ + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return; + } + + non_conformal_decomposition(); +} + +int +ContourSubElement_Quad_9::non_conformal_decomposition() +{ /* %TRACE% */ /* %TRACE% */ + int success = true; // optimism + + // create 4, 3-noded, adaptive triangles + my_subelements.reserve(4); + ContourSubElement *sub = NULL; + PointVec sub_coords(3,Vector3d::ZERO); + std::vector sub_ids(3); + + // triangle #1 + sub_coords[0] = my_coords[0]; + sub_coords[1] = my_coords[1]; + sub_coords[2] = my_coords[8]; + sub_ids[0] = my_side_ids[0]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // triangle #2 + sub_coords[0] = my_coords[1]; + sub_coords[1] = my_coords[2]; + sub_coords[2] = my_coords[8]; + sub_ids[0] = my_side_ids[1]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // triangle #3 + sub_coords[0] = my_coords[2]; + sub_coords[1] = my_coords[3]; + sub_coords[2] = my_coords[8]; + sub_ids[0] = my_side_ids[2]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // triangle #4 + sub_coords[0] = my_coords[3]; + sub_coords[1] = my_coords[0]; + sub_coords[2] = my_coords[8]; + sub_ids[0] = my_side_ids[3]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + return success; +} + +ContourSubElement_Tri_3::ContourSubElement_Tri_3( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth, + const int subelement_sign ) + : ContourSubElement( stk::topology::TRIANGLE_3_2D, + coords, + side_ids, + in_owner, + in_subelement_depth, + subelement_sign ) +{ /* %TRACE% */ /* %TRACE% */ + // if this is a conformal element, return quickly + if ( subelement_sign != 0 ) + { + return; + } + + // snap to mesh + for (int n = 0; n < 3; ++n) + { + if (std::fabs(my_dist[n]) < my_owner->edge_linear_tolerance() * my_owner->length_scale() ) + { + my_dist[n] = 0.0; + } + } + + // see if there is a crossing + bool have_crossing = false; + for ( int i = 1; i < my_num_nodes; i++ ) + { + if ( LevelSet::sign_change(my_dist[0], my_dist[i]) ) have_crossing = true; + } + + if ( have_crossing ) + { + // attempt conformal decomposition + int success = conformal_decomposition(); + ThrowErrorMsgIf(!success, " Conformal decomposition failed.\n"); + } + else + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + } +} + +int +ContourSubElement_Tri_3::conformal_decomposition() +{ /* %TRACE% */ /* %TRACE% */ + + // create 4 conforming triangular subelements + + // create 4, 3-noded tris + my_subelements.clear(); + my_subelements.reserve(4); + ContourSubElement *sub = NULL; + PointVec sub_coords(3,Vector3d::ZERO); + + // For any edge with a crossing, we will move the + // mid side node for that egdge to the crossing + // we will keep the modified locations of the nodes + // in a local vector of nodes (lcoords). + // We will also create local vectors for the distance and side_ids + // so that we can reorient the tri as discussed below. + PointVec lcoords = my_coords; + lcoords.resize(6,Vector3d::ZERO); + std::vector sub_ids(3); + std::vector is_on_surf(6); // initializes to 0 (false) + int sub_sign; + std::vector edge_node_ids(6); + + // find edge crossings + edge_node_ids[0] = 0; + edge_node_ids[1] = 1; + edge_node_ids[2] = 2; + edge_node_ids[3] = process_edge( 0, 1, 3, is_on_surf, lcoords, my_dist ); + edge_node_ids[4] = process_edge( 1, 2, 4, is_on_surf, lcoords, my_dist ); + edge_node_ids[5] = process_edge( 2, 0, 5, is_on_surf, lcoords, my_dist ); + + const int zero_sign = LevelSet::sign(0.0); + std::vector sub_degenerate(4); // initializes to zero (false) + + sub_degenerate[0] = is_degenerate(edge_node_ids,0,3,5); + sub_degenerate[1] = is_degenerate(edge_node_ids,3,1,4); + sub_degenerate[2] = is_degenerate(edge_node_ids,5,4,2); + sub_degenerate[3] = is_degenerate(edge_node_ids,3,4,5); + + // tri #1 + if (!sub_degenerate[0]) + { + sub_coords[0] = lcoords[0]; + sub_coords[1] = lcoords[3]; + sub_coords[2] = lcoords[5]; + sub_sign = LevelSet::sign(my_dist[0]); + sub_ids[0] = my_side_ids[0]; + sub_ids[1] = ((is_on_surf[3] && is_on_surf[5]) && (zero_sign != sub_sign || sub_degenerate[3])) ? -2 : -1; + sub_ids[2] = my_side_ids[2]; + sub = new ContourSubElement_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tri #2 + if (!sub_degenerate[1]) + { + sub_coords[0] = lcoords[3]; + sub_coords[1] = lcoords[1]; + sub_coords[2] = lcoords[4]; + sub_sign = LevelSet::sign(my_dist[1]); + sub_ids[0] = my_side_ids[0]; + sub_ids[1] = my_side_ids[1]; + sub_ids[2] = ((is_on_surf[3] && is_on_surf[4]) && (zero_sign != sub_sign || sub_degenerate[3])) ? -2 : -1; + sub = new ContourSubElement_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tri #3 + if (!sub_degenerate[2]) + { + sub_coords[0] = lcoords[5]; + sub_coords[1] = lcoords[4]; + sub_coords[2] = lcoords[2]; + sub_sign = LevelSet::sign(my_dist[2]); + sub_ids[0] = ((is_on_surf[5] && is_on_surf[4]) && (zero_sign != sub_sign || sub_degenerate[3])) ? -2 : -1; + sub_ids[1] = my_side_ids[1]; + sub_ids[2] = my_side_ids[2]; + sub = new ContourSubElement_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tri #4 + if (!sub_degenerate[3]) + { + sub_coords[0] = lcoords[3]; + sub_coords[1] = lcoords[4]; + sub_coords[2] = lcoords[5]; + sub_sign = LevelSet::sign( (is_on_surf[3] ? 0.0 : my_dist[0]+my_dist[1]) + + (is_on_surf[4] ? 0.0 : my_dist[1]+my_dist[2]) + + (is_on_surf[5] ? 0.0 : my_dist[2]+my_dist[0]) ); + sub_ids[0] = ((is_on_surf[3] && is_on_surf[4]) && (zero_sign != sub_sign || sub_degenerate[1])) ? -2 : -1; + sub_ids[1] = ((is_on_surf[4] && is_on_surf[5]) && (zero_sign != sub_sign || sub_degenerate[2])) ? -2 : -1; + sub_ids[2] = ((is_on_surf[5] && is_on_surf[3]) && (zero_sign != sub_sign || sub_degenerate[0])) ? -2 : -1; + sub = new ContourSubElement_Tri_3( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // check quality of subelements + // Here we assume that the linear tri is always decomposed into reasonable quality sub-tris + int success = true; + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + for ( unsigned i=0; iphysical_quality() < 0.0 ) + { + krinolog << "low quality subelement: " << i << "\n" + << *my_subelements[i] << "\n" + << "parent:" << "\n" + << *this << "\n"; + } + } + } + + return success; +} + +int +ContourSubElement_Tri_3::process_edge( const int i0, + const int i1, + const int i2, + std::vector & is_on_surf, + PointVec & lcoords, + const std::vector & ldist ) +{ /* %TRACE% */ /* %TRACE% */ + int edge_node_id = i2; + is_on_surf[i2] = LevelSet::sign_change( ldist[i0], ldist[i1] ); + if ( is_on_surf[i2] ) + { + // tolerance chosen very small since degeneracies should already be eliminated + const double tol = std::numeric_limits::epsilon(); + + const double d0 = std::fabs(ldist[i0]); + const double d1 = std::fabs(ldist[i1]); + + // make calculation completely symmetric + if (d0 < d1) + { + const double alpha = my_owner->dist_is_linear() ? d0/(d0+d1) : find_quadratic_crossing(ldist[i0],ldist[i1],my_owner->distance(0.5*(lcoords[i0]+lcoords[i1]))); + if (alpha < tol) + { + edge_node_id = i0; + lcoords[i2] = lcoords[edge_node_id]; + } + else + { + lcoords[i2] = (1.-alpha) * lcoords[i0] + alpha * lcoords[i1]; + } + } + else + { + const double alpha = my_owner->dist_is_linear() ? d1/(d1+d0) : find_quadratic_crossing(ldist[i1],ldist[i0],my_owner->distance(0.5*(lcoords[i1]+lcoords[i0]))); + if (alpha < tol) + { + edge_node_id = i1; + lcoords[i2] = lcoords[edge_node_id]; + } + else + { + lcoords[i2] = (1.-alpha) * lcoords[i1] + alpha * lcoords[i0]; + } + } + } + else + { + // eliminate side node by sliding to one end or the other + const double d0 = std::fabs(ldist[i0]); + const double d1 = std::fabs(ldist[i1]); + const double epsilon = std::sqrt(std::numeric_limits::epsilon()) * (d0 + d1); + + if ( d0 > d1 + epsilon ) + { + edge_node_id = i0; + } + else if ( d1 > d0 + epsilon ) + { + edge_node_id = i1; + } + else + { + // tie breaker + const Vector3d phys0 = my_owner->coordinates(lcoords[i0]); + const Vector3d phys1 = my_owner->coordinates(lcoords[i1]); + + if ( is_more(phys1,phys0) ) + { + edge_node_id = i0; + } + else + { + edge_node_id = i1; + } + } + lcoords[i2] = lcoords[edge_node_id]; + } + return edge_node_id; +} + +bool +ContourSubElement_Tri_3::is_degenerate( const std::vector & edge_node_ids, + const int i0, const int i1, const int i2 ) +{ /* %TRACE% */ /* %TRACE% */ + + // DRN: This is really ugly, hand-optimized code for looking for degenerate tris + // Basically, it checks if any of the edges are degenerate. Then it has to look for + // the entire tri being degerate because it consists of 3 colinear points. + // This is handled by checking against the 3 specific bad cases. + + if ( edge_node_ids[i0] == edge_node_ids[i1] || + edge_node_ids[i0] == edge_node_ids[i2] || + edge_node_ids[i1] == edge_node_ids[i2] ) + { + // this tri is degenerate with two coincident nodes + return true; + } + + if ( edge_node_ids[i0]==i0 && + edge_node_ids[i1]==i1 && + edge_node_ids[i2]==i2 ) + { + // this tri is not degenerate since is has no degenerate nodes + return false; + } + + // look for a colinear triangle + std::vector is_used(6); // initializes to zero (false); + is_used[edge_node_ids[i0]] = true; + is_used[edge_node_ids[i1]] = true; + is_used[edge_node_ids[i2]] = true; + + if ((is_used[0] && ((is_used[1] && is_used[3]) || (is_used[2] && is_used[5]))) || + (is_used[1] && is_used[2] && is_used[4])) + { + // this tri is colinear + return true; + } + + return false; +} + +int +ContourSubElement_Tri_3::side_facets( Faceted_Surface & facets, + int side ) const +{ /* %TRACE% */ /* %TRACE% */ + ThrowAssert( my_side_ids[side] == -2 ); + + // just one linear facet per side + const int num_facets = 1; + + const unsigned * const lnn = get_side_node_ordinals(topology(), side); + + if ( LevelSet::sign_change(0.0, (double) my_sign) ) + { + std::unique_ptr facet = std::make_unique( my_owner->coordinates(my_coords[lnn[0]]), my_owner->coordinates(my_coords[lnn[1]]) ); + facets.add( std::move(facet) ); + } + else + { + std::unique_ptr facet = std::make_unique( my_owner->coordinates(my_coords[lnn[1]]), my_owner->coordinates(my_coords[lnn[0]]) ); + facets.add( std::move(facet) ); + } + + return( num_facets ); +} + +const int ContourSubElement_Adaptive_Tri_3::MAX_REFINMENT_LEVELS = 6; + +ContourSubElement_Adaptive_Tri_3::ContourSubElement_Adaptive_Tri_3( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth ) + : ContourSubElement( stk::topology::TRIANGLE_3_2D, + coords, + side_ids, + in_owner, + in_subelement_depth, + 0 ) +{ /* %TRACE% */ /* %TRACE% */ + my_edge_age.resize(3); // initializes to zero + non_conformal_decomposition(); +} + +ContourSubElement_Adaptive_Tri_3::ContourSubElement_Adaptive_Tri_3( + const PointVec & coords, + const std::vector & side_ids, + const std::vector & edge_age, + const ContourElement * in_owner, + const int in_subelement_depth ) + : ContourSubElement( stk::topology::TRIANGLE_3_2D, + coords, + side_ids, + in_owner, + in_subelement_depth, + 0 ) +{ /* %TRACE% */ /* %TRACE% */ + my_edge_age = edge_age; + non_conformal_decomposition(); +} + +int +ContourSubElement_Adaptive_Tri_3::non_conformal_decomposition() +{ /* %TRACE% */ /* %TRACE% */ + int success = true; // optimism + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return success; + } + + int longest_bad_edge = -1; + + // use temporary storage for vertex and side nodes + PointVec lcoords = my_coords; + lcoords.resize(6,Vector3d::ZERO); + lcoords[3] = 0.5 * (my_coords[0] + my_coords[1]); + lcoords[4] = 0.5 * (my_coords[1] + my_coords[2]); + lcoords[5] = 0.5 * (my_coords[2] + my_coords[0]); + + PointVec lphyscoords(6); + for (int n = 0; n < 6; ++n) + { + lphyscoords[n] = my_owner->coordinates( lcoords[n] ); + } + + std::vector ldist = my_dist; + ldist.resize(6); + for (int n = 3; n < 6; ++n) + { + ldist[n] = my_owner->distance( lcoords[n] ); + } + + const stk::topology Top = stk::topology::TRIANGLE_6_2D; + int num_edges = Top.num_edges(); + + std::vector bad_edges; + bad_edges.reserve(num_edges); + + std::vector edge_lengths(num_edges); + + for ( int edge = 0; edge < num_edges; edge++ ) + { + const unsigned * const lnn = get_edge_node_ordinals(Top, edge); + + ThrowAssert(Top.edge_topology(edge).num_nodes() == 3); + + const double edge_straight_length = (lphyscoords[lnn[0]] - lphyscoords[lnn[1]]).length(); + ThrowRequire(edge_straight_length > 0.0); + edge_lengths[edge] = edge_straight_length; + + const double edge_curve_error = (lphyscoords[lnn[2]] - 0.5*(lphyscoords[lnn[0]] + lphyscoords[lnn[1]])).length(); + + const double edge_dist_error = std::fabs(ldist[lnn[2]] - 0.5*(ldist[lnn[0]]+ldist[lnn[1]])); + + const double scale = std::min(std::sqrt(std::numeric_limits::max()),std::fabs(ldist[lnn[0]]) + std::fabs(ldist[lnn[1]]) + my_owner->length_scale()); + + const double edge_error = (edge_curve_error + edge_dist_error)*edge_straight_length/(scale*scale); + + if (edge_error > my_owner->edge_nonlinear_tolerance() && my_edge_age[edge] < MAX_REFINMENT_LEVELS) + { + bad_edges.push_back(edge); + } + } + + double max_length = 0.0; + for (auto edge : bad_edges) + { + const double edge_length = edge_lengths[edge]; + ThrowRequire(edge_length > 0.0); + + // we need an absolute mechanism for selecting the edge to bisect so that all elements that share + // common edges will make the same decisions + if (utility::is_more(edge_length,max_length)) + { + longest_bad_edge = edge; + max_length = edge_length; + } + else if (!utility::is_less(edge_length,max_length)) // tie breaker + { + const Vector3d & edge_midside_coords = lphyscoords[get_edge_node_ordinals(Top, edge)[2]]; + // note that it is safe to assume that longest_bad_edge is already assigned if edge_length == max_length + const Vector3d longest_edge_midside_coords = lphyscoords[get_edge_node_ordinals(Top, longest_bad_edge)[2]]; + + ThrowAssert((utility::is_not_equal(edge_midside_coords[0],longest_edge_midside_coords[0]) || + utility::is_not_equal(edge_midside_coords[1],longest_edge_midside_coords[1]))); + + if (utility::is_more(edge_midside_coords[0],longest_edge_midside_coords[0]) || + (!utility::is_less(edge_midside_coords[0],longest_edge_midside_coords[0]) && + (utility::is_more(edge_midside_coords[1],longest_edge_midside_coords[1])))) + { + longest_bad_edge = edge; + max_length = edge_length; + } + } + } + + if ( longest_bad_edge == -1 ) + { + // no bad edges + + // use a single nonconformal linear tet subelement + my_subelements.clear(); + my_subelements.reserve(1); + + ContourSubElement *sub = new ContourSubElement_Tri_3( my_coords, my_side_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + } + else + { + // + // create 2, adaptive, 3-noded triangles by cutting the longest_bad_edge + // + + my_subelements.clear(); + my_subelements.reserve(2); + ContourSubElement *sub = NULL; + PointVec sub_coords(3,Vector3d::ZERO); + std::vector sub_ids(3); + std::vector sub_edge_age(3); + + static const unsigned permute_0[] = { 0,1,2 }; + static const unsigned permute_1[] = { 1,2,0 }; + static const unsigned permute_2[] = { 2,0,1 }; + static const unsigned * permute_table[] = { permute_0, permute_1, permute_2 }; + + const unsigned * lnn = permute_table[longest_bad_edge]; + const unsigned * lsn = lnn; // side permutation mirrors node permutation + + const Vector3d edge_node = 0.5 * (my_coords[lnn[0]] + my_coords[lnn[1]]); + + // tri #1 + sub_coords[0] = my_coords[lnn[0]]; + sub_coords[1] = edge_node; + sub_coords[2] = my_coords[lnn[2]]; + sub_ids[0] = my_side_ids[lsn[0]]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = my_side_ids[lsn[2]]; + sub_edge_age[0] = my_edge_age[lsn[0]]+1; + sub_edge_age[1] = my_edge_age[lsn[0]]+1; + sub_edge_age[2] = my_edge_age[lsn[2]]; + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, sub_edge_age, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tri #2 + sub_coords[0] = edge_node; + sub_coords[1] = my_coords[lnn[1]]; + sub_coords[2] = my_coords[lnn[2]]; + sub_ids[0] = my_side_ids[lsn[0]]; + sub_ids[1] = my_side_ids[lsn[1]]; + sub_edge_age[0] = my_edge_age[lsn[0]]+1; + sub_edge_age[1] = my_edge_age[lsn[1]]; + sub_edge_age[2] = my_edge_age[lsn[0]]+1; + sub_ids[2] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, sub_ids, sub_edge_age, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + } + + return success; +} + +ContourSubElement_Tri_6::ContourSubElement_Tri_6( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth, + const int subelement_sign ) + : ContourSubElement( stk::topology::TRIANGLE_6_2D, + coords, + side_ids, + in_owner, + in_subelement_depth, + subelement_sign ) +{ /* %TRACE% */ /* %TRACE% */ + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return; + } + + // use a single non-conformal, adaptive 4-noded tet + my_subelements.clear(); + my_subelements.reserve(1); + ContourSubElement *sub = NULL; + PointVec sub_coords(3,Vector3d::ZERO); + + sub_coords[0] = my_coords[0]; + sub_coords[1] = my_coords[1]; + sub_coords[2] = my_coords[2]; + + sub = new ContourSubElement_Adaptive_Tri_3( sub_coords, my_side_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); +} + +ContourSubElement_Hex_8::ContourSubElement_Hex_8( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ) + : ContourSubElement( stk::topology::HEXAHEDRON_8, + coords, + side_ids, + in_owner, + 0, /* in_subelement_depth*/ + 0 /* subelement_sign=0 for now, correct this below if this element is entirely on one side */ ) +{ /* %TRACE% */ /* %TRACE% */ + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return; + } + + // create 24, 4-noded, adaptive tetrahedra + my_subelements.reserve(24); + + // Conceptually, hex is broken into 6 prisms, with the + // bases of the prisms corresponding to a face of the hex. + int success = true; // optimism + for ( int face = 0; face < 6 && success; ++face ) + { + success &= subpyramid_non_conformal_decomposition( face ); + } +} + +int +ContourSubElement_Hex_8::subpyramid_non_conformal_decomposition( const int face ) +{ /* %TRACE% */ /* %TRACE% */ + int success = true; // optimism + ContourSubElement *sub = NULL; + PointVec sub_coords(4,Vector3d::ZERO); + std::vector sub_ids(4); + + static const unsigned face_0[] = { 0,1,5,4 }; + static const unsigned face_1[] = { 1,2,6,5 }; + static const unsigned face_2[] = { 2,3,7,6 }; + static const unsigned face_3[] = { 0,4,7,3 }; + static const unsigned face_4[] = { 0,3,2,1 }; + static const unsigned face_5[] = { 4,5,6,7 }; + + static const unsigned * face_table[] = { face_0 , face_1 , face_2 , face_3 , face_4 , face_5 }; + + const unsigned * lnn = face_table[face]; + + // + // create 4, 4-noded adaptive tetrahedra + // + // The advantage of 4 tets per face over 2 tets per face is that all corners + // will be bisected. This eliminates some of the pathologies that occur when + // 3 nodes have the same value while the 4th node on the face has a different + // sign. Note that this problem can be mitigated, however, if the non-conformal + // refinement of the sub-tets will do longest edge bisection rather than the + // self-similar 8 subtet refinement. + + Vector3d vol_center = 0.125*(my_coords[0]+my_coords[1]+my_coords[2]+my_coords[3]+ + my_coords[4]+my_coords[5]+my_coords[6]+my_coords[7]); + Vector3d face_center = 0.25*(my_coords[lnn[0]]+my_coords[lnn[1]]+my_coords[lnn[2]]+my_coords[lnn[3]]); + + // tet #1 + sub_coords[0] = my_coords[lnn[0]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[1]]; + sub_coords[3] = vol_center; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #2 + sub_coords[0] = my_coords[lnn[1]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[2]]; + sub_coords[3] = vol_center; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #3 + sub_coords[0] = my_coords[lnn[2]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[3]]; + sub_coords[3] = vol_center; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #4 + sub_coords[0] = my_coords[lnn[3]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[0]]; + sub_coords[3] = vol_center; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + return success; +} + +ContourSubElement_Hex_27::ContourSubElement_Hex_27( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ) + : ContourSubElement( stk::topology::HEXAHEDRON_27, + coords, + side_ids, + in_owner, + 0, /* in_subelement_depth*/ + 0 /* subelement_sign=0 for now, correct this below if this element is entirely on one side */ ) +{ /* %TRACE% */ /* %TRACE% */ + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return; + } + + // create 24, 4-noded, adaptive tetrahedra + my_subelements.reserve(24); + + // Conceptually, hex is broken into 6 prisms, with the + // bases of the prisms corresponding to a face of the hex. + int success = true; // optimism + for ( int face = 0; face < 6 && success; ++face ) + { + success &= subpyramid_non_conformal_decomposition( face ); + } +} + +int +ContourSubElement_Hex_27::subpyramid_non_conformal_decomposition( const int face ) +{ /* %TRACE% */ /* %TRACE% */ + int success = true; // optimism + ContourSubElement *sub = NULL; + PointVec sub_coords(4,Vector3d::ZERO); + std::vector sub_ids(4); + + static const unsigned face_0[] = { 0,1,5,4, 25 }; + static const unsigned face_1[] = { 1,2,6,5, 24 }; + static const unsigned face_2[] = { 2,3,7,6, 26 }; + static const unsigned face_3[] = { 0,4,7,3, 23 }; + static const unsigned face_4[] = { 0,3,2,1, 21 }; + static const unsigned face_5[] = { 4,5,6,7, 22 }; + + static const unsigned * face_table[] = { face_0 , face_1 , face_2 , face_3 , face_4 , face_5 }; + + const unsigned * lnn = face_table[face]; + + // + // create 4, 4-noded adaptive tetrahedra + // + // The advantage of 4 tets per face over 2 tets per face is that all corners + // will be bisected. This eliminates some of the pathologies that occur when + // 3 nodes have the same value while the 4th node on the face has a different + // sign. Note that this problem can be mitigated, however, if the non-conformal + // refinement of the sub-tets will do longest edge bisection rather than the + // self-similar 8 subtet refinement. + + // tet #1 + sub_coords[0] = my_coords[lnn[0]]; + sub_coords[1] = my_coords[lnn[4]]; + sub_coords[2] = my_coords[lnn[1]]; + sub_coords[3] = my_coords[20]; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #2 + sub_coords[0] = my_coords[lnn[1]]; + sub_coords[1] = my_coords[lnn[4]]; + sub_coords[2] = my_coords[lnn[2]]; + sub_coords[3] = my_coords[20]; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #3 + sub_coords[0] = my_coords[lnn[2]]; + sub_coords[1] = my_coords[lnn[4]]; + sub_coords[2] = my_coords[lnn[3]]; + sub_coords[3] = my_coords[20]; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #4 + sub_coords[0] = my_coords[lnn[3]]; + sub_coords[1] = my_coords[lnn[4]]; + sub_coords[2] = my_coords[lnn[0]]; + sub_coords[3] = my_coords[20]; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + return success; +} + +ContourSubElement_Wedge_6::ContourSubElement_Wedge_6( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ) + : ContourSubElement( stk::topology::WEDGE_6, + coords, + side_ids, + in_owner, + 0, /* in_subelement_depth*/ + 0 /* subelement_sign=0 for now, correct this below if this element is entirely on one side */ ) +{ /* %TRACE% */ /* %TRACE% */ + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return; + } + + // create 12, 4-noded, adaptive tetrahedra + my_subelements.reserve(12); + + int success = true; // optimism + for ( int face = 0; face < 3 && success; ++face ) + { + success &= subpyramid_non_conformal_decomposition( face ); + } +} + +int +ContourSubElement_Wedge_6::subpyramid_non_conformal_decomposition( const int face ) +{ /* %TRACE% */ /* %TRACE% */ + int success = true; // optimism + ContourSubElement *sub = NULL; + PointVec sub_coords(4,Vector3d::ZERO); + std::vector sub_ids(4); + + static const unsigned face_0[] = {0, 1, 4, 3}; + static const unsigned face_1[] = {1, 2, 5, 4}; + static const unsigned face_2[] = {0, 3, 5, 2}; + static const unsigned * face_table[] = { face_0 , face_1 , face_2 }; + + const unsigned * lnn = face_table[face]; + + // + // create 4, 4-noded adaptive tetrahedra + // + // The advantage of 4 tets per face over 2 tets per face is that all corners + // will be bisected. This eliminates some of the pathologies that occur when + // 3 nodes have the same value while the 4th node on the face has a different + // sign. Note that this problem can be mitigated, however, if the non-conformal + // refinement of the sub-tets will do longest edge bisection rather than the + // self-similar 8 subtet refinement. + + // Not guaranteed to be within a highly deformed wedge + const Vector3d centroid = (my_coords[0]+my_coords[1]+my_coords[2]+my_coords[3]+my_coords[4]+my_coords[5])/6.; + const Vector3d face_center = 0.25*(my_coords[lnn[0]]+my_coords[lnn[1]]+my_coords[lnn[2]]+my_coords[lnn[3]]); + + // tet #1 + sub_coords[0] = my_coords[lnn[0]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[1]]; + sub_coords[3] = centroid; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #2 + sub_coords[0] = my_coords[lnn[1]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[2]]; + sub_coords[3] = centroid; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #3 + sub_coords[0] = my_coords[lnn[2]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[3]]; + sub_coords[3] = centroid; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #4 + sub_coords[0] = my_coords[lnn[3]]; + sub_coords[1] = face_center; + sub_coords[2] = my_coords[lnn[0]]; + sub_coords[3] = centroid; + sub_ids[0] = my_side_ids[face]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = -1; /* not on any parent side */ + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + return success; +} + +ContourSubElement_Tet_4::ContourSubElement_Tet_4( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth, + const int subelement_sign ) + : ContourSubElement( stk::topology::TETRAHEDRON_4, + coords, + side_ids, + in_owner, + in_subelement_depth, + subelement_sign ) +{ /* %TRACE% */ /* %TRACE% */ + // if this is a conformal element, return quickly + if ( subelement_sign != 0 ) + { + return; + } + + // snap to mesh + for (int n = 0; n < 4; ++n) + { + if (std::fabs(my_dist[n]) < my_owner->edge_linear_tolerance() * my_owner->length_scale()) + { + my_dist[n] = 0.0; + } + } + + // see if there is a crossing + bool have_crossing = false; + for ( int i = 1; i < my_num_nodes; i++ ) + { + if ( LevelSet::sign_change(my_dist[0], my_dist[i]) ) have_crossing = true; + } + + if ( have_crossing ) + { + // attempt conformal decomposition + int success = conformal_decomposition(); + ThrowErrorMsgIf(!success, " Conformal decomposition failed.\n"); + } + else + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + } +} + +int +ContourSubElement_Tet_4::conformal_decomposition() +{ /* %TRACE% */ /* %TRACE% */ + + // attempt to create 8 conforming tetrahedral subelements + // This attempt may unsuccessful if the resulting subelements + // are of poor quality + int success = true; // optimism + + // create 8, 4-noded tets + my_subelements.clear(); + my_subelements.reserve(8); + ContourSubElement *sub = NULL; + PointVec sub_coords(4,Vector3d::ZERO); + + // For any edge with a crossing, we will move the + // mid side node for that egdge to the crossing + // we will keep the modified locations of the nodes + // in a local vector of nodes (lcoords). + // We will also create local vectors for the distance and side_ids + // so that we can reorient the tet as discussed below. + PointVec lcoords = my_coords; + lcoords.resize(10,Vector3d::ZERO); + std::vector lsides = my_side_ids; + std::vector ldist = my_dist; + std::vector sub_ids(4); + std::vector is_on_surf(10); // initializes to 0 (false) + int sub_sign; + + // Find orientation of tet + // Specifically, orient such that we don't have nodes 0 and 2 on one side and + // nodes 1 and 3 on the other + if ( LevelSet::sign_change(my_dist[0],my_dist[1]) && + LevelSet::sign_change(my_dist[1],my_dist[2]) && + LevelSet::sign_change(my_dist[2],my_dist[3]) ) + { + lcoords[0] = my_coords[0]; + lcoords[1] = my_coords[3]; + lcoords[2] = my_coords[1]; + lcoords[3] = my_coords[2]; + ldist[0] = my_dist[0]; + ldist[1] = my_dist[3]; + ldist[2] = my_dist[1]; + ldist[3] = my_dist[2]; + lsides[0] = my_side_ids[2]; + lsides[1] = my_side_ids[1]; + lsides[2] = my_side_ids[3]; + lsides[3] = my_side_ids[0]; + } + + std::vector edge_node_ids(10); + edge_node_ids[0] = 0; + edge_node_ids[1] = 1; + edge_node_ids[2] = 2; + edge_node_ids[3] = 3; + edge_node_ids[4] = process_edge( 0, 1, 4, is_on_surf, lcoords, ldist ); + edge_node_ids[5] = process_edge( 1, 2, 5, is_on_surf, lcoords, ldist ); + edge_node_ids[6] = process_edge( 2, 0, 6, is_on_surf, lcoords, ldist ); + edge_node_ids[7] = process_edge( 0, 3, 7, is_on_surf, lcoords, ldist ); + edge_node_ids[8] = process_edge( 1, 3, 8, is_on_surf, lcoords, ldist ); + edge_node_ids[9] = process_edge( 2, 3, 9, is_on_surf, lcoords, ldist ); + + const int zero_sign = LevelSet::sign(0.0); + std::vector sub_degenerate(8); // initializes to zero (false) + + sub_degenerate[0] = is_degenerate(edge_node_ids,0,4,6,7); + sub_degenerate[1] = is_degenerate(edge_node_ids,4,1,5,8); + sub_degenerate[2] = is_degenerate(edge_node_ids,6,5,2,9); + sub_degenerate[3] = is_degenerate(edge_node_ids,7,8,9,3); + sub_degenerate[4] = is_degenerate(edge_node_ids,8,7,6,4); + sub_degenerate[5] = is_degenerate(edge_node_ids,6,9,8,5); + sub_degenerate[6] = is_degenerate(edge_node_ids,9,8,7,6); + sub_degenerate[7] = is_degenerate(edge_node_ids,5,6,4,8); + + // tet #1 + if (!sub_degenerate[0]) + { + sub_coords[0] = lcoords[0]; + sub_coords[1] = lcoords[4]; + sub_coords[2] = lcoords[6]; + sub_coords[3] = lcoords[7]; + sub_sign = LevelSet::sign(ldist[0]); + sub_ids[0] = lsides[0]; + sub_ids[1] = ((is_on_surf[4] && is_on_surf[6] && is_on_surf[7]) && (zero_sign != sub_sign || sub_degenerate[4])) ? -2 : -1; + sub_ids[2] = lsides[2]; + sub_ids[3] = lsides[3]; + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tet #2 + if (!sub_degenerate[1]) + { + sub_coords[0] = lcoords[4]; + sub_coords[1] = lcoords[1]; + sub_coords[2] = lcoords[5]; + sub_coords[3] = lcoords[8]; + sub_sign = LevelSet::sign(ldist[1]); + sub_ids[0] = lsides[0]; + sub_ids[1] = lsides[1]; + sub_ids[2] = ((is_on_surf[4] && is_on_surf[5] && is_on_surf[8]) && (zero_sign != sub_sign || sub_degenerate[7])) ? -2 : -1; + sub_ids[3] = lsides[3]; + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tet #3 + if (!sub_degenerate[2]) + { + sub_coords[0] = lcoords[6]; + sub_coords[1] = lcoords[5]; + sub_coords[2] = lcoords[2]; + sub_coords[3] = lcoords[9]; + sub_sign = LevelSet::sign(ldist[2]); + sub_ids[0] = ((is_on_surf[5] && is_on_surf[6] && is_on_surf[9]) && (zero_sign != sub_sign || sub_degenerate[5])) ? -2 : -1; + sub_ids[1] = lsides[1]; + sub_ids[2] = lsides[2]; + sub_ids[3] = lsides[3]; + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tet #4 + if (!sub_degenerate[3]) + { + sub_coords[0] = lcoords[7]; + sub_coords[1] = lcoords[8]; + sub_coords[2] = lcoords[9]; + sub_coords[3] = lcoords[3]; + sub_sign = LevelSet::sign(ldist[3]); + sub_ids[0] = lsides[0]; + sub_ids[1] = lsides[1]; + sub_ids[2] = lsides[2]; + sub_ids[3] = ((is_on_surf[7] && is_on_surf[8] && is_on_surf[9]) && (zero_sign != sub_sign || sub_degenerate[6])) ? -2 : -1; + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tet #5 + if (!sub_degenerate[4]) + { + sub_coords[0] = lcoords[8]; + sub_coords[1] = lcoords[7]; + sub_coords[2] = lcoords[6]; + sub_coords[3] = lcoords[4]; + sub_sign = LevelSet::sign( (is_on_surf[8] ? 0.0 : ldist[1]+ldist[3]) + + (is_on_surf[7] ? 0.0 : ldist[0]+ldist[3]) + + (is_on_surf[6] ? 0.0 : ldist[0]+ldist[2]) + + (is_on_surf[4] ? 0.0 : ldist[0]+ldist[1]) ); + sub_ids[0] = lsides[0]; // 8-7-4 + sub_ids[1] = ((is_on_surf[7] && is_on_surf[6] && is_on_surf[4]) && (zero_sign != sub_sign || sub_degenerate[0])) ? -2 : -1; + sub_ids[2] = ((is_on_surf[8] && is_on_surf[6] && is_on_surf[4]) && (zero_sign != sub_sign || sub_degenerate[7])) ? -2 : -1; + sub_ids[3] = ((is_on_surf[8] && is_on_surf[7] && is_on_surf[6]) && (zero_sign != sub_sign || sub_degenerate[6])) ? -2 : -1; + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tet #6 + if (!sub_degenerate[5]) + { + sub_coords[0] = lcoords[6]; + sub_coords[1] = lcoords[9]; + sub_coords[2] = lcoords[8]; + sub_coords[3] = lcoords[5]; + sub_sign = LevelSet::sign( (is_on_surf[6] ? 0.0 : ldist[0]+ldist[2]) + + (is_on_surf[9] ? 0.0 : ldist[2]+ldist[3]) + + (is_on_surf[8] ? 0.0 : ldist[1]+ldist[3]) + + (is_on_surf[5] ? 0.0 : ldist[1]+ldist[2]) ); + sub_ids[0] = ((is_on_surf[6] && is_on_surf[9] && is_on_surf[5]) && (zero_sign != sub_sign || sub_degenerate[2])) ? -2 : -1; + sub_ids[1] = lsides[1]; // 8-9-5 + sub_ids[2] = ((is_on_surf[6] && is_on_surf[8] && is_on_surf[5]) && (zero_sign != sub_sign || sub_degenerate[7])) ? -2 : -1; + sub_ids[3] = ((is_on_surf[6] && is_on_surf[9] && is_on_surf[8]) && (zero_sign != sub_sign || sub_degenerate[6])) ? -2 : -1; + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tet #7 + if (!sub_degenerate[6]) + { + sub_coords[0] = lcoords[9]; + sub_coords[1] = lcoords[8]; + sub_coords[2] = lcoords[7]; + sub_coords[3] = lcoords[6]; + sub_sign = LevelSet::sign( (is_on_surf[9] ? 0.0 : ldist[2]+ldist[3]) + + (is_on_surf[8] ? 0.0 : ldist[1]+ldist[3]) + + (is_on_surf[7] ? 0.0 : ldist[0]+ldist[3]) + + (is_on_surf[6] ? 0.0 : ldist[0]+ldist[2]) ); + sub_ids[0] = ((is_on_surf[9] && is_on_surf[8] && is_on_surf[6]) && (zero_sign != sub_sign || sub_degenerate[5])) ? -2 : -1; + sub_ids[1] = ((is_on_surf[8] && is_on_surf[7] && is_on_surf[6]) && (zero_sign != sub_sign || sub_degenerate[4])) ? -2 : -1; + sub_ids[2] = lsides[2]; // 9-7-6 + sub_ids[3] = ((is_on_surf[9] && is_on_surf[8] && is_on_surf[7]) && (zero_sign != sub_sign || sub_degenerate[3])) ? -2 : -1; + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // tet #8 + if (!sub_degenerate[7]) + { + sub_coords[0] = lcoords[5]; + sub_coords[1] = lcoords[6]; + sub_coords[2] = lcoords[4]; + sub_coords[3] = lcoords[8]; + sub_sign = LevelSet::sign( (is_on_surf[5] ? 0.0 : ldist[1]+ldist[2]) + + (is_on_surf[6] ? 0.0 : ldist[0]+ldist[2]) + + (is_on_surf[4] ? 0.0 : ldist[0]+ldist[1]) + + (is_on_surf[8] ? 0.0 : ldist[1]+ldist[3]) ); + sub_ids[0] = ((is_on_surf[5] && is_on_surf[6] && is_on_surf[8]) && (zero_sign != sub_sign || sub_degenerate[5])) ? -2 : -1; + sub_ids[1] = ((is_on_surf[6] && is_on_surf[4] && is_on_surf[8]) && (zero_sign != sub_sign || sub_degenerate[4])) ? -2 : -1; + sub_ids[2] = ((is_on_surf[5] && is_on_surf[4] && is_on_surf[8]) && (zero_sign != sub_sign || sub_degenerate[1])) ? -2 : -1; + sub_ids[3] = lsides[3]; // 4-5-6 + sub = new ContourSubElement_Tet_4( sub_coords, sub_ids, my_owner, my_subelement_depth+1, sub_sign ); + my_subelements.push_back( sub ); + } + + // check quality of subelements + // Here we assume that the linear tet is always decomposed into reasonable quality sub-tets + success = true; + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + for ( unsigned i=0; iphysical_quality() < 0.0 ) + { + krinolog << "low quality subelement: " << i << "\n" + << *my_subelements[i] << "\n" + << "parent:" << "\n" + << *this << "\n"; + } + } + } + + return success; +} + +int +ContourSubElement_Tet_4::process_edge( const int i0, + const int i1, + const int i2, + std::vector & is_on_surf, + PointVec & lcoords, + const std::vector & ldist ) +{ /* %TRACE% */ /* %TRACE% */ + int edge_node_id = i2; + is_on_surf[i2] = LevelSet::sign_change( ldist[i0], ldist[i1] ); + if ( is_on_surf[i2] ) + { + // tolerance chosen very small since degeneracies should already be eliminated + const double tol = std::numeric_limits::epsilon(); + + const double d0 = std::fabs(ldist[i0]); + const double d1 = std::fabs(ldist[i1]); + + // make calculation completely symmetric + if (d0 < d1) + { + const double linear_alpha = d0/(d0+d1); + + if (linear_alpha < tol) + { + edge_node_id = i0; + lcoords[i2] = lcoords[edge_node_id]; + } + else + { + const double alpha = my_owner->dist_is_linear() ? linear_alpha : + find_quadratic_crossing(ldist[i0],ldist[i1],my_owner->distance(0.5*(lcoords[i0]+lcoords[i1]))); + lcoords[i2] = (1.-alpha) * lcoords[i0] + alpha * lcoords[i1]; + } + } + else + { + const double linear_alpha = d1/(d1+d0); + + if (linear_alpha < tol) + { + edge_node_id = i1; + lcoords[i2] = lcoords[edge_node_id]; + } + else + { + const double alpha = my_owner->dist_is_linear() ? linear_alpha : + find_quadratic_crossing(ldist[i1],ldist[i0],my_owner->distance(0.5*(lcoords[i1]+lcoords[i0]))); + lcoords[i2] = (1.-alpha) * lcoords[i1] + alpha * lcoords[i0]; + } + } + } + else + { + // eliminate side node by sliding to one end or the other + const double d0 = std::fabs(ldist[i0]); + const double d1 = std::fabs(ldist[i1]); + const double epsilon = std::sqrt(std::numeric_limits::epsilon()) * (d0 + d1); + + if ( d0 > d1 + epsilon ) + { + edge_node_id = i0; + } + else if ( d1 > d0 + epsilon ) + { + edge_node_id = i1; + } + else + { + // tie breaker + const Vector3d phys0 = my_owner->coordinates(lcoords[i0]); + const Vector3d phys1 = my_owner->coordinates(lcoords[i1]); + + if ( is_more(phys1,phys0) ) + { + edge_node_id = i0; + } + else + { + edge_node_id = i1; + } + } + lcoords[i2] = lcoords[edge_node_id]; + } + + return edge_node_id; +} + +bool +ContourSubElement_Tet_4::is_degenerate( const std::vector & edge_node_ids, + const int i0, const int i1, const int i2, const int i3 ) +{ /* %TRACE% */ /* %TRACE% */ + + // DRN: This is really ugly, hand-optimized code for looking for degenerate tets + // Basically, it checks if any of the edges are degenerate. Then it has to look for degenerate + // faces that consist of 3 colinear points. These are handled by checking against + // the 6 specific bad cases. + + if ( edge_node_ids[i0] == edge_node_ids[i1] || + edge_node_ids[i0] == edge_node_ids[i2] || + edge_node_ids[i0] == edge_node_ids[i3] || + edge_node_ids[i1] == edge_node_ids[i2] || + edge_node_ids[i1] == edge_node_ids[i3] || + edge_node_ids[i2] == edge_node_ids[i3] ) + { + // this tet is degenerate with two coincident nodes + return true; + } + + if ( edge_node_ids[i0]==i0 && + edge_node_ids[i1]==i1 && + edge_node_ids[i2]==i2 && + edge_node_ids[i3]==i3 ) + { + // this tet is not degenerate since is has no degenerate nodes + return false; + } + + // look for a colinear face + std::vector is_used(10); // initializes to zero (false); + is_used[edge_node_ids[i0]] = true; + is_used[edge_node_ids[i1]] = true; + is_used[edge_node_ids[i2]] = true; + is_used[edge_node_ids[i3]] = true; + + if ((is_used[0] && ((is_used[1] && is_used[4]) || (is_used[2] && is_used[6]) || (is_used[3] && is_used[7]))) || + (is_used[1] && ((is_used[2] && is_used[5]) || (is_used[3] && is_used[8]))) || + (is_used[2] && is_used[3] && is_used[9])) + { + // this tet has a colinear face + return true; + } + + // look for all nodes on the same face + if ((!is_used[0] && !is_used[4] && !is_used[6] && !is_used[7]) || // all on face 1 + (!is_used[4] && !is_used[1] && !is_used[5] && !is_used[8]) || // all on face 2 + (!is_used[6] && !is_used[5] && !is_used[2] && !is_used[9]) || // all on face 0 + (!is_used[7] && !is_used[8] && !is_used[9] && !is_used[3])) // all on face 3 + { + // this tet has all nodes on one face + return true; + } + + return false; +} + +int +ContourSubElement_Tet_4::side_facets( Faceted_Surface & facets, + int side ) const +{ /* %TRACE% */ /* %TRACE% */ + ThrowAssert( my_side_ids[side] == -2 ); + + // just one linear facet per linear triangle + const int num_facets = 1; + + const unsigned * const lnn = get_side_node_ordinals(topology(), side); + + if ( LevelSet::sign_change(0.0, (double) my_sign) ) + { + std::unique_ptr facet = std::make_unique( my_owner->coordinates(my_coords[lnn[0]]), my_owner->coordinates(my_coords[lnn[1]]), my_owner->coordinates(my_coords[lnn[2]]) ); + facets.add( std::move(facet) ); + } + else + { + std::unique_ptr facet = std::make_unique( my_owner->coordinates(my_coords[lnn[0]]), my_owner->coordinates(my_coords[lnn[2]]), my_owner->coordinates(my_coords[lnn[1]]) ); + facets.add( std::move(facet) ); + } + + return( num_facets ); +} + +ContourSubElement_Tet_10::ContourSubElement_Tet_10( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth, + const int subelement_sign ) + : ContourSubElement( stk::topology::TETRAHEDRON_10, + coords, + side_ids, + in_owner, + in_subelement_depth, + subelement_sign ) +{ /* %TRACE% */ /* %TRACE% */ + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return; + } + + // use a single non-conformal, adaptive 4-noded tet + my_subelements.clear(); + my_subelements.reserve(1); + ContourSubElement *sub = NULL; + PointVec sub_coords(4,Vector3d::ZERO); + + sub_coords[0] = my_coords[0]; + sub_coords[1] = my_coords[1]; + sub_coords[2] = my_coords[2]; + sub_coords[3] = my_coords[3]; + + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, my_side_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); +} + +const int ContourSubElement_Adaptive_Tet_4::MAX_REFINMENT_LEVELS = 6; + +ContourSubElement_Adaptive_Tet_4::ContourSubElement_Adaptive_Tet_4( + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth ) + : ContourSubElement( stk::topology::TETRAHEDRON_4, + coords, + side_ids, + in_owner, + in_subelement_depth, + 0 ) +{ /* %TRACE% */ /* %TRACE% */ + my_edge_age.resize(6); // initializes to zero + non_conformal_decomposition(); +} + +ContourSubElement_Adaptive_Tet_4::ContourSubElement_Adaptive_Tet_4( + const PointVec & coords, + const std::vector & side_ids, + const std::vector & edge_age, + const ContourElement * in_owner, + const int in_subelement_depth ) + : ContourSubElement( stk::topology::TETRAHEDRON_4, + coords, + side_ids, + in_owner, + in_subelement_depth, + 0 ) +{ /* %TRACE% */ /* %TRACE% */ + my_edge_age = edge_age; + non_conformal_decomposition(); +} + +int +ContourSubElement_Adaptive_Tet_4::non_conformal_decomposition() +{ /* %TRACE% */ /* %TRACE% */ + int success = true; // optimism + + // Determine if we will continue to look for crossing within this element. + // This test should be conservative, proceeding to look for crossings if there + // is even a remote chance of a crossing (To avoid cracks in the surface). + + // find extrema + double max_dist = -std::numeric_limits::max(); + double min_dist = std::numeric_limits::max(); + for ( int n = 0; n < my_num_nodes; n++ ) + { + if (my_dist[n] < min_dist) min_dist = my_dist[n]; + if (my_dist[n] > max_dist) max_dist = my_dist[n]; + } + + const double variation = max_dist - min_dist; + + const bool all_hi = (min_dist - variation) > 0.0; + const bool all_lo = (max_dist + variation) < 0.0; + + if (all_hi || all_lo) + { + // correct the sign since we lie entirely on one side of the interface + my_sign = LevelSet::sign(my_dist[0]); + return success; + } + + int longest_bad_edge = -1; + + // use temporary storage for vertex and side nodes + PointVec lcoords = my_coords; + lcoords.resize(10,Vector3d::ZERO); + lcoords[4] = 0.5 * (my_coords[0] + my_coords[1]); + lcoords[5] = 0.5 * (my_coords[1] + my_coords[2]); + lcoords[6] = 0.5 * (my_coords[2] + my_coords[0]); + lcoords[7] = 0.5 * (my_coords[0] + my_coords[3]); + lcoords[8] = 0.5 * (my_coords[1] + my_coords[3]); + lcoords[9] = 0.5 * (my_coords[2] + my_coords[3]); + + PointVec lphyscoords(10); + for (int n = 0; n < 10; ++n) + { + lphyscoords[n] = my_owner->coordinates( lcoords[n] ); + } + + std::vector ldist = my_dist; + ldist.resize(10); + for (int n = 4; n < 10; ++n) + { + ldist[n] = my_owner->distance( lcoords[n] ); + } + + const stk::topology Top = stk::topology::TETRAHEDRON_10; + int num_edges = Top.num_edges(); + + std::vector bad_edges; + bad_edges.reserve(num_edges); + + std::vector edge_lengths(num_edges); + + for ( int edge = 0; edge < num_edges; edge++ ) + { + const unsigned * const lnn = get_edge_node_ordinals(Top, edge); + + ThrowAssert(Top.edge_topology(edge).num_nodes() == 3); + + const double edge_straight_length = (lphyscoords[lnn[0]] - lphyscoords[lnn[1]]).length(); + ThrowRequire(edge_straight_length > 0.0); + edge_lengths[edge] = edge_straight_length; + + const double edge_curve_error = (lphyscoords[lnn[2]] - 0.5*(lphyscoords[lnn[0]] + lphyscoords[lnn[1]])).length(); + + const double edge_dist_error = std::fabs(ldist[lnn[2]] - 0.5*(ldist[lnn[0]]+ldist[lnn[1]])); + + const double scale = std::min(std::sqrt(std::numeric_limits::max()),std::fabs(ldist[lnn[0]]) + std::fabs(ldist[lnn[1]]) + my_owner->length_scale()); + + const double edge_error = (edge_curve_error + edge_dist_error)*edge_straight_length/(scale*scale); + + if (edge_error > my_owner->edge_nonlinear_tolerance() && my_edge_age[edge] < MAX_REFINMENT_LEVELS) + { + bad_edges.push_back(edge); + } + } + + double max_length = 0.0; + for (auto edge : bad_edges) + { + const double edge_length = edge_lengths[edge]; + ThrowRequire(edge_length > 0.0); + + // we need an absolute mechanism for selecting the edge to bisect so that all elements that share + // common edges will make the same decisions + if (utility::is_more(edge_length,max_length)) + { + longest_bad_edge = edge; + max_length = edge_length; + } + else if (!utility::is_less(edge_length,max_length)) // tie breaker + { + const Vector3d & edge_midside_coords = lphyscoords[get_edge_node_ordinals(Top, edge)[2]]; + // note that it is safe to assume that longest_bad_edge is already assigned if edge_length == max_length + const Vector3d longest_edge_midside_coords = lphyscoords[get_edge_node_ordinals(Top, longest_bad_edge)[2]]; + + ThrowAssert((utility::is_not_equal(edge_midside_coords[0],longest_edge_midside_coords[0]) || + utility::is_not_equal(edge_midside_coords[1],longest_edge_midside_coords[1]) || + utility::is_not_equal(edge_midside_coords[2],longest_edge_midside_coords[2]))); + + if (utility::is_more(edge_midside_coords[0],longest_edge_midside_coords[0]) || + (!utility::is_less(edge_midside_coords[0],longest_edge_midside_coords[0]) && + (utility::is_more(edge_midside_coords[1],longest_edge_midside_coords[1]) || + (!utility::is_less(edge_midside_coords[1],longest_edge_midside_coords[1]) && + (utility::is_more(edge_midside_coords[2],longest_edge_midside_coords[2])))))) + { + longest_bad_edge = edge; + max_length = edge_length; + } + } + } + + if ( longest_bad_edge == -1 ) + { + // no bad edges + + // use a single nonconformal linear tet subelement + my_subelements.clear(); + my_subelements.reserve(1); + + ContourSubElement *sub = new ContourSubElement_Tet_4( my_coords, my_side_ids, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + } + else + { + // + // create 2, adaptive, 4-noded tetrahedra by cutting the longest_bad_edge + // + + my_subelements.clear(); + my_subelements.reserve(2); + ContourSubElement *sub = NULL; + PointVec sub_coords(4,Vector3d::ZERO); + std::vector sub_ids(4); + std::vector sub_edge_age(6); + + static const unsigned node_permute_0[] = { 0,1,2,3 }; + static const unsigned node_permute_1[] = { 1,2,0,3 }; + static const unsigned node_permute_2[] = { 2,0,1,3 }; + static const unsigned node_permute_3[] = { 3,0,2,1 }; + static const unsigned node_permute_4[] = { 1,3,2,0 }; + static const unsigned node_permute_5[] = { 3,2,1,0 }; + static const unsigned side_permute_0[] = { 0,1,2,3 }; + static const unsigned side_permute_1[] = { 1,2,0,3 }; + static const unsigned side_permute_2[] = { 2,0,1,3 }; + static const unsigned side_permute_3[] = { 0,3,1,2 }; + static const unsigned side_permute_4[] = { 0,2,3,1 }; + static const unsigned side_permute_5[] = { 2,3,0,1 }; + static const unsigned edge_permute_0[] = { 0,1,2,3,4,5 }; + static const unsigned edge_permute_1[] = { 1,2,0,4,5,3 }; + static const unsigned edge_permute_2[] = { 2,0,1,5,3,4 }; + static const unsigned edge_permute_3[] = { 3,2,5,4,0,1 }; + static const unsigned edge_permute_4[] = { 4,5,1,0,3,2 }; + static const unsigned edge_permute_5[] = { 5,1,4,3,2,0 }; + static const unsigned * node_permute_table[] = { node_permute_0, node_permute_1, node_permute_2, node_permute_3, node_permute_4, node_permute_5 }; + static const unsigned * side_permute_table[] = { side_permute_0, side_permute_1, side_permute_2, side_permute_3, side_permute_4, side_permute_5 }; + static const unsigned * edge_permute_table[] = { edge_permute_0, edge_permute_1, edge_permute_2, edge_permute_3, edge_permute_4, edge_permute_5 }; + + const unsigned * lnn = node_permute_table[longest_bad_edge]; + const unsigned * lsn = side_permute_table[longest_bad_edge]; + const unsigned * len = edge_permute_table[longest_bad_edge]; + + const Vector3d edge_node = 0.5 * (my_coords[lnn[0]] + my_coords[lnn[1]]); + + // tet #1 + sub_coords[0] = my_coords[lnn[0]]; + sub_coords[1] = edge_node; + sub_coords[2] = my_coords[lnn[2]]; + sub_coords[3] = my_coords[lnn[3]]; + sub_ids[0] = my_side_ids[lsn[0]]; + sub_ids[1] = -1; /* not on any parent side */ + sub_ids[2] = my_side_ids[lsn[2]]; + sub_ids[3] = my_side_ids[lsn[3]]; + sub_edge_age[0] = my_edge_age[len[0]]+1; + sub_edge_age[1] = my_edge_age[len[0]]+1; + sub_edge_age[2] = my_edge_age[len[2]]; + sub_edge_age[3] = my_edge_age[len[3]]; + sub_edge_age[4] = my_edge_age[len[0]]+1; + sub_edge_age[5] = my_edge_age[len[5]]; + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, sub_edge_age, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + + // tet #2 + sub_coords[0] = edge_node; + sub_coords[1] = my_coords[lnn[1]]; + sub_coords[2] = my_coords[lnn[2]]; + sub_coords[3] = my_coords[lnn[3]]; + sub_ids[0] = my_side_ids[lsn[0]]; + sub_ids[1] = my_side_ids[lsn[1]]; + sub_ids[2] = -1; /* not on any parent side */ + sub_ids[3] = my_side_ids[lsn[3]]; + sub_edge_age[0] = my_edge_age[len[0]]+1; + sub_edge_age[1] = my_edge_age[len[1]]; + sub_edge_age[2] = my_edge_age[len[0]]+1; + sub_edge_age[3] = my_edge_age[len[0]]+1; + sub_edge_age[4] = my_edge_age[len[4]]; + sub_edge_age[5] = my_edge_age[len[5]]; + sub = new ContourSubElement_Adaptive_Tet_4( sub_coords, sub_ids, sub_edge_age, my_owner, my_subelement_depth+1 ); + my_subelements.push_back( sub ); + } + + return success; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_ContourSubElement.hpp b/packages/krino/krino/krino_lib/Akri_ContourSubElement.hpp new file mode 100644 index 000000000000..b00b27325508 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ContourSubElement.hpp @@ -0,0 +1,311 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_ContourSubElement_h +#define Akri_ContourSubElement_h + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace krino { + +template +bool compare_ptr_by_global_id (const T* i, const T* j) { return (i->global_id() < j->global_id()); } + +class ContourSubElement { +public: + + ContourSubElement( const stk::topology topo, + const PointVec & coords, + const std::vector & side_ids, + const ContourElement * owner, + const int in_subelement_depth, + const int subelement_sign ); + + virtual ~ContourSubElement(); + + static bool is_more(const Vector3d & v1, const Vector3d & v2); + + double relative_volume() const; + double side_relative_area( const int side ) const; + double parametric_quality() const; + double physical_quality() const; + double side_quality(const int side) const; + + int num_intg_pts( const int intg_pt_sign ); + + int gather_intg_pts( const int intg_pt_sign, + sierra::ArrayContainer & intg_pt_locations, + sierra::ArrayContainer & intg_weights, + sierra::ArrayContainer & determinants, + int index = 0 ); + + int build_facets( Faceted_Surface & facets ); + + // default implementation + virtual int side_facets( Faceted_Surface & facets, int side ) const; + + stk::topology topology() const { return my_master_element.get_topology(); } + + int spatial_dim() const { return my_owner->spatial_dim(); } + int num_subelements() const { return my_subelements.size(); } + + std::vector & get_side_ids() { return my_side_ids; } + const std::vector & get_side_ids() const { return my_side_ids; } + + const PointVec & get_coords() const { return my_coords; } + const ContourElement * owner() const { return my_owner; } + int subelement_depth() const { return my_subelement_depth; } + + void dump_structure() const; + void dump_details() const; + + bool have_interface_sides() const; + + virtual std::ostream & put( std::ostream& os ) const; + + friend std::ostream & operator << ( std::ostream &os , const ContourSubElement &s ) { + return s.put(os); + } + + static double find_quadratic_crossing( const double d0, + const double d1, + const double d2 ); + +protected: + + const MasterElement& my_master_element; + const MasterElement& my_side_master_element; + int my_num_nodes; + int my_num_sides; + PointVec my_coords; + std::vector my_side_ids; + std::vector my_dist; + std::vector< ContourSubElement * > my_subelements; + const ContourElement * my_owner; + int my_subelement_depth; // depth down the tree of subelements (0 for the base_subelement) + int my_sign; // -1 for elements on negative side, +1 for positive side, 0 for non-conformal elements spanning interface + +private: + //: Default constructor not allowed + ContourSubElement(); +}; + +class ContourSubElement_Quad_4 : public ContourSubElement { +public: + ContourSubElement_Quad_4( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ); + virtual ~ContourSubElement_Quad_4() {} + +private: + //: Default constructor not allowed + ContourSubElement_Quad_4(); + + int non_conformal_decomposition(); +}; + +class ContourSubElement_Quad_9 : public ContourSubElement { +public: + ContourSubElement_Quad_9( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ); + + virtual ~ContourSubElement_Quad_9() {} + +private: + //: Default constructor not allowed + ContourSubElement_Quad_9(); + + int non_conformal_decomposition(); +}; + +class ContourSubElement_Hex_8 : public ContourSubElement { +public: + ContourSubElement_Hex_8( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ); + virtual ~ContourSubElement_Hex_8() {} + +private: + //: Default constructor not allowed + ContourSubElement_Hex_8(); + + int subpyramid_non_conformal_decomposition( const int face ); +}; + +class ContourSubElement_Hex_27 : public ContourSubElement { +public: + ContourSubElement_Hex_27( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ); + virtual ~ContourSubElement_Hex_27() {} + +private: + //: Default constructor not allowed + ContourSubElement_Hex_27(); + + int subpyramid_non_conformal_decomposition( const int face ); +}; + +class ContourSubElement_Wedge_6 : public ContourSubElement { +public: + ContourSubElement_Wedge_6( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner ); + virtual ~ContourSubElement_Wedge_6() {} + +private: + int subpyramid_non_conformal_decomposition( const int face ); +}; + +class ContourSubElement_Tri_3 : public ContourSubElement { +public: + ContourSubElement_Tri_3( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth = 0, + const int subelement_sign = 0 ); + virtual ~ContourSubElement_Tri_3() {} + + virtual int side_facets( Faceted_Surface & facets, int side ) const; + +private: + //: Default constructor not allowed + ContourSubElement_Tri_3(); + + int conformal_decomposition(); + + int process_edge( const int i0, + const int i1, + const int i2, + std::vector & is_on_surf, + PointVec & lnodes, + const std::vector & ldist ); + + bool is_degenerate( const std::vector & edge_node_ids, + const int i0, const int i1, const int i2 ); +}; + +class ContourSubElement_Adaptive_Tri_3 : public ContourSubElement { +public: + ContourSubElement_Adaptive_Tri_3( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth = 0); + ContourSubElement_Adaptive_Tri_3( const PointVec & coords, + const std::vector & side_ids, + const std::vector & edge_age, + const ContourElement * in_owner, + const int in_subelement_depth = 0); + virtual ~ContourSubElement_Adaptive_Tri_3() {} + +private: + //: Default constructor not allowed + ContourSubElement_Adaptive_Tri_3(); + + static const int MAX_REFINMENT_LEVELS; + + int non_conformal_decomposition(); + + std::vector my_edge_age; +}; + +class ContourSubElement_Tri_6 : public ContourSubElement { +public: + ContourSubElement_Tri_6( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth = 0, + const int subelement_sign = 0 ); + virtual ~ContourSubElement_Tri_6() {} + +private: + //: Default constructor not allowed + ContourSubElement_Tri_6(); +}; + +class ContourSubElement_Tet_4 : public ContourSubElement { +public: + ContourSubElement_Tet_4( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth = 0, + const int subelement_sign = 0 ); + virtual ~ContourSubElement_Tet_4() {} + + virtual int side_facets( Faceted_Surface & facets, int side ) const; + +private: + //: Default constructor not allowed + ContourSubElement_Tet_4(); + + int conformal_decomposition(); + + int process_edge( const int i0, + const int i1, + const int i2, + std::vector & is_on_surf, + PointVec & lnodes, + const std::vector & ldist ); + + bool is_degenerate( const std::vector & edge_node_ids, + const int i0, const int i1, const int i2, const int i3 ); +}; + +class ContourSubElement_Adaptive_Tet_4 : public ContourSubElement { +public: + ContourSubElement_Adaptive_Tet_4( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth = 0); + ContourSubElement_Adaptive_Tet_4( const PointVec & coords, + const std::vector & side_ids, + const std::vector & edge_age, + const ContourElement * in_owner, + const int in_subelement_depth = 0); + virtual ~ContourSubElement_Adaptive_Tet_4() {} + +private: + //: Default constructor not allowed + ContourSubElement_Adaptive_Tet_4(); + + static const int MAX_REFINMENT_LEVELS; + + int non_conformal_decomposition(); + + std::vector my_edge_age; +}; + +class ContourSubElement_Tet_10 : public ContourSubElement { +public: + ContourSubElement_Tet_10( const PointVec & coords, + const std::vector & side_ids, + const ContourElement * in_owner, + const int in_subelement_depth = 0, + const int subelement_sign = 0 ); + virtual ~ContourSubElement_Tet_10() {} + +private: + //: Default constructor not allowed + ContourSubElement_Tet_10(); +}; + +} // namespace krino + +#endif // Akri_ContourSubElement_h diff --git a/packages/krino/krino/krino_lib/Akri_Cutting_Surface.cpp b/packages/krino/krino/krino_lib/Akri_Cutting_Surface.cpp new file mode 100644 index 000000000000..2cef540c7dd0 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Cutting_Surface.cpp @@ -0,0 +1,170 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +#include +#include + +namespace krino{ + +int +Plane_Cutting_Surface::sign_at_position(const Vector3d & p_coords) const +{ + return my_plane.signed_distance(p_coords) < 0. ? -1 : 1; +} + +bool +Plane_Cutting_Surface::on_surface(const Vector3d & p_coords, const double tol) const +{ + return std::abs(my_plane.signed_distance(p_coords)) < tol; +} + +double +Plane_Cutting_Surface::interface_crossing_position(const Segment3d & edge) const +{ + const double signed_dist_node0 = my_plane.signed_distance(edge.GetNode(0)); + const double signed_dist_node1 = my_plane.signed_distance(edge.GetNode(1)); + + if((signed_dist_node0 < 0) == (signed_dist_node1 < 0)) + { + std::stringstream str; + str << "Failed to find intersection of plane " << my_plane << " with segment " << edge; + throw std::runtime_error(str.str()); + } + + const double pos = signed_dist_node0 / (signed_dist_node0-signed_dist_node1); + + return pos; +} + +std::string Plane_Cutting_Surface::print() const +{ + std::ostringstream os; + os << "Plane cutting surface, plane = " << my_plane; + return os.str(); +} + +Intersecting_Planes_Cutting_Surface::Intersecting_Planes_Cutting_Surface(const Vector3d & p0, const Vector3d & p1, const Vector3d & p2, const Vector3d & p3) +: Cutting_Surface() +{ + // Points p0 and p2 lie on the line common to the two planes + my_plane0.set_from_most_orthogonal_angle_of_triangle(p0,p1,p2); + my_plane1.set_from_most_orthogonal_angle_of_triangle(p2,p3,p0); + + myTriangleForPlane0IsLarger = Cross(p2-p0, p1-p0).length_squared() > Cross(p2-p0, p3-p0).length_squared(); + + // Need to keep track of whether the normals to the plane point "toward" each other (positive_dihedral = true) + // or "away" from each other (positive_dihedral = false). + // We check this by seeing if p1 and p2 are on the positive side of the other plane. + const double dist1 = my_plane1.signed_distance(p1); + const double dist3 = my_plane0.signed_distance(p3); + my_positive_dihedral = (std::abs(dist1) > std::abs(dist3)) ? (dist1>0) : (dist3>0); +} + +int +Intersecting_Planes_Cutting_Surface::sign_at_position(const Vector3d & p_coords) const +{ + const double signed_dist0 = my_plane0.signed_distance(p_coords); + const double signed_dist1 = my_plane1.signed_distance(p_coords); + if (my_positive_dihedral) + { + const double min = std::min(signed_dist0,signed_dist1); + return min < 0. ? -1 : 1; + } + const double max = std::max(signed_dist0,signed_dist1); + return max < 0. ? -1 : 1; +} + +bool +Intersecting_Planes_Cutting_Surface::on_surface(const Vector3d & p_coords, const double tol) const +{ + const double signed_dist0 = my_plane0.signed_distance(p_coords); + const double signed_dist1 = my_plane1.signed_distance(p_coords); + if (my_positive_dihedral) + { + return (signed_dist0 > -tol && std::abs(signed_dist1) < tol) || + (signed_dist1 > -tol && std::abs(signed_dist0) < tol); + } + return (signed_dist0 < tol && std::abs(signed_dist1) < tol) || + (signed_dist1 < tol && std::abs(signed_dist0) < tol); +} + +double +Intersecting_Planes_Cutting_Surface::interface_crossing_position(const Segment3d & edge) const +{ + const double signed_dist0_node0 = my_plane0.signed_distance(edge.GetNode(0)); + const double signed_dist0_node1 = my_plane0.signed_distance(edge.GetNode(1)); + const double signed_dist1_node0 = my_plane1.signed_distance(edge.GetNode(0)); + const double signed_dist1_node1 = my_plane1.signed_distance(edge.GetNode(1)); + + double pos0 = signed_dist0_node0 / (signed_dist0_node0-signed_dist0_node1); + double pos1 = signed_dist1_node0 / (signed_dist1_node0-signed_dist1_node1); + + const int sign_case_id = + (((signed_dist0_node0 < 0) == my_positive_dihedral) ? 0 : 1) + + (((signed_dist1_node0 < 0) == my_positive_dihedral) ? 0 : 2) + + (((signed_dist0_node1 < 0) == my_positive_dihedral) ? 0 : 4) + + (((signed_dist1_node1 < 0) == my_positive_dihedral) ? 0 : 8); + static const int case_id_from_sign_case_id [] = {-1,-1,-1, 2,-1,-1,-1, 1,-1,-1,-1, 0, 3, 1, 0,-1}; + + const int case_id = case_id_from_sign_case_id[sign_case_id]; + switch (case_id) + { + case 0: return pos0; + case 1: return pos1; + case 2: return (pos0 < pos1) ? pos0 : pos1; + case 3: return (pos0 < pos1) ? pos1 : pos0; + } + + // Either 0 or 2 true crossings. Not ok. + std::stringstream str; + str << "Failed to find intersection of Intersecting_Planes_Cutting_Surface with planes " + << my_plane0 << " and " << my_plane1 + << " with segment " << edge + << " with crossings " << pos0 << " and " << pos1 << " with diff " << pos0-pos1 << " sign_case_id " << sign_case_id; + throw std::runtime_error(str.str()); +} + +std::string Intersecting_Planes_Cutting_Surface::print() const +{ + std::ostringstream os; + os << "Intersecting planes cutting surface. Plane 0 = " << my_plane0 << " Plane 1 = " << my_plane1 + << " positive dihedral = " << std::boolalpha << my_positive_dihedral; + return os.str(); +} + +std::string print_plane_visualization(const Plane3d & plane, std::array & geomIds, const std::string & description) +{ + std::ostringstream os; + const Vector3d x0 = plane.constant() * plane.normal(); + const Vector3d x1 = x0 + plane.normal(); + os << "Create vertex " << x0[0] << " " << x0[1] << " " << x0[2] << std::endl; + os << "Create vertex " << x1[0] << " " << x1[1] << " " << x1[2] << std::endl; + os << "Create curve vertex " << ++geomIds[0] << " " << ++geomIds[0] << std::endl; + os << "Create planar surface curve " << ++geomIds[1] << " distance 0. # Plane " << ++geomIds[2] << ": " << description << std::endl; + geomIds[0] += 4; // 4 vertices created for plane + geomIds[1] += 4; // 4 curves created for plane + return os.str(); +} + +std::string Plane_Cutting_Surface::print_visualization(std::array & geomIds, const std::string & description) const +{ + return print_plane_visualization(my_plane, geomIds, description); +} + +std::string Intersecting_Planes_Cutting_Surface::print_visualization(std::array & geomIds, const std::string & description) const +{ + const std::string viz0 = print_plane_visualization(my_plane0, geomIds, description+", plane 1/2"); + const std::string viz1 = print_plane_visualization(my_plane1, geomIds, description+", plane 2/2"); + return viz0 + viz1; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Cutting_Surface.hpp b/packages/krino/krino/krino_lib/Akri_Cutting_Surface.hpp new file mode 100644 index 000000000000..51e764864f50 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Cutting_Surface.hpp @@ -0,0 +1,81 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Cutting_Surface_h +#define Akri_Cutting_Surface_h + +#include +#include +#include +#include +#include + +namespace krino { + +class Cutting_Surface +{ +public: + Cutting_Surface() {} + virtual ~Cutting_Surface() {} + virtual bool on_surface(const Vector3d & p_coords, const double tol) const = 0; + virtual int sign_at_position(const Vector3d & p_coords) const = 0; + virtual double interface_crossing_position(const Segment3d & edge) const = 0; + virtual std::string print() const = 0; + virtual std::string print_visualization(std::array & geomIds, const std::string & description) const = 0; + virtual const Plane3d & get_plane() const = 0; +}; + +class Plane_Cutting_Surface : public Cutting_Surface +{ +public: + // for 3D from 3 points on plane + Plane_Cutting_Surface(const Vector3d & p0, const Vector3d & p1, const Vector3d & p2) : Cutting_Surface() {my_plane.set_from_most_orthogonal_angle_of_triangle(p0,p1,p2);} + // for 2D or 3D from direction and point on plane + Plane_Cutting_Surface(const Vector3d & direction, const Vector3d & p0) : my_plane(direction,p0) {} + virtual ~Plane_Cutting_Surface() {} + virtual bool on_surface(const Vector3d & p_coords, const double tol) const override; + int sign_at_position(const Vector3d & p_coords) const override; + double interface_crossing_position(const Segment3d & edge) const override; + std::string print() const override; + virtual std::string print_visualization(std::array & geomIds, const std::string & description) const override; + virtual const Plane3d & get_plane() const override { return my_plane; } +private: + Plane3d my_plane; +}; + +class Plane_Cutting_Surface_2D : public Plane_Cutting_Surface +{ +public: + // for 2D from 2 points on line + Plane_Cutting_Surface_2D(const Vector3d & p0, const Vector3d & p1) : Plane_Cutting_Surface(Vector3d(p1[1]-p0[1],p0[0]-p1[0],0),p0) {} + virtual ~Plane_Cutting_Surface_2D() {} +private: +}; + + +class Intersecting_Planes_Cutting_Surface : public Cutting_Surface +{ +public: + Intersecting_Planes_Cutting_Surface(const Vector3d & p0, const Vector3d & p1, const Vector3d & p2, const Vector3d & p3); // 3D + virtual ~Intersecting_Planes_Cutting_Surface() {} + virtual bool on_surface(const Vector3d & p_coords, const double tol) const override; + int sign_at_position(const Vector3d & p_coords) const override; + double interface_crossing_position(const Segment3d & edge) const override; + std::string print() const override; + virtual std::string print_visualization(std::array & geomIds, const std::string & description) const override; + virtual const Plane3d & get_plane() const override { /* THIS IS A BAD IDEA. SHOULD WE TEST PLANARITY? */ return myTriangleForPlane0IsLarger ? my_plane0 : my_plane1; } +private: + bool myTriangleForPlane0IsLarger; + Plane3d my_plane0; + Plane3d my_plane1; + bool my_positive_dihedral; +}; + +} // namespace krino + +#endif // Akri_Cutting_Surface_h diff --git a/packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.cpp b/packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.cpp new file mode 100644 index 000000000000..71bc5488dd90 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.cpp @@ -0,0 +1,302 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" + +namespace krino { + +static bool owned_nodes_are_handled_by_other_procs(const stk::mesh::BulkData & mesh, + const std::vector & interfaceNodesWithoutMatchingCrossing, + const std::vector & interfaceNodesWithMatchingCrossing) +{ + stk::CommSparse commSparse(mesh.parallel()); + pack_entities_for_owning_proc(mesh, interfaceNodesWithMatchingCrossing, commSparse); + std::set ownedNodesWithRemoteMatchingCrossing; + unpack_entities_from_other_procs(mesh, ownedNodesWithRemoteMatchingCrossing, commSparse); + + for (auto node : interfaceNodesWithoutMatchingCrossing) + if (mesh.bucket(node).owned() && ownedNodesWithRemoteMatchingCrossing.count(node) == 0) + return false; + + return true; +} + +static bool any_node_of_edge_including_children_is_on_interface(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + const ParentsToChildMapper & parentToChildMapper, + const EdgeIntersection & edgeCrossing) +{ + std::vector edgeNodesIncludingChildren; + fill_edge_nodes(mesh, edgeCrossing.nodes[0], edgeCrossing.nodes[1], parentToChildMapper, edgeNodesIncludingChildren); + for (auto node : edgeNodesIncludingChildren) + if (node_is_on_interface(mesh, cdfemSupport, phaseSupport, node, edgeCrossing.interface)) + return true; + return false; +} + +static bool edge_crossings_have_matching_interface_node(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + const ParentsToChildMapper & parentToChildMapper, + const EdgeIntersection & edgeCrossing, + const double snapTol) +{ + const auto & edgeNodes = edgeCrossing.nodes; + const auto & crossingInterface = edgeCrossing.interface; + const double crossingPosition = edgeCrossing.crossingLocation; + if (crossingPosition < snapTol) + { + if (!node_is_on_interface(mesh, cdfemSupport, phaseSupport, edgeNodes[0], crossingInterface)) + return false; + } + else if (crossingPosition > 1.-snapTol) + { + if (!node_is_on_interface(mesh, cdfemSupport, phaseSupport, edgeNodes[1], crossingInterface)) + return false; + } + else if (!any_node_of_edge_including_children_is_on_interface(mesh, cdfemSupport, phaseSupport, parentToChildMapper, edgeCrossing)) + { + return false; + } + return true; +} + +static bool edges_with_crossings_have_matching_interface_nodes(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + const ParentsToChildMapper & parentToChildMapper, + const std::vector & edgeIntersections) +{ + const double snapTol = cdfemSupport.get_snapper().get_edge_tolerance(); + bool edgesWithCrossingHaveMatchingInterfaceNodes = true; + for (auto && edgeIntersection : edgeIntersections) + { + const EdgeIntersection edge(edgeIntersection); + if (!edge_crossings_have_matching_interface_node(mesh, cdfemSupport, phaseSupport, parentToChildMapper, edge, snapTol)) + { + edgesWithCrossingHaveMatchingInterfaceNodes = false; + break; + } + } + return stk::is_true_on_all_procs(mesh.parallel(), edgesWithCrossingHaveMatchingInterfaceNodes); +} + +static std::map> build_nodes_to_interfaces_within_tolerance(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const std::vector & edgeIntersections, + const ParentsToChildMapper & parentToChildMapper) +{ + + const double snapTol = cdfemSupport.get_snapper().get_edge_tolerance(); + std::vector parentEdgeNodes; + std::vector parentEdgeNodePositions; + + std::map> nodesToInterfacesWithinTolerance; + for (auto && edgeIntersection : edgeIntersections) + { + const EdgeIntersection edgeCrossing(edgeIntersection); + const auto & crossingInterface = edgeCrossing.interface; + const double crossingPosition = edgeCrossing.crossingLocation; + + parentEdgeNodes.clear(); + parentEdgeNodePositions.clear(); + fill_edge_nodes_and_positions(mesh, edgeCrossing.nodes[0], edgeCrossing.nodes[1], parentToChildMapper, parentEdgeNodes, parentEdgeNodePositions); + + for (size_t iNode=0; iNode> & nodesToInterfacesWithinTolerance) +{ + const auto mapIter = nodesToInterfacesWithinTolerance.find(node); + if (mapIter == nodesToInterfacesWithinTolerance.end()) + return false; + + const PhaseTag nodePhase = determine_phase_for_entity(mesh, node, phaseSupport); + const auto & nodeInterfacesWithinTolerance = mapIter->second; + for (auto && interfaceWithinTolerance : nodeInterfacesWithinTolerance) + if (phase_matches_interface(cdfemSupport, nodePhase, interfaceWithinTolerance)) + return true; + + return false; +} + +static void fill_interface_nodes_with_and_without_matching_crossing(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + const ParentsToChildMapper & parentToChildMapper, + const std::vector & edgeIntersections, + std::vector & interfaceNodesWithMatchingCrossing, + std::vector & interfaceNodesWithoutMatchingCrossing) +{ + interfaceNodesWithMatchingCrossing.clear(); + interfaceNodesWithoutMatchingCrossing.clear(); + + const std::map> nodesToInterfacesWithinTolerance = build_nodes_to_interfaces_within_tolerance(mesh, cdfemSupport, edgeIntersections, parentToChildMapper); + + for ( auto && bucket : mesh.buckets(stk::topology::NODE_RANK) ) + { + if (nodes_are_on_any_interface(mesh, phaseSupport, *bucket)) + { + for ( auto && node : *bucket ) + { + if (node_has_matching_interface_within_tolerance(mesh, cdfemSupport, phaseSupport, node, nodesToInterfacesWithinTolerance)) + interfaceNodesWithMatchingCrossing.push_back(node); + else + interfaceNodesWithoutMatchingCrossing.push_back(node); + } + } + } +} + +static bool interface_nodes_have_matching_edge_crossing(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + const ParentsToChildMapper & parentToChildMapper, + const std::vector & edgeIntersections) +{ + std::vector interfaceNodesWithMatchingCrossing; + std::vector interfaceNodesWithoutMatchingCrossing; + fill_interface_nodes_with_and_without_matching_crossing(mesh, cdfemSupport, phaseSupport, parentToChildMapper, edgeIntersections, interfaceNodesWithMatchingCrossing, interfaceNodesWithoutMatchingCrossing); + + if (stk::is_true_on_all_procs(mesh.parallel(), interfaceNodesWithoutMatchingCrossing.empty())) + return true; + + bool allOwnedNotSharedInterfaceNodesHaveMatchingEdgeCrossing = true; + for (auto node : interfaceNodesWithoutMatchingCrossing) + { + if (mesh.bucket(node).owned() && !mesh.bucket(node).shared()) + { + allOwnedNotSharedInterfaceNodesHaveMatchingEdgeCrossing = false; + break; + } + } + + if (!stk::is_true_on_all_procs(mesh.parallel(), allOwnedNotSharedInterfaceNodesHaveMatchingEdgeCrossing)) + return false; + + const bool allInterfaceNodesHaveMatchingEdgeCrossing = + owned_nodes_are_handled_by_other_procs(mesh, interfaceNodesWithoutMatchingCrossing, interfaceNodesWithMatchingCrossing); + + return stk::is_true_on_all_procs(mesh.parallel(), allInterfaceNodesHaveMatchingEdgeCrossing); +} + +void fill_neighbor_nodes(const stk::mesh::BulkData & mesh, + stk::mesh::Entity node, + const stk::mesh::Selector & elementSelector, + std::vector & neighborNodes) +{ + neighborNodes.clear(); + for (auto element : StkMeshEntities{mesh.begin_elements(node), mesh.end_elements(node)}) + if (elementSelector(mesh.bucket(element))) + for (auto nbr : StkMeshEntities{mesh.begin_nodes(element), mesh.end_nodes(element)}) + if (nbr != node) + neighborNodes.push_back(nbr); + stk::util::sort_and_unique(neighborNodes); +} + +static bool snap_displacements_at_node_are_small_compared_to_parent_edges(const stk::mesh::BulkData & mesh, + stk::mesh::Entity node, + FieldRef cdfemSnapField, + FieldRef coordsField, + const stk::mesh::Selector & parentElementSelector, + const double snapTol) +{ + const Vector3d snapDisplacements(field_data(cdfemSnapField, node), mesh.mesh_meta_data().spatial_dimension()); + const double snapDisplacmentsSqrLength = snapDisplacements.length_squared(); + const Vector3d nodeCoords(field_data(coordsField, node), mesh.mesh_meta_data().spatial_dimension()); + std::vector neighborNodes; + fill_neighbor_nodes(mesh, node, parentElementSelector, neighborNodes); + for (auto && nbr : neighborNodes) + { + const Vector3d nbrCoords(field_data(coordsField, nbr), mesh.mesh_meta_data().spatial_dimension()); + const double edgeSqrLength = (nbrCoords-nodeCoords).length_squared(); + if (snapDisplacmentsSqrLength > snapTol*snapTol * edgeSqrLength) + return false; + } + return true; +} + +static bool locally_snap_displacements_are_small_on_nodes_with_interfaces(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + FieldRef cdfemSnapField = cdfemSupport.get_cdfem_snap_displacements_field(); + if (cdfemSnapField.valid()) + { + const double snapTol = cdfemSupport.get_snapper().get_edge_tolerance(); + FieldRef oldCdfemSnapField = cdfemSupport.get_cdfem_snap_displacements_field().field_state(stk::mesh::StateOld); + const stk::mesh::Selector parentElementSelector = get_owned_parent_element_selector(mesh, activePart, cdfemSupport, phaseSupport); + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + for ( auto && bucket : mesh.buckets(stk::topology::NODE_RANK) ) + if (bucket->field_data_is_allocated(oldCdfemSnapField) && nodes_are_on_any_interface(mesh, phaseSupport, *bucket)) + for ( auto && node : *bucket ) + if (!snap_displacements_at_node_are_small_compared_to_parent_edges(mesh, node, oldCdfemSnapField, coordsField, parentElementSelector, snapTol)) + return false; + } + return true; +} + +static bool snap_displacements_are_small_on_nodes_with_interfaces(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + return stk::is_true_on_all_procs(mesh.parallel(), locally_snap_displacements_are_small_on_nodes_with_interfaces(mesh, activePart, cdfemSupport, phaseSupport)); +} + +bool decomposition_has_changed(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & interfaceGeometry, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + if (!snap_displacements_are_small_on_nodes_with_interfaces(mesh, activePart, cdfemSupport, phaseSupport)) + return true; + + const NodeToCapturedDomainsMap nodesToCapturedDomains; + const std::vector edgeIntersections = interfaceGeometry.get_edge_intersection_points(mesh, nodesToCapturedDomains); + + ParentsToChildMapper parentToChildMapper; + parentToChildMapper.build_map(mesh, activePart, cdfemSupport, phaseSupport); + + if (edges_with_crossings_have_matching_interface_nodes(mesh, cdfemSupport, phaseSupport, parentToChildMapper, edgeIntersections) && + interface_nodes_have_matching_edge_crossing(mesh, cdfemSupport, phaseSupport, parentToChildMapper, edgeIntersections)) + { + return false; + } + + return true; +} + + +} // namespace diff --git a/packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.hpp b/packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.hpp new file mode 100644 index 000000000000..f6aedb4fe6ad --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_DecompositionHasChanged.hpp @@ -0,0 +1,25 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_DECOMPOSITIONHASCHANGED_H_ +#define KRINO_INCLUDE_AKRI_DECOMPOSITIONHASCHANGED_H_ +#include +#include +#include +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" +namespace krino { + +bool decomposition_has_changed(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & interfaceGeometry, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport); + +} + +#endif /* KRINO_INCLUDE_AKRI_DECOMPOSITIONHASCHANGED_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_DiagWriter.cpp b/packages/krino/krino/krino_lib/Akri_DiagWriter.cpp new file mode 100644 index 000000000000..6135a722231b --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_DiagWriter.cpp @@ -0,0 +1,54 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include + +#include + +namespace krino { + +DiagWriterParser & +theDiagWriterParser() +{ + /* %TRACE% */ /* %TRACE% */ + static DiagWriterParser parser; + return parser; +} + + +stk::diag::Writer & +theDiagWriter() +{ + /* %TRACE[NONE]% */ /* %TRACE% */ + static stk::diag::Writer s_diagWriter(sierra::dwout().rdbuf(), theDiagWriterParser().parse(std::getenv("KRINOLOG"))); + + return s_diagWriter; +} + +DiagWriterParser::DiagWriterParser() + : stk::diag::WriterParser() +{ + /* %TRACE% */ /* %TRACE% */ + mask("debug", (unsigned long) (LOG_DEBUG), "Display debug diagnostic information"); + mask("subelement", (unsigned long) (LOG_SUBELEMENT), "Display subelement decomposition diagnostic information"); + mask("facets", (unsigned long) (LOG_FACETS), "Output exodus file with facets data"); + mask("parts", (unsigned long) (LOG_PARTS), "Display CDFEM parts diagnostic information"); +} + +namespace +{ + +void bootstrap() { sierra::Diag::registerWriter("krinolog", krinolog, theDiagWriterParser()); } + +stk::Bootstrap x(&bootstrap); +} // namespace + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_DiagWriter.hpp b/packages/krino/krino/krino_lib/Akri_DiagWriter.hpp new file mode 100644 index 000000000000..915dd4b42ae7 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_DiagWriter.hpp @@ -0,0 +1,63 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_DiagWriter_h +#define Akri_DiagWriter_h + +#include +#include +#include +#include + +#include + +namespace krino +{ + class DiagWriterParser : public stk::diag::WriterParser + { + public: + DiagWriterParser(); + }; + + stk::diag::Writer &theDiagWriter(); + DiagWriterParser &theDiagWriterParser(); + +#define krinolog krino::theDiagWriter() + +#ifdef KRINO_TRACE_ENABLED + typedef stk::diag::Tracespec Tracespec; + typedef stk::diag::Traceback Traceback; + + class Trace : public stk::diag::Trace + { + public: + Trace(const char *message) + : stk::diag::Trace(krinolog, message) + {} + }; + +#elif defined(KRINO_TRACE_TRACEBACK) + typedef stk::diag::Tracespec Tracespec; + typedef stk::diag::Traceback Traceback; + typedef stk::diag::Traceback Trace; + +#else + typedef stk::diag::Tracespec Tracespec; + typedef stk::diag::Tracespec Traceback; + typedef stk::diag::Tracespec Trace; +#endif + +#define ThrowRuntimeError(message) \ +{ \ + std::ostringstream internal_throw_runtime_message; \ + internal_throw_runtime_message << message; \ + throw std::runtime_error(internal_throw_runtime_message.str()); \ +} +} // namespace krino + +#endif // Akri_DiagWriter_h diff --git a/packages/krino/krino/krino_lib/Akri_DiagWriter_fwd.hpp b/packages/krino/krino/krino_lib/Akri_DiagWriter_fwd.hpp new file mode 100644 index 000000000000..d5aa04dc9174 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_DiagWriter_fwd.hpp @@ -0,0 +1,37 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_DiagWriter_fwd_h +#define Akri_DiagWriter_fwd_h + +#define KRINO_TRACE_ENABLED +// #define KRINO_TRACE_TRACEBACK + +#include + +namespace krino +{ + class DebugOut; + + enum + { + LOG_ALWAYS = stk::LOG_ALWAYS, + LOG_MEMBERS = stk::LOG_MEMBERS, + LOG_TRACE = stk::LOG_TRACE, + LOG_TRACE_STATS = stk::LOG_TRACE_STATS, + LOG_TRACE_SUB_CALLS = stk::LOG_TRACE_SUB_CALLS, + + LOG_DEBUG = 0x00010000, + LOG_SUBELEMENT = 0x00020000, + LOG_FACETS = 0x00040000, + LOG_PARTS = 0x00080000 + }; + +} // namespace krino + +#endif // Akri_DiagWriter_fwd_h diff --git a/packages/krino/krino/krino_lib/Akri_DistanceSweeper.cpp b/packages/krino/krino/krino_lib/Akri_DistanceSweeper.cpp new file mode 100644 index 000000000000..49a96dc635ea --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_DistanceSweeper.cpp @@ -0,0 +1,177 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace krino{ + +namespace { + +bool node_has_other_sign(stk::mesh::Entity node, const FieldRef distance_field, const double signed_narrow_band) +{ + double * distance = field_data(distance_field, node); + ThrowAssert(nullptr != distance); + return (signed_narrow_band < 0.) ? (*distance > 0.) : (*distance < 0.); +} + +bool node_is_inside_narrow_band(stk::mesh::Entity node, const FieldRef distance_field, const double signed_narrow_band) +{ + double * distance = field_data(distance_field, node); + ThrowAssert(nullptr != distance); + return (signed_narrow_band < 0.) ? (*distance > signed_narrow_band) : (*distance < signed_narrow_band); +} + +void get_nodes_ready_to_update(const AuxMetaData & aux_meta, const stk::mesh::BulkData& mesh, const FieldRef distance_field, const double signed_narrow_band, std::set & nodes_ready_to_update) +{ + stk::mesh::Selector field_not_ghost = aux_meta.active_not_ghost_selector() & stk::mesh::selectField(distance_field); + const stk::mesh::BucketVector & buckets = mesh.get_buckets( stk::topology::ELEMENT_RANK, field_not_ghost ); + std::vector elem_nodes_to_update; + + for ( auto && bucket : buckets ) + { + stk::topology elem_topology = bucket->topology(); + const unsigned num_nodes = elem_topology.num_nodes(); + + for ( auto && elem : *bucket ) + { + elem_nodes_to_update.clear(); + const stk::mesh::Entity* nodes = mesh.begin_nodes(elem); + bool have_node_with_other_sign = false; + for ( unsigned i = 0; i < num_nodes; ++i ) + { + stk::mesh::Entity node = nodes[i]; + if (has_field_data(distance_field, node)) + { + if (node_has_other_sign(node, distance_field, signed_narrow_band)) + { + have_node_with_other_sign = true; + } + else if (!node_is_inside_narrow_band(node, distance_field, signed_narrow_band)) + { + elem_nodes_to_update.push_back(node); + } + } + } + if (have_node_with_other_sign) + { + nodes_ready_to_update.insert(elem_nodes_to_update.begin(), elem_nodes_to_update.end()); + } + } + } +} + +void get_neighbor_nodes_ready_to_update(const AuxMetaData & aux_meta, + const stk::mesh::BulkData& mesh, + const FieldRef distance_field, + const double signed_narrow_band, + const bool check_if_nbr_is_outside_band, + stk::mesh::Entity node, + std::set & nodes_ready_to_update) +{ + const stk::mesh::Entity* node_elems = mesh.begin_elements(node); + const unsigned num_node_elements = mesh.num_elements(node); + + for (unsigned ielem = 0; ielem < num_node_elements; ++ ielem) + { + stk::mesh::Entity elem = node_elems[ielem]; + const unsigned num_nodes = mesh.num_nodes(elem); + const stk::mesh::Entity* elem_nodes = mesh.begin_nodes(elem); + for ( unsigned inode = 0; inode < num_nodes; ++inode ) + { + stk::mesh::Entity nbr_node = elem_nodes[inode]; + if (nbr_node != node && + has_field_data(distance_field, nbr_node) && + !node_has_other_sign(nbr_node, distance_field, signed_narrow_band) && + (!check_if_nbr_is_outside_band || !node_is_inside_narrow_band(nbr_node, distance_field, signed_narrow_band))) + { + nodes_ready_to_update.insert(nbr_node); + } + } + } +} + +void get_shared_nodes_ready_to_update(const AuxMetaData & aux_meta, const stk::mesh::BulkData& mesh, const FieldRef distance_field, const double signed_narrow_band, std::set & nodes_ready_to_update) +{ + stk::mesh::Selector field_globally_shared = mesh.mesh_meta_data().globally_shared_part() & stk::mesh::selectField(distance_field); + std::vector< stk::mesh::Entity> shared_nodes; + stk::mesh::get_selected_entities( field_globally_shared, mesh.buckets( stk::topology::NODE_RANK ), shared_nodes ); + const bool check_if_nbr_is_outside_band = true; + + for (auto && node : shared_nodes) + { + if (node_has_other_sign(node, distance_field, signed_narrow_band)) + { + get_neighbor_nodes_ready_to_update(aux_meta, mesh, distance_field, signed_narrow_band, check_if_nbr_is_outside_band, node, nodes_ready_to_update); + } + } +} + +} // anonymous + +void DistanceSweeper::fix_sign_by_sweeping(const stk::mesh::BulkData& mesh, const FieldRef distance_field, const double signed_narrow_band) +{ + const AuxMetaData & aux_meta = AuxMetaData::get(mesh.mesh_meta_data()); + if (signed_narrow_band == 0.0) return; + const int sign_to_fix = (signed_narrow_band < 0.) ? -1 : 1; + + std::set nodes_ready_to_update; + + get_nodes_ready_to_update(aux_meta, mesh, distance_field, signed_narrow_band, nodes_ready_to_update); + + bool done = false; + while (!done) + { + size_t num_nodes_updated = 0; + + const bool check_if_nbr_is_outside_band = false; + while (!nodes_ready_to_update.empty()) + { + ++num_nodes_updated; + stk::mesh::Entity node = *nodes_ready_to_update.begin(); + nodes_ready_to_update.erase(nodes_ready_to_update.begin()); + + double * distance = field_data(distance_field, node); + ThrowAssert(nullptr != distance); + *distance = -signed_narrow_band; + + get_neighbor_nodes_ready_to_update(aux_meta, mesh, distance_field, signed_narrow_band, check_if_nbr_is_outside_band, node, nodes_ready_to_update); + } + + done = true; + if (mesh.parallel_size() > 1) + { + const size_t local_num_nodes_updated = num_nodes_updated; + stk::all_reduce_sum(mesh.parallel(), &local_num_nodes_updated, &num_nodes_updated, 1); + + if (num_nodes_updated > 0) + { + done = false; + + std::vector parallel_fields(1, &distance_field.field()); + if (sign_to_fix > 0) stk::mesh::parallel_min(mesh, parallel_fields); + else stk::mesh::parallel_max(mesh, parallel_fields); + + get_shared_nodes_ready_to_update(aux_meta, mesh, distance_field, signed_narrow_band, nodes_ready_to_update); + } + } + } +} + +//---------------------------------------------------------------- +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_DistanceSweeper.hpp b/packages/krino/krino/krino_lib/Akri_DistanceSweeper.hpp new file mode 100644 index 000000000000..051cf9dc37cb --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_DistanceSweeper.hpp @@ -0,0 +1,22 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_DistanceSweeper_h +#define Akri_DistanceSweeper_h + +namespace stk { namespace mesh { class BulkData; } } +namespace krino { class FieldRef; } + +namespace krino { +namespace DistanceSweeper { + + void fix_sign_by_sweeping(const stk::mesh::BulkData& mesh, const FieldRef distance_field, const double signed_narrow_band); + +}} + +#endif // Akri_DistanceSweeper_h diff --git a/packages/krino/krino/krino_lib/Akri_Element.cpp b/packages/krino/krino/krino_lib/Akri_Element.cpp new file mode 100644 index 000000000000..3eb348e43e1a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Element.cpp @@ -0,0 +1,1077 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" +#include +namespace krino{ + +ElementObj::ElementObj(const stk::mesh::BulkData & stkMesh, stk::mesh::Entity elemEntity) + : my_master_elem(MasterElementDeterminer::getMasterElement(stkMesh.bucket(elemEntity).topology())), + my_entity(elemEntity), + my_entityId(stkMesh.identifier(elemEntity)), + my_prolongation_data(NULL) +{ +} + +ElementObj::ElementObj(const stk::topology elem_topology, const NodeVec & nodes) + : my_master_elem(MasterElementDeterminer::getMasterElement(elem_topology)), + my_nodes(nodes), + my_entity(), + my_entityId(0), + my_prolongation_data(NULL) +{ +} + +ElementObj::~ElementObj() {} + +void +ElementObj::integration_locations( + std::vector & intg_pt_locations, + const MasterElement & me) +{ + const unsigned num_intg_pts = me.num_intg_pts(); + const double * intg_pt_loc_ptr = me.intg_pt_locations(); + const unsigned dim = me.topology_dimension(); + + intg_pt_locations.resize(num_intg_pts); + for(unsigned i=0; i & intg_weights, // includes both gauss point weight and detJ + const int numCoordDims, + const std::vector & mesh_coords, + const MasterElement & me, + const MasterElement & mesh_me) +{ + const unsigned num_intg_pts = me.num_intg_pts(); + const unsigned dim = me.topology_dimension(); + + std::vector det_J(num_intg_pts); + double det_J_error; + + if ( me.get_topology() == mesh_me.get_topology() ) + { + mesh_me.determinant( numCoordDims, 1, mesh_coords.data(), det_J.data(), &det_J_error ); + } + else + { + const int num_coord_dofs = mesh_me.get_topology().num_nodes(); + const double * intg_pt_loc_ptr = me.intg_pt_locations(); + std::vector d_shapef_coords(num_coord_dofs*dim*num_intg_pts); + mesh_me.shape_fcn_deriv(num_intg_pts, intg_pt_loc_ptr, d_shapef_coords.data()); + mesh_me.determinant( + numCoordDims, // Number of coordinate dimensions + num_intg_pts, // Number of target points + num_coord_dofs, // Number of coord shape functions + d_shapef_coords.data(), // Mesh shape function derivatives + 1, // Number of elements + mesh_coords.data(), // Mesh coordinate values + det_J.data(), // Determinant of the transformation Jacobian for each element (output) + &det_J_error ); // Determinant error (output) + } + + const double * intg_wt_ptr = me.intg_weights(); + + intg_weights.resize(num_intg_pts); + for(unsigned i=0; i & result, unsigned dim, unsigned npe) +{ + if (npe == 0) + { + npe = mesh.bucket(element).topology().num_nodes(); + } + result.resize(npe*dim); + + const stk::mesh::Entity* elem_nodes = mesh.begin_nodes(element); + + for (unsigned n=0; n(field, elem_nodes[n]); + for (unsigned d=0; d coords; + ElementObj::gather_nodal_field(mesh, element, coords_field, coords, dim); + + const MasterElement& master_elem = MasterElementDeterminer::getMasterElement(mesh.bucket(element).topology()); + + std::vector intg_weights; + ElementObj::integration_weights( intg_weights, dim, coords, master_elem ); + + double vol = 0.0; + for ( double intg_wt : intg_weights ) + { + vol += intg_wt; + } + return vol; +} + +PhaseTag +ElementObj::update_phase(const CDMesh & mesh, const PhaseTag & startPhase, const InterfaceID interface_key, const int sign) +{ + PhaseTag phase = startPhase; + if (sign == -1 || sign == 1) + { + if (interface_key.is_single_ls()) + { + phase.add(mesh.ls_field(interface_key.first_ls()).identifier,sign); + } + else + { + const int other_ls_id = (sign < 0) ? interface_key.second_ls() : interface_key.first_ls(); + if (phase.contain(mesh.ls_field(other_ls_id).identifier, -1)) + { + const int ls_id = (sign < 0) ? interface_key.first_ls() : interface_key.second_ls(); + + phase.clear(); + phase.add(mesh.ls_field(ls_id).identifier, -1); + } + } + } + + return phase; +} + +PhaseTag +ElementObj::update_phase(const CDMesh & mesh, const PhaseTag & startPhase, const std::vector & interfaces, const std::vector & interfaceSigns) +{ + PhaseTag phase = startPhase; + const int numInterfaces = interfaces.size(); + ThrowRequire((int)interfaceSigns.size() == numInterfaces); + + if (numInterfaces > 0) + { + bool badConfig = false; + int iterCount = 0; + while (true) + { + PhaseTag iterationPhase = phase; + for (int i=0; i numInterfaces) + { + krinolog << "BAD INTERFACE CROSSING CONFIG WITH STARTING PHASE: " << startPhase << stk::diag::dendl; + for (int i=0; i & nodeOwnerCoords) const +{ + nodeOwnerCoords.clear(); + nodeOwnerCoords.reserve(my_nodes.size()); + + for ( auto && node : my_nodes ) + nodeOwnerCoords.push_back(node->owner_coords(owner)); +} + +Vector3d +ElementObj::compute_local_coords_from_owner_coordinates(const Mesh_Element * owner, const Vector3d & ptOwnerCoords) const +{ + std::vector nodeOwnerCoords; + fill_node_owner_coords(owner, nodeOwnerCoords); + + ThrowAssert((spatial_dim() == 2 && (nodeOwnerCoords.size() == 3 || nodeOwnerCoords.size() == 6)) || + (spatial_dim() == 3 && (nodeOwnerCoords.size() == 4 || nodeOwnerCoords.size() == 10))); + if (spatial_dim() == 2 && nodeOwnerCoords.size() == 6) + nodeOwnerCoords.resize(3); + else if (spatial_dim() == 3 && nodeOwnerCoords.size() == 10) + nodeOwnerCoords.resize(4); + + return get_parametric_coordinates_of_point(nodeOwnerCoords, ptOwnerCoords); +} + +void +ElementObj::integration_weights( + std::vector & intg_weights, // includes both gauss point weight and detJ + const MasterElement & me ) const +{ + const unsigned dim = spatial_dim(); + const unsigned nnodes = my_nodes.size(); + std::vector flat_coords(nnodes*dim); + for ( unsigned n = 0; n < nnodes; n++ ) + { + const Vector3d & node_coords = my_nodes[n]->coordinates(); + for (unsigned d=0; d intg_weights; + integration_weights( intg_weights, my_master_elem ); + + double vol = 0.0; + + for ( double intg_weight : intg_weights ) + { + vol += intg_weight; + } + return vol; +} + +void ElementObj::add_subelement(std::unique_ptr subelem) +{ + my_subelements.emplace_back(std::move(subelem)); +} + +void ElementObj::get_subelements( std::vector & subelems ) const +{ + if ( !my_subelements.empty() ) + { + for ( auto && subelem : my_subelements ) + { + subelem->get_subelements(subelems); + } + return; + } + + const SubElement * subelem = dynamic_cast( this ); + if (NULL != subelem) subelems.push_back(subelem); +} + +void ElementObj::get_subelements( std::vector & subelems ) +{ + if ( !my_subelements.empty() ) + { + for ( auto && subelem : my_subelements ) + { + subelem->get_subelements(subelems); + } + return; + } + + SubElement * subelem = dynamic_cast( this ); + if (NULL != subelem) subelems.push_back(subelem); +} + +bool +ElementObj::have_refined_edges() const +{ /* %TRACE% */ /* %TRACE% */ + + const stk::topology Top = topology(); + const int num_edges = Top.num_edges(); + + for ( int edge = 0; edge < num_edges; edge++ ) + { + const unsigned * const lnn = get_edge_node_ordinals(Top, edge); + + const int num_edge_nodes = Top.edge_topology(edge).num_nodes(); + ThrowRequire(2 == num_edge_nodes || 3 == num_edge_nodes); + + if ((2 == num_edge_nodes && + NULL != SubElementNode::common_child({my_nodes[lnn[0]], my_nodes[lnn[1]]})) || + (3 == num_edge_nodes && + (NULL != SubElementNode::common_child({my_nodes[lnn[0]], my_nodes[lnn[2]]}) || + NULL != SubElementNode::common_child({my_nodes[lnn[1]], my_nodes[lnn[2]]})))) + { + return true; + } + } + return false; +} + +void ElementObj::cut_interior_intersection_point(CDMesh & mesh, const Vector3d & pCoords, const std::vector & sortedDomains) +{ + throw std::runtime_error("Incorrect usage of ElementObj. The type of element cannot cut_interior_intersection_point."); +} + +void ElementObj::cut_face_intersection_point(const int iFace, const Vector3d & pCoords, const std::vector & sortedDomains) +{ + throw std::runtime_error("Incorrect usage of ElementObj. The type of element cannot cut_face_intersection_point."); +} + +bool ElementObj::captures_intersection_point_domains(const std::vector & intersectionPointDomains) const +{ + for (auto && node : my_nodes) + if (node->captures_intersection_point_domains(intersectionPointDomains)) + return true; + return false; +} + +void +ElementObj::prolongate_fields(const CDMesh & mesh) const +{ + const CDMesh* old_mesh = mesh.get_old_mesh(); + if (nullptr != old_mesh) + { + const FieldSet & element_fields = mesh.get_element_fields(); + if (element_fields.empty()) return; + + const ProlongationElementData * prolong_element = NULL; + const SubElement * subelem = dynamic_cast(this); + if (NULL == subelem) + { + prolong_element = old_mesh->fetch_prolong_element(entityId()); + } + else + { + prolong_element = old_mesh->fetch_prolong_element(subelem->get_owner().entityId()); + } + + //I think that adaptivity currently can lead to prolong_element == NULL: ThrowAssert(NULL != prolong_element); + + for(FieldSet::const_iterator it = element_fields.begin(); it != element_fields.end(); ++it) + { + const FieldRef field = *it; + + double * val = field_data(field, entity()); + if (NULL == val) continue; + + const unsigned field_length = field.length(); + + const double * owner_val = (NULL == prolong_element) ? NULL : prolong_element->get_field_data(field); + if (NULL == owner_val) + { + std::fill(val, val+field_length, 0.); + } + else + { + std::copy(owner_val, owner_val+field_length, val); + } + } + } +} + +const MasterElement* +ElementObj::get_evaluation_master_element(const FieldRef field) const +{ /* %TRACE% */ /* %TRACE% */ + // Supports Q1Q1, Q2Q2, and Q2Q1 cases + const MasterElement* calc_master_elem = &master_elem(); + const unsigned full_npe = master_elem().get_topology().num_nodes(); + ThrowAssert(field.type_is()); + + for ( unsigned n = 0; n < full_npe; n++ ) + { + double * data = field_data(field, my_nodes[n]->entity()); + if (nullptr == data) + { + calc_master_elem = &MasterElementDeterminer::getMasterElement(master_elem().get_topology().base()); + ThrowRequire(n >= calc_master_elem->num_nodes()); + break; + } + } + return calc_master_elem; +} + +void +ElementObj::evaluate_prolongation_field(const CDMesh & mesh, const FieldRef field, const unsigned field_length, const Vector3d & p_coords, double * result) const +{ /* %TRACE% */ /* %TRACE% */ + + // Figuring out the field master element here is actually quite hard since the entity may not exist any more. + // We'll assume that the field master element is the master_elem or the one with topology master_elem->get_topology().base(). + // This will handle the Q2Q1 case. + + for (unsigned i=0; i node_data(full_npe, nullptr); + const std::vector zeros(field_length,0.0); + + for ( int n = 0; n < full_npe; n++ ) + { + const ProlongationNodeData * prolong_data = mesh.fetch_prolong_node(my_nodes[n]->entityId()); + if (nullptr != prolong_data) node_data[n] = prolong_data->get_field_data(field); + if (node_data[n] == nullptr) + { + if (!initial_field.valid()) + { + initial_field = mesh.get_cdfem_support().get_initial_prolongation_field( field ); + } + if (initial_field.valid()) + { + node_data[n] = prolong_data->get_field_data(initial_field); + } + } + if (node_data[n] == nullptr) + { + calc_master_elem = &MasterElementDeterminer::getMasterElement(master_elem().get_topology().base()); + node_data[n] = zeros.data(); + } + } + + const int npe = calc_master_elem->get_topology().num_nodes(); + std::vector shapefcn (npe,0.); + calc_master_elem->shape_fcn(1, p_coords.data(), shapefcn.data()); + + for ( int n = 0; n < npe; n++ ) + { + ThrowRequire(nullptr != node_data[n]); + for (unsigned i=0; idetermine_decomposed_elem_phase(mesh); + } + // Phase for Mesh_Element with subelements is left empty + return; + } +} + +static std::function &)> +cut_element_intersecting_planes_diagonal_picker(const NodeVec & cutElemNodes) +{ + auto intersectingPlanesDiagonalPicker = + [&cutElemNodes](const std::array & faceNodes) + { + return ElementObj::determine_diagonal_for_internal_quad_of_cut_tet_from_owner_nodes(cutElemNodes[faceNodes[0]], cutElemNodes[faceNodes[1]], cutElemNodes[faceNodes[2]], cutElemNodes[faceNodes[3]]); + }; + return intersectingPlanesDiagonalPicker; +} + +std::function &)> +Mesh_Element::get_diagonal_picker() const +{ + return cut_element_intersecting_planes_diagonal_picker(my_nodes); +} + + +bool +Mesh_Element::is_single_coincident() const +{ + std::vector conformal_subelems; + get_subelements(conformal_subelems); + if(conformal_subelems.size() != 1) return false; + + const SubElement * subelem = conformal_subelems[0]; + if(subelem->topology() != coord_topology()) return false; + if (get_nodes() != subelem->get_nodes()) return false; + return true; +} + +Mesh_Element::~Mesh_Element() {} + +Mesh_Element::Mesh_Element(CDMesh & mesh, + stk::mesh::Entity elemEntity) + : ElementObj(mesh.stk_bulk(), elemEntity), + my_subelement_order(0), + my_have_interface(false) +{ + // set subelement specs + const stk::topology me_topology = my_master_elem.get_topology(); + + std::tie(my_subelement_topology, my_subelement_order) = determine_subelement_topology(me_topology); + ThrowRequire(stk::topology::INVALID_TOPOLOGY != my_subelement_topology); + + const unsigned npe_coords = me_topology.num_nodes(); + const stk::mesh::Entity* elem_nodes = mesh.stk_bulk().begin_nodes(my_entity); + + my_nodes.reserve(npe_coords); + const unsigned npe_base = me_topology.base().num_nodes(); + for (unsigned i=0; i npe_base) + { + my_nodes.resize(npe_coords); + ThrowRequireMsg(npe_coords == npe_base + me_topology.num_edges(), "Unexpected topology"); + for (unsigned edge_i=0; edge_i= npe_base && inode +Mesh_Element::determine_subelement_topology(stk::topology elem_topology) +{ + switch(elem_topology()) + { + case stk::topology::TRIANGLE_3_2D: + return std::pair(stk::topology::TRIANGLE_3_2D, 1); + case stk::topology::TRIANGLE_6_2D: + return std::pair(stk::topology::TRIANGLE_6_2D, 2); + case stk::topology::TETRAHEDRON_4: + return std::pair(stk::topology::TETRAHEDRON_4, 1); + case stk::topology::TETRAHEDRON_10: + return std::pair(stk::topology::TETRAHEDRON_10, 2); + default: + return std::pair(stk::topology::INVALID_TOPOLOGY, 0); + } +} + +Vector3d +Mesh_Element::get_node_parametric_coords( const int lnn ) const +{ + const double * nodal_parametric_coordinates = my_master_elem.nodal_parametric_coordinates(); + const int dim = spatial_dim(); + return Vector3d(&nodal_parametric_coordinates[lnn*dim],dim); +} + +static int get_local_node_number( const NodeVec & nodes, const SubElementNode * node ) +{ + for (size_t iNode=0; iNode conformal_subelems; + get_subelements(conformal_subelems); + + double minSqrDist = std::numeric_limits::max(); + for ( auto&& subelement : conformal_subelems ) + { + const Vector3d currentChildPCoords = subelement->compute_local_coords_from_owner_coordinates(this, ownerCoordinates); + const double currentChildSqrDist = compute_parametric_square_distance(currentChildPCoords); + if (currentChildSqrDist < minSqrDist) + { + minSqrDist = currentChildSqrDist; + child = subelement; + childPCoords = currentChildPCoords; + } + } +} + +Vector3d +Mesh_Element::coordinates( const Vector3d & p_coords ) const +{ /* %TRACE% */ /* %TRACE% */ + + const int npeCoords = my_nodes.size(); + std::vector shapeFcn(npeCoords); + my_master_elem.shape_fcn(1, p_coords.data(), shapeFcn.data()); + + Vector3d coords(Vector3d::ZERO); + for ( int n = 0; n < npeCoords; n++ ) + coords += shapeFcn[n] * my_nodes[n]->coordinates(); + + return coords; +} + +std::string Mesh_Element::visualize(const CDMesh & mesh) const +{ + std::ostringstream os; + os << "Debugging mesh element " << entityId() << " with cutter:\n" << myCutter->visualize(mesh.stk_bulk()) << "\n"; + return os.str(); +} + +double +Mesh_Element::interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const +{ + ThrowRequire(get_cutter()); + return get_cutter()->interface_crossing_position(interface, edge); +} + +static ElementIntersectionPointFilter build_element_intersection_filter(const NodeVec & nodes) +{ + auto filter = + [&nodes](const std::vector & intersectionPointDomains) + { + for (auto && node : nodes) + if (node->captures_intersection_point_domains(intersectionPointDomains)) + return false; + return true; + }; + return filter; +} + +void +Mesh_Element::fill_face_interior_intersections(const NodeVec & faceNodes, const InterfaceID & interface1, const InterfaceID & interface2, std::vector & faceIntersectionPoints) const +{ + ThrowRequire(get_cutter() && faceNodes.size() == 3); + const std::array faceNodeOwnerCoords = {{faceNodes[0]->owner_coords(this), faceNodes[1]->owner_coords(this), faceNodes[2]->owner_coords(this)}}; + const ElementIntersectionPointFilter intersectionPointFilter = build_element_intersection_filter(faceNodes); + get_cutter()->fill_tetrahedron_face_interior_intersections(faceNodeOwnerCoords, interface1, interface2, intersectionPointFilter, faceIntersectionPoints); +} + +int +Mesh_Element::interface_node_sign(const InterfaceID interface, const SubElementNode * node) const +{ + ThrowRequire(get_cutter()); + return get_cutter()->sign_at_position(interface, node->owner_coords(this)); +} + +double +Mesh_Element::interface_crossing_position(const InterfaceID interface, const SubElementNode * node1, const SubElementNode * node2) const +{ + ThrowRequire(get_cutter()); + Segment3d edge(node1->owner_coords(this), node2->owner_coords(this)); + return get_cutter()->interface_crossing_position(interface, edge); +} + +bool +Mesh_Element::is_prolonged() const +{ /* %TRACE% */ /* %TRACE% */ + for (auto && node : my_nodes) + { + if (!(node->is_prolonged())) return false; + } + return true; +} + +int Mesh_Element::get_interface_index(const InterfaceID interface) const +{ + ThrowAssert(have_interface(interface)); + const auto iter = std::lower_bound(myCuttingInterfaces.begin(), myCuttingInterfaces.end(), interface); + return std::distance(myCuttingInterfaces.begin(), iter); +} + +bool +Mesh_Element::triangulate(const CDMesh & mesh, const InterfaceGeometry & interfaceGeometry) +{ /* %TRACE% */ /* %TRACE% */ + + const auto & decomposedBlocksSelector = mesh.get_phase_support().get_all_decomposed_blocks_selector(); + const bool inDecomposedBlock = decomposedBlocksSelector(mesh.stk_bulk().bucket(entity())); + if (inDecomposedBlock) + { + create_cutter(mesh, interfaceGeometry); + + if (!myCuttingInterfaces.empty()) + my_have_interface = true; + + if (my_phase.empty()) + my_phase = interfaceGeometry.get_starting_phase(myCutter.get()); + else + my_have_interface = false; // uncut element with phase already set + } + + if (!have_subelements() && (have_interface() || have_refined_edges())) + { + create_base_subelement(); + } + + return false; +} + +const SubElementNode * get_node_matching_entity(const std::vector & nodes, stk::mesh::Entity stkNode) +{ + for (auto && node : nodes) + if (stkNode == node->entity()) + return node; + return nullptr; +} + +static IntersectionPointFilter +keep_all_intersecion_points_filter() +{ + auto filter = + [](const std::vector & intersectionPointNodes, const std::vector & intersectionPointSortedDomains) + { + return true; + }; + return filter; +} + +Vector3d Mesh_Element::get_intersection_point_parametric_coordinates(const IntersectionPoint & intersectionPoint) const +{ + Vector3d pCoords(Vector3d::ZERO); + const auto & intersectionPointNodes = intersectionPoint.get_nodes(); + const auto & intersectionPointWeights = intersectionPoint.get_weights(); + const size_t numIntersectionPointNodes = intersectionPointNodes.size(); + for (size_t iNode=0; iNodeowner_coords(this); + } + return pCoords; +} + +std::vector +Mesh_Element::get_interface_signs_based_on_crossings(const NodeVec & nodes) const +{ + std::vector nodeCoords; + std::vector*> nodeDomains; + + nodeCoords.clear(); + nodeDomains.clear(); + nodeCoords.reserve(nodes.size()); + nodeDomains.reserve(nodes.size()); + for (auto && node : nodes) + { + nodeCoords.push_back(node->owner_coords(this)); + nodeDomains.push_back(&node->get_sorted_node_domains()); + } + return get_cutter()->get_interface_signs_based_on_crossings(nodeCoords, nodeDomains); +} + +void +Mesh_Element::cut_interior_intersection_points(CDMesh & mesh) +{ /* %TRACE% */ /* %TRACE% */ + if (!have_interface()) + return; + + std::vector intersectionPoints; + stk::topology elementTopology = mesh.stk_bulk().bucket(entity()).topology(); + const MasterElement & masterElement = MasterElementDeterminer::getMasterElement(elementTopology); + const std::vector elementNodes(mesh.stk_bulk().begin_nodes(entity()), mesh.stk_bulk().end_nodes(entity())); + append_intersection_points_from_element_interior(masterElement, elementNodes, *myCutter, keep_all_intersecion_points_filter(), intersectionPoints); + + for (auto && intersectionPoint : intersectionPoints) + { + const Vector3d pCoords = get_intersection_point_parametric_coordinates(intersectionPoint); + + const ElementObj * containingElem = nullptr; + Vector3d containingElemPCoords; + find_child_coordinates_at_owner_coordinates(pCoords, containingElem, containingElemPCoords); + ThrowAssert(containingElem); + + ElementObj * elem = const_cast(containingElem); + if (!elem->captures_intersection_point_domains(intersectionPoint.get_sorted_domains())) + elem->cut_interior_intersection_point(mesh, containingElemPCoords, intersectionPoint.get_sorted_domains()); + } + + const std::vector interfaces = get_sorted_cutting_interfaces(); + for (size_t i1=0; i1cut_face_interior_intersection_points(mesh, interfaces[i1], interfaces[i2]); + } + } + + std::vector leafSubElements; + get_subelements(leafSubElements); + for (auto && leafSubElement : leafSubElements) + { + SubElement * subElem = const_cast(leafSubElement); + const std::vector interfaceSigns = get_interface_signs_based_on_crossings(subElem->get_nodes()); + subElem->set_interface_signs(interfaceSigns); + } +} + +void +Mesh_Element::create_cutter(const CDMesh & mesh, const InterfaceGeometry & interfaceGeometry) +{ /* %TRACE% */ /* %TRACE% */ + const auto intersectingPlanesDiagonalPicker = get_diagonal_picker(); + myCutter = interfaceGeometry.build_element_cutter(mesh.stk_bulk(), entity(), intersectingPlanesDiagonalPicker); + myCuttingInterfaces = myCutter->get_sorted_cutting_interfaces(); +} + +void +Mesh_Element::create_base_subelement() +{ /* %TRACE% */ /* %TRACE% */ + + stk::topology base_topology = coord_topology(); + + const unsigned num_sides = base_topology.num_sides(); + + std::vector parent_side_ids(num_sides); + for (unsigned i=0; i base_subelement; + if (stk::topology::TETRAHEDRON_4 == base_topology) + { + base_subelement = std::make_unique( my_nodes, parent_side_ids, this); + } + else if (stk::topology::TETRAHEDRON_10 == base_topology) + { + // purposely use lower order base subelement + std::vector sub_nodes(my_nodes.begin(), my_nodes.begin()+4); + base_subelement = std::make_unique( sub_nodes, parent_side_ids, this); + } + else if (stk::topology::TRIANGLE_3_2D == base_topology) + { + base_subelement = std::make_unique( my_nodes, parent_side_ids, this); + } + else if (stk::topology::TRIANGLE_6_2D == base_topology) + { + // purposely use lower order base subelement + std::vector sub_nodes(my_nodes.begin(), my_nodes.begin()+3); + base_subelement = std::make_unique( sub_nodes, parent_side_ids, this); + } + ThrowErrorMsgIf(!base_subelement, "Elements with topology " << base_topology.name() << " not supported for CDFEM."); + + base_subelement->initialize_interface_signs(); + add_subelement(std::move(base_subelement)); +} + +void +Mesh_Element::determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key) +{ + if (have_interface(interface_key)) + { + for(auto && subelement : my_subelements) + { + subelement->SubElement::determine_node_signs(mesh, interface_key); + } + } +} + +void +Mesh_Element::determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key) +{ + if (have_interface(interface_key)) + { + for(auto && subelement : my_subelements) + { + subelement->SubElement::determine_node_scores(mesh, interface_key); + } + } +} + +void +Mesh_Element::decompose(CDMesh & mesh, const InterfaceID interface_key) +{ + if (have_interface(interface_key)) + { + for(auto && subelement : my_subelements) + { + subelement->decompose(mesh, interface_key); + } + } +} + +void +Mesh_Element::handle_hanging_children(CDMesh & mesh, const InterfaceID & interface) +{ + if (!have_subelements() && have_edges_with_children()) + { + create_base_subelement(); + } + + for(auto && subelement : my_subelements) + { + subelement->handle_hanging_children(mesh, interface); + } +} + +void +Mesh_Element::build_quadratic_subelements(CDMesh & mesh) +{ + if (2 == subelement_order()) + { + for (auto && subelement : my_subelements) + { + subelement->build_quadratic_subelements(mesh); + } + } +} + +int +ElementObj::evaluate_quad(const SubElementNode * n0, const SubElementNode * n1, const SubElementNode * n2, const SubElementNode * n3) +{ /* %TRACE% */ /* %TRACE% */ + return evaluate_quad(n0->coordinates(), n1->coordinates(), n2->coordinates(), n3->coordinates()); +} + +int +ElementObj::evaluate_quad(const Vector3d & x0, const Vector3d & x1, const Vector3d & x2, const Vector3d & x3) +{ /* %TRACE% */ /* %TRACE% */ + // given 4 angles + // angle0 - angle subtended by (x3-x0) and (x1-x0) + // angle1 - angle subtended by (x0-x1) and (x2-x1) + // angle2 - angle subtended by (x1-x2) and (x3-x2) + // angle3 - angle subtended by (x2-x3) and (x0-x3) + // returns -1 if the max of angle 0 and angle 2 is significant larger than the max of angle 1 and angle 3 + // returns +1 if the opposite is true + // returns 0 if neither is true (the max angles are basically the same) OR one of the sides is degenerate + + const Vector3d side0 = x1 - x0; + const Vector3d side1 = x2 - x1; + const Vector3d side2 = x3 - x2; + const Vector3d side3 = x0 - x3; + + const double side_len0 = side0.length(); + const double side_len1 = side1.length(); + const double side_len2 = side2.length(); + const double side_len3 = side3.length(); + + // here, meas0 = -cos(angle0) * side_len0*side_len1*side_len2*side_len3 + const double meas0 = Dot(side3,side0) * side_len1*side_len2; + const double meas1 = Dot(side0,side1) * side_len2*side_len3; + const double meas2 = Dot(side1,side2) * side_len3*side_len0; + const double meas3 = Dot(side2,side3) * side_len0*side_len1; + + if ( krinolog.shouldPrint(LOG_DEBUG) ) + { + krinolog << "Lengths: " << side_len0 << ", " << side_len1 << ", " << side_len2 << ", " << side_len3 << "\n"; + krinolog << "Angle measures: " << meas0 << ", " << meas1 << ", " << meas2 << ", " << meas3 << "\n"; + krinolog << "Angles: " << std::acos(-meas0/(side_len0*side_len1*side_len2*side_len3))*180.0/3.141592654 << ", " + << std::acos(-meas1/(side_len0*side_len1*side_len2*side_len3))*180.0/3.141592654 << ", " + << std::acos(-meas2/(side_len0*side_len1*side_len2*side_len3))*180.0/3.141592654 << ", " + << std::acos(-meas3/(side_len0*side_len1*side_len2*side_len3))*180.0/3.141592654 << "\n"; + } + + const double meas02 = std::max(meas0,meas2); + const double meas13 = std::max(meas1,meas3); + + const double tol = std::numeric_limits::epsilon()*(side_len0*side_len1*side_len2*side_len3); + + if (meas02 > (meas13 + tol)) + { + return -1; + } + else if (meas13 > (meas02 + tol)) + { + return +1; + } + else + { + return 0; + } +} + +bool +ElementObj::have_edges_with_children() const +{ /* %TRACE% */ /* %TRACE% */ + const int num_edges = topology().num_edges(); + + // Iterate edges looking for any common children of the edge nodes + for ( int edge = 0; edge < num_edges; ++edge ) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(topology(), edge); + + const SubElementNode * node0 = my_nodes[edge_node_ordinals[0]]; + const SubElementNode * node1 = my_nodes[edge_node_ordinals[1]]; + const SubElementNode * child = SubElementNode::common_child({node0, node1}); + if( child ) + { + return true; + } + } + return false; +} + +bool +ElementObj::determine_diagonal_for_internal_quad_of_cut_tet_from_owner_nodes(const SubElementNode * pn0, const SubElementNode * pn1, const SubElementNode * pn2, const SubElementNode * pn3) +{ + // true: connect child of pn0 and pn2 (n0) to child of pn1 and pn3 (n2) + // false: connect child of pn1 and pn2 (n1) to child of pn0 and pn3 (n3) + + // This method is used for the cutter. For CUT_QUADS_BY_GLOBAL_IDENTIFIER, use logic that is consistent with the way elements are cut. + // For other strategies, still use id based logic because this is only being used for the cutter, and it will be removed when + // we use the actual facets instead of the cutter. + + stk::mesh::EntityId pn0_id = pn0->entityId(); + stk::mesh::EntityId pn1_id = pn1->entityId(); + stk::mesh::EntityId pn2_id = pn2->entityId(); + stk::mesh::EntityId pn3_id = pn3->entityId(); + + ThrowAssert(pn0_id != 0 && pn1_id != 0 && pn2_id != 0 && pn3_id != 0); + + if ((pn0_id < pn1_id) == (pn2_id < pn3_id)) + { + return true; + } + return false; +} + +bool +ElementObj::determine_diagonal_for_internal_quad_of_cut_tet_from_edge_nodes(const Simplex_Generation_Method simplexMethod, const SubElementNode * n0, const SubElementNode * n1, const SubElementNode * n2, const SubElementNode * n3, + const bool face0, const bool face1, const bool face2, const bool face3) +{ + // true: connect n0 and n2, false: connect n1 and n3 + + if (simplexMethod == CUT_QUADS_BY_GLOBAL_IDENTIFIER) + { + SubElementNodeAncestry ancestry0 = n0->get_ancestry(); + SubElementNodeAncestry ancestry1 = n1->get_ancestry(); + SubElementNodeAncestry ancestry2 = n2->get_ancestry(); + SubElementNodeAncestry ancestry3 = n3->get_ancestry(); + + SubElementNodeAncestry ancestry02 = std::min(ancestry0, ancestry2); + SubElementNodeAncestry ancestry13 = std::min(ancestry1, ancestry3); + + return (ancestry02 < ancestry13); + } + + // Make sure that diagonal can be picked either way and still avoid Steiner point + const int caseId = (face0 ? 1 : 0) + (face1 ? 2 : 0) + (face2 ? 4 : 0) + (face3 ? 8 : 0); + ThrowRequire(caseId == 3 || caseId == 5 || caseId == 10 || caseId == 12); + + return (evaluate_quad(n0,n1,n2,n3) == -1); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Element.hpp b/packages/krino/krino/krino_lib/Akri_Element.hpp new file mode 100644 index 000000000000..10f0f06a1da5 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Element.hpp @@ -0,0 +1,207 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Element_h +#define Akri_Element_h + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" +namespace krino { + +class SubElement; +class SubElementNode; +class ProlongationElementData; +class IntersectionPoint; +class ElementIntersection; + +class ElementObj { +public: + + ElementObj(const stk::mesh::BulkData & stkMesh, stk::mesh::Entity elemEntity); + ElementObj(const stk::topology elem_topology, const NodeVec & nodes); + + virtual ~ElementObj(); // Definition must in implementation file because SubElement is incomplete + + static bool is_less(const ElementObj * elem0,const ElementObj * elem1) { return (elem0->entityId() < elem1->entityId()); } + + static int evaluate_quad(const SubElementNode * n0, const SubElementNode * n1, const SubElementNode * n2, const SubElementNode * n3); + static int evaluate_quad(const Vector3d & x0, const Vector3d & x1, const Vector3d & x2, const Vector3d & x3); + static bool determine_diagonal_for_internal_quad_of_cut_tet_from_owner_nodes(const SubElementNode * pn0, const SubElementNode * pn1, const SubElementNode * pn2, const SubElementNode * pn3); + static bool determine_diagonal_for_internal_quad_of_cut_tet_from_edge_nodes(const Simplex_Generation_Method simplexMethod, const SubElementNode * n0, const SubElementNode * n1, const SubElementNode * n2, const SubElementNode * n3, + const bool face0, const bool face1, const bool face2, const bool face3); + + int spatial_dim() const { return my_master_elem.topology_dimension(); } + const NodeVec & get_nodes() const { return my_nodes; } + unsigned num_nodes() const { return my_nodes.size(); } + const PhaseTag & get_phase() const { return my_phase; } + void set_phase(const PhaseTag & phase) { my_phase = phase; } + stk::mesh::EntityId entityId() const { return my_entityId; } + bool check_entity(const stk::mesh::BulkData & mesh) const { return (my_entityId == 0) ? (my_entity == stk::mesh::Entity()) : (mesh.is_valid(my_entity) && my_entityId == mesh.identifier(my_entity)); } + stk::mesh::Entity entity() const { return my_entity; } + void set_entity( const stk::mesh::BulkData & mesh, stk::mesh::Entity meshEntity ) const { my_entity = meshEntity; my_entityId = mesh.identifier(my_entity); } + + const MasterElement & master_elem() const { return my_master_elem; } + stk::topology topology() const { return my_master_elem.get_topology(); } + void std_intg_pts( sierra::Array & intg_pt_locations, + sierra::Array & intg_weights, + sierra::ArrayContainer & det_J, + const sierra::ArrayContainer & coords, + const MasterElement & me ) const; + void std_intg_pts( sierra::Array & intg_pt_locations, + sierra::Array & intg_weights, + sierra::ArrayContainer & det_J, + const sierra::ArrayContainer & coords ) const { std_intg_pts(intg_pt_locations, intg_weights, det_J, coords, my_master_elem); } + + void fill_node_owner_coords(const Mesh_Element * owner, std::vector & coords) const; + Vector3d compute_local_coords_from_owner_coordinates(const Mesh_Element * owner, const Vector3d & coords) const; + + void integration_weights( + std::vector & intg_weights, // includes both gauss point weight and detJ + const MasterElement & me ) const; + void integration_weights( + std::vector & intg_weights ) const { return integration_weights(intg_weights, my_master_elem); } + static void integration_weights( + std::vector & intg_weights, // includes both gauss point weight and detJ + const int numCoordDims, + const std::vector & mesh_coords, + const MasterElement & me, + const MasterElement & mesh_me); + static void integration_weights( + std::vector & intg_weights, // includes both gauss point weight and detJ + const int numCoordDims, + const std::vector & mesh_coords, + const MasterElement & me) { integration_weights(intg_weights, numCoordDims, mesh_coords, me, me); } + static void integration_locations( + std::vector & intg_pt_locations, + const MasterElement & me); + static void gather_nodal_field(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef field, std::vector & result, unsigned dim = 1, unsigned npe = 0); + static double volume(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coords_field); + static PhaseTag update_phase(const CDMesh & mesh, const PhaseTag & startPhase, const InterfaceID interface_key, const int sign); + static PhaseTag update_phase(const CDMesh & mesh, const PhaseTag & startPhase, const std::vector & interfaces, const std::vector & interfaceSigns); + + const MasterElement* get_evaluation_master_element(const FieldRef field) const; + void evaluate_prolongation_field(const CDMesh & mesh, const FieldRef field, const unsigned field_length, const Vector3d & p_coords, double * result) const; + + void add_subelement(std::unique_ptr subelem); + void get_subelements( std::vector & subelems ) const; + void get_subelements( std::vector & subelems ); + bool have_subelements() const { return !my_subelements.empty(); } + unsigned num_subelements() const { return my_subelements.size(); } + bool have_refined_edges() const; + + virtual void determine_decomposed_elem_phase(const CDMesh & mesh) { ThrowRequire(false); } + virtual void cut_interior_intersection_point(CDMesh & mesh, const Vector3d & pCoords, const std::vector & sortedDomains); + virtual void cut_face_intersection_point(const int iFace, const Vector3d & pCoords, const std::vector & sortedDomains); + bool have_edges_with_children() const; + bool captures_intersection_point_domains(const std::vector & intersectionPointDomains) const; + bool face_captures_intersection_point_domains(const int iFace, const std::vector & intersectionPointDomains) const; + + ProlongationElementData * get_prolongation_data() const { return my_prolongation_data; } + void set_prolongation_data( ProlongationElementData * data ) const { my_prolongation_data = data; } + + void prolongate_fields(const CDMesh & mesh) const; + +protected: + const MasterElement& my_master_elem; + std::vector my_nodes; + std::vector> my_subelements; + PhaseTag my_phase; + mutable stk::mesh::Entity my_entity; + mutable stk::mesh::EntityId my_entityId; + mutable ProlongationElementData * my_prolongation_data; + +private: + //: Default constructor not allowed + ElementObj(); +}; + +class Mesh_Element : public ElementObj { +public: + + Mesh_Element(CDMesh & mesh, stk::mesh::Entity elemEntity); + + virtual ~Mesh_Element(); + + static bool is_supported_topology(stk::topology elem_topology) { return determine_subelement_topology(elem_topology).first != stk::topology::INVALID_TOPOLOGY; } + static std::pair determine_subelement_topology(stk::topology elem_topology); + + Vector3d get_node_parametric_coords( const int lnn ) const; + Vector3d get_node_parametric_coords( const SubElementNode * node ) const; + + const MasterElement & coord_master_elem() const { return my_master_elem; } + + stk::topology coord_topology() const { return my_master_elem.get_topology(); } + + const stk::topology & subelement_topology() const { return my_subelement_topology; } + int subelement_order() const { return my_subelement_order; } + + std::string visualize(const CDMesh & mesh) const; + double interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const; + int interface_node_sign(const InterfaceID interface, const SubElementNode * node) const; + void fill_face_interior_intersections(const NodeVec & faceNodes, const InterfaceID & interface1, const InterfaceID & interface2, std::vector & faceIntersectionPoints) const; + double interface_crossing_position(const InterfaceID interface, const SubElementNode * node1, const SubElementNode * node2) const; + std::function &)> get_diagonal_picker() const; + + const ElementCutter * get_cutter() const { return myCutter.get(); } + ElementCutter * get_cutter() { return myCutter.get(); } + + bool is_prolonged() const; + Vector3d coordinates( const Vector3d & p_coords ) const; + + PhaseTag determine_phase_at_location(const Vector3d & location) const; + bool have_interface() const { return my_have_interface; } + bool have_interface(const InterfaceID interface) const { return std::binary_search(myCuttingInterfaces.begin(), myCuttingInterfaces.end(), interface); } + int get_num_interfaces() const { return myCuttingInterfaces.size(); } + int get_interface_index(const InterfaceID interface) const; + const std::vector & get_sorted_cutting_interfaces() const { return myCuttingInterfaces; } + virtual void determine_decomposed_elem_phase(const CDMesh & mesh) override; + + bool triangulate(const CDMesh & mesh, const InterfaceGeometry & interfaceGeometry); //return value indicates if any changes were made + void create_cutter(const CDMesh & mesh, const InterfaceGeometry & interfaceGeometry); + void create_base_subelement(); + void determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key); + void determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key); + void decompose(CDMesh & mesh, const InterfaceID interface_key); + void handle_hanging_children(CDMesh & mesh, const InterfaceID & interface); + void build_quadratic_subelements(CDMesh & mesh); + std::vector get_interface_signs_based_on_crossings(const NodeVec & nodes) const; + void cut_interior_intersection_points(CDMesh & mesh); + + void find_child_coordinates_at_owner_coordinates(const Vector3d & ownerCoordinates, const ElementObj *& child, Vector3d & child_p_coords) const; + + double volume() const; + + bool is_single_coincident() const; + +private: + Mesh_Element() = delete; + Vector3d get_intersection_point_parametric_coordinates(const IntersectionPoint & intersectionPoint) const; + std::vector gather_nodal_coordinates() const; + + int my_subelement_order; + stk::topology my_subelement_topology; + bool my_have_interface; + + std::unique_ptr myCutter; + std::vector myCuttingInterfaces; +}; + +} // namespace krino + +#endif // Akri_Element_h diff --git a/packages/krino/krino/krino_lib/Akri_ElementCutterUtils.cpp b/packages/krino/krino/krino_lib/Akri_ElementCutterUtils.cpp new file mode 100644 index 000000000000..e640df211435 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ElementCutterUtils.cpp @@ -0,0 +1,184 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" +#include + +namespace krino { + +static void fill_nodal_shape_functions(const MasterElement & masterElement, const Vector3d & elementParametricCoords, std::vector & nodalShapeFunctions) +{ + const int npe = masterElement.num_nodes(); + nodalShapeFunctions.resize(npe); + masterElement.shape_fcn(1, elementParametricCoords.data(), nodalShapeFunctions.data()); +} + +static ElementIntersectionPointFilter build_element_intersection_filter(const std::vector & nodes, + const IntersectionPointFilter & intersectionPointFilter) +{ + auto filter = + [&nodes, &intersectionPointFilter](const std::vector & intersectionPointSortedDomains) + { + return intersectionPointFilter(nodes, intersectionPointSortedDomains); + }; + return filter; +} + +static void append_intersection_points_from_interior(const MasterElement & masterElement, + const std::vector & intersectionPointNodes, + const std::vector & interiorIntersections, + std::vector & intersectionPoints) +{ + if (interiorIntersections.empty()) + return; + + const bool intersectionPointIsOwned = true; + + std::vector intersectionPointWeights; + for (auto & interiorIntersection : interiorIntersections) + { + fill_nodal_shape_functions(masterElement, interiorIntersection.parametricCoords, intersectionPointWeights); + intersectionPoints.emplace_back(intersectionPointIsOwned, intersectionPointNodes, intersectionPointWeights, interiorIntersection.sortedDomains); + } +} + +static void append_intersection_points_from_element_face(const MasterElement & elementMasterElement, + const std::vector & elementNodes, + const int iFace, + const ElementCutter & elementCutter, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) +{ + std::vector interiorIntersections; + + const std::vector interfaces = elementCutter.get_sorted_cutting_interfaces(); + + std::array faceNodeOrdinals; + elementMasterElement.get_topology().face_node_ordinals(iFace, faceNodeOrdinals.data()); + const std::vector faceNodes{elementNodes[faceNodeOrdinals[0]], elementNodes[faceNodeOrdinals[1]], elementNodes[faceNodeOrdinals[2]]}; + const double * elemNodeParamCoords = elementMasterElement.nodal_parametric_coordinates(); + const std::array faceNodeCoordinates = {{Vector3d(elemNodeParamCoords+3*faceNodeOrdinals[0]), Vector3d(elemNodeParamCoords+3*faceNodeOrdinals[1]), Vector3d(elemNodeParamCoords+3*faceNodeOrdinals[2])}}; + + const MasterElement & faceMasterElement = MasterElementDeterminer::getMasterElement(elementMasterElement.get_topology().face_topology(iFace)); + const ElementIntersectionPointFilter faceIntersectionPointFilter = build_element_intersection_filter(faceNodes, intersectionPointFilter); + + std::vector sortedDomains; + for (size_t i1=0; i1 & elementNodes, + const int iFace) +{ + stk::mesh::EntityId elementId = mesh.identifier(element); + std::vector faceNodes(elementTopology.face_topology(iFace).num_nodes()); + std::vector faceElements; + elementTopology.face_nodes(elementNodes, iFace, faceNodes.data()); + stk::mesh::get_entities_through_relations(mesh, faceNodes, stk::topology::ELEMENT_RANK, faceElements); + + for (auto faceElement : faceElements) + if (mesh.identifier(faceElement) < elementId && elementSelector(mesh.bucket(faceElement))) + return false; + return true; +} + +static void append_intersection_points_from_within_element_and_owned_faces(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & parentElementSelector, + const stk::mesh::Entity element, + const InterfaceGeometry & geometry, + const std::function &)> & diagonalPicker, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) +{ + stk::topology elementTopology = mesh.bucket(element).topology(); + const MasterElement & masterElement = MasterElementDeterminer::getMasterElement(elementTopology); + const std::vector elementNodes(mesh.begin_nodes(element), mesh.end_nodes(element)); + + std::unique_ptr elementCutter = geometry.build_element_cutter(mesh, element, diagonalPicker); + if (elementCutter->might_have_interior_or_face_intersections()) + { + append_intersection_points_from_element_interior(masterElement, elementNodes, *elementCutter, intersectionPointFilter, intersectionPoints); + + const int numFaces = masterElement.get_topology().num_faces(); + if (numFaces > 0) + { + for (int iFace=0; iFace &)> +temporary_build_always_true_diagonal_picker() +{ + auto diagonalPicker = + [](const std::array & faceNodes) + { + return true; + }; + return diagonalPicker; +} + +void append_intersection_points_from_within_elements_and_owned_faces(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & parentElementSelector, + const std::vector & elements, + const InterfaceGeometry & geometry, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) +{ + const auto diagonalPicker = temporary_build_always_true_diagonal_picker(); + + for (auto element : elements) + { + if (parentElementSelector(mesh.bucket(element))) + { + append_intersection_points_from_within_element_and_owned_faces(mesh, + parentElementSelector, + element, + geometry, + diagonalPicker, + intersectionPointFilter, + intersectionPoints); + } + } +} + +void append_intersection_points_from_element_interior(const MasterElement & masterElement, + const std::vector & nodes, + const ElementCutter & elementCutter, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) +{ + std::vector interiorIntersections; + const ElementIntersectionPointFilter elementIntersectionPointFilter = build_element_intersection_filter(nodes, intersectionPointFilter); + elementCutter.fill_interior_intersections(elementIntersectionPointFilter, interiorIntersections); + append_intersection_points_from_interior(masterElement, nodes, interiorIntersections, intersectionPoints); +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_ElementCutterUtils.hpp b/packages/krino/krino/krino_lib/Akri_ElementCutterUtils.hpp new file mode 100644 index 000000000000..051f02e028fd --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ElementCutterUtils.hpp @@ -0,0 +1,37 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_ELEMENTCUTTERUTILS_H_ +#define KRINO_INCLUDE_AKRI_ELEMENTCUTTERUTILS_H_ +#include +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" + +namespace krino { + +void append_intersection_points_from_element_interior(const MasterElement & masterElement, + const std::vector & nodes, + const ElementCutter & elementCutter, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints); + +void append_intersection_points_from_within_elements_and_owned_faces(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & parentElementSelector, + const std::vector & elements, + const InterfaceGeometry & geometry, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints); + +} + +#endif /* KRINO_INCLUDE_AKRI_ELEMENTCUTTERUTILS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_Element_Cutter.cpp b/packages/krino/krino/krino_lib/Akri_Element_Cutter.cpp new file mode 100644 index 000000000000..bea70cbe6fdb --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Element_Cutter.cpp @@ -0,0 +1,1064 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Akri_MasterElementDeterminer.hpp" + +namespace krino { + +bool all_interfaces_have_single_level_set(const std::vector &interfaces) +{ + for (auto && interface : interfaces) + if (interface.first_ls() != interface.second_ls()) + return false; + return true; +} + +void fill_sorted_domains(const bool isOneLSPerPhase,const InterfaceID & interface0, const InterfaceID & interface1, std::vector & sortedDomains) +{ + sortedDomains.clear(); + if (isOneLSPerPhase) + { + + sortedDomains = {interface0.first_ls(), interface0.second_ls(), interface1.first_ls(), interface1.second_ls()}; + stk::util::sort_and_unique(sortedDomains); + } + else + { + ThrowAssert(all_interfaces_have_single_level_set({interface0, interface1})); + sortedDomains = {interface0.first_ls(), interface1.first_ls()}; + } +} + +void fill_sorted_domains(const bool isOneLSPerPhase,const InterfaceID & interface0, const InterfaceID & interface1, const InterfaceID & interface2, std::vector & sortedDomains) +{ + sortedDomains.clear(); + if (isOneLSPerPhase) + { + + sortedDomains = {interface0.first_ls(), interface0.second_ls(), interface1.first_ls(), interface1.second_ls(), interface2.first_ls(), interface2.second_ls()}; + stk::util::sort_and_unique(sortedDomains); + } + else + { + ThrowAssert(all_interfaces_have_single_level_set({interface0, interface1, interface2})); + sortedDomains = {interface0.first_ls(), interface1.first_ls(), interface2.first_ls()}; + } +} + +bool sorted_domains_form_triple_point(const bool is_one_interface_per_phase, const std::vector & sortedDomains) +{ + return !is_one_interface_per_phase || sortedDomains.size() == 3; +} + +bool sorted_domains_form_quadruple_point(const bool is_one_interface_per_phase, const std::vector & sortedDomains) +{ + return !is_one_interface_per_phase || sortedDomains.size() == 4; +} + +void fill_tetrahedron_face_surface_intersection_points(const int iFace, const Cutting_Surface * surf0, const Cutting_Surface * surf1, const std::vector & sortedDomains, std::vector & intersections) +{ + Vector3d intersectionPoint; + if (find_intersection_of_two_planes_and_side_of_tet(iFace, surf0->get_plane(), surf1->get_plane(), intersectionPoint)) + intersections.emplace_back(intersectionPoint, sortedDomains); +} + +void fill_tetrahedron_interior_surface_intersection_points(const Cutting_Surface * surf0, const Cutting_Surface * surf1, const Cutting_Surface * surf2, const std::vector & sortedDomains, std::vector & intersections) +{ + Vector3d intersectionPoint; + if (find_intersection_of_three_planes_within_tet(surf0->get_plane(), surf1->get_plane(), surf2->get_plane(), intersectionPoint)) + intersections.emplace_back(intersectionPoint, sortedDomains); +} + +std::string visualize_element(const stk::topology & topology, std::array & usedGeomIds) +{ + const auto & masterElem = MasterElementDeterminer::getMasterElement(topology); + const double * elemNodeParamCoords = masterElem.nodal_parametric_coordinates(); + + const unsigned numNodes = topology.num_nodes(); + const int dim = topology.dimension(); + + std::ostringstream os; + for (unsigned n=0; n usedGeomIds{0,0,0,0}; + std::string viz = visualize_element(topology, usedGeomIds); + for (auto && cuttingSurf : cuttingSurfaces) + { + std::ostringstream os; + os << cuttingSurf.first; + const std::string interfaceDesc = "on interface " + os.str(); + viz += cuttingSurf.second->print_visualization(usedGeomIds, interfaceDesc); + } + return viz; +} + + +void Element_Cutter::fill_triangle_interior_intersection_points(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const +{ + std::vector interfacesWithCuttingSurface; + fill_interfaces_with_cutting_surface(interfacesWithCuttingSurface); + + std::vector sortedDomains; + std::set> alreadyFound; + Vector3d intersectionPoint; + + intersections.clear(); + for (size_t index0=0; index0get_plane(); + const Plane3d & plane1 = cutting_surfaces.at(interfacesWithCuttingSurface[index1])->get_plane(); + if (find_intersection_of_two_2D_planes_within_tri(plane0, plane1, intersectionPoint) && + intersection_point_is_real(intersectionPoint, sortedDomains)) + { + intersections.emplace_back(intersectionPoint, sortedDomains); + } + } + } + } +} + +void Element_Cutter::fill_tetrahedron_face_interior_intersections(const std::array & faceNodes, + const InterfaceID & interface1, + const InterfaceID & interface2, + const ElementIntersectionPointFilter & intersectionPointFilter, + std::vector & intersections) const +{ + const Plane3d facePlane{faceNodes[0], faceNodes[1], faceNodes[2]}; + + std::vector sortedDomains; + Vector3d intersectionPoint; + + intersections.clear(); + fill_sorted_domains(is_one_ls_per_phase(), interface1, interface2, sortedDomains); + if (sorted_domains_form_triple_point(is_one_ls_per_phase(), sortedDomains)) + { + const Plane3d & plane0 = cutting_surfaces.at(interface1)->get_plane(); + const Plane3d & plane1 = cutting_surfaces.at(interface2)->get_plane(); + if (intersectionPointFilter(sortedDomains) && + find_intersection_of_three_planes(plane0, plane1, facePlane, intersectionPoint) && + intersection_point_is_real(intersectionPoint, sortedDomains)) + { + const Vector3d faceCoords = triangle_parametric_coordinates_of_projected_point(faceNodes, intersectionPoint); + if (within_tri_bounds(faceCoords)) + intersections.emplace_back(faceCoords, sortedDomains); + } + } +} + +void Element_Cutter::fill_tetrahedron_interior_intersection_points(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const +{ + std::vector interfacesWithCuttingSurface; + fill_interfaces_with_cutting_surface(interfacesWithCuttingSurface); + + std::vector sortedDomains; + std::set> alreadyFound; + Vector3d intersectionPoint; + + intersections.clear(); + for (size_t index0=0; index0get_plane(); + const Plane3d & plane1 = cutting_surfaces.at(interfacesWithCuttingSurface[index1])->get_plane(); + const Plane3d & plane2 = cutting_surfaces.at(interfacesWithCuttingSurface[index2])->get_plane(); + if (find_intersection_of_three_planes_within_tet(plane0, plane1, plane2, intersectionPoint) && + intersection_point_is_real(intersectionPoint, sortedDomains)) + { + intersections.emplace_back(intersectionPoint, sortedDomains); + } + } + } + } + } +} + +static ElementIntersectionPointFilter +keep_all_intersecion_points_filter() +{ + auto filter = + [](const std::vector & intersectionPointSortedDomains) + { + return true; + }; + return filter; +} + +void Element_Cutter::fill_interior_intersections(std::vector & intersections) const +{ + fill_interior_intersections(keep_all_intersecion_points_filter(), intersections); +} + +void Element_Cutter::fill_interior_intersections(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const +{ + if (myTopology.base() == stk::topology::TETRAHEDRON_4) + fill_tetrahedron_interior_intersection_points(intersectionPointFilter, intersections); + else if (myTopology.base() == stk::topology::TRIANGLE_3_2D || myTopology.base() == stk::topology::TRIANGLE_3) + fill_triangle_interior_intersection_points(intersectionPointFilter, intersections); + else + ThrowErrorMsg("Unexepected topology " << myTopology.name()); +} + +std::unique_ptr create_element_cutter(const bool oneLSPerPhase, + const MasterElement & masterElem, + const std::vector & elementParentEdges, + const std::vector & areParentEdgesAreOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker) +{ + std::unique_ptr cutter; + if(oneLSPerPhase) + cutter.reset(new One_LS_Per_Phase_Cutter(masterElem, elementParentEdges, areParentEdgesAreOrientedSameAsElementEdges, intersectingPlanesDiagonalPicker)); + else + cutter.reset(new LS_Per_Interface_Cutter(masterElem, elementParentEdges, areParentEdgesAreOrientedSameAsElementEdges, intersectingPlanesDiagonalPicker)); + + return cutter; +} + + +int +Element_Cutter::sign_at_position(const InterfaceID interface, const Vector3d & p_coords) const +{ + const auto iter = cutting_surfaces.find(interface); + if(iter == cutting_surfaces.end()) + { + // Right now, One_LS_Per_Phase_Cutter does not build up the cutting surfaces for the single_phase case. So + // we need a "default" answer. + return +1; + } + const Cutting_Surface & interface_surface = *(iter->second); + + return interface_surface.sign_at_position(p_coords); +} + +double +Element_Cutter::interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const +{ + ThrowErrorMsgIf(cutting_surfaces.find(interface) == cutting_surfaces.end(), + "No cutting surface found for interface " << interface); + const Cutting_Surface & interface_surface = *(cutting_surfaces.find(interface)->second); + return interface_surface.interface_crossing_position(edge); +} + +void +Element_Cutter::fill_interfaces_with_cutting_surface(std::vector & interfaces) const +{ + interfaces.clear(); + for (auto && entry : cutting_surfaces) + interfaces.push_back(entry.first); +} + +int +Element_Cutter::get_starting_phase_for_cutting_surfaces() const +{ + if (cutting_surfaces.empty()) + return -1; + return cutting_surfaces.begin()->first.first_ls(); +} + +bool +Element_Cutter::have_cutting_surface(const InterfaceID interface) const +{ + return cutting_surfaces.find(interface) != cutting_surfaces.end(); +} + +Cutting_Surface * +Element_Cutter::get_cutting_surface(const InterfaceID interface) const +{ + const auto iter = cutting_surfaces.find(interface); + if (iter != cutting_surfaces.end()) + return iter->second.get(); + return nullptr; +} + +static std::vector< std::array > get_element_edge_node_ordinals(const stk::topology baseTopology) +{ + const unsigned numEdges = baseTopology.num_edges(); + std::vector< std::array > elemEdgeNodeOrdinals(numEdges); + for(unsigned i=0; i < numEdges; ++i) + { + const unsigned * node_ordinals = get_edge_node_ordinals(baseTopology, i); + elemEdgeNodeOrdinals[i][0] = node_ordinals[0]; + elemEdgeNodeOrdinals[i][1] = node_ordinals[1]; + } + return elemEdgeNodeOrdinals; +} + +static std::vector +get_parametric_node_coords_on_element_nodes_and_cut_edges(const MasterElement & masterElem, + const std::vector & cutEdges, + const std::vector< std::array > elemEdgeNodeOrdinals) +{ + const stk::topology baseTopology = masterElem.get_topology().base(); + const double * elemNodeParamCoords = masterElem.nodal_parametric_coordinates(); + + const unsigned numNodes = baseTopology.num_nodes(); + const unsigned numEdges = baseTopology.num_edges(); + const int dim = baseTopology.dimension(); + + std::vector nodeParamCoords(numNodes+numEdges); + + for (unsigned n=0; n +get_node_signs_on_element_nodes(const stk::topology baseTopology, + const std::vector & cutEdges, + const std::vector< std::array > elemEdgeNodeOrdinals) +{ + const unsigned numNodes = baseTopology.num_nodes(); + std::vector nodeSigns(numNodes,-2); + + unsigned edge_error = 0; + for (auto && cut_edge : cutEdges) + { + const unsigned edge = cut_edge.edge; + const unsigned node0_ord = elemEdgeNodeOrdinals[edge][0]; + const unsigned node1_ord = elemEdgeNodeOrdinals[edge][1]; + + int node0_sign = -cut_edge.sign; + int node1_sign = cut_edge.sign; + + if (cut_edge.pos < Element_Cutter::theSnapToNodeTol) + node0_sign = 0; + else if (cut_edge.pos > 1.-Element_Cutter::theSnapToNodeTol) + node1_sign = 0; + + if ((nodeSigns[node0_ord]*node0_sign == -1) || + (nodeSigns[node1_ord]*node1_sign == -1)) + edge_error += 1< 0) + { + std::ostringstream err_msg; + err_msg << "Edge crossings are not consistent: " << std::endl; + for(auto && cut_edge : cutEdges) + { + const unsigned edge = cut_edge.edge; + const double pos = cut_edge.pos; + const int sign = cut_edge.sign; + const unsigned node0_ord = elemEdgeNodeOrdinals[edge][0]; + const unsigned node1_ord = elemEdgeNodeOrdinals[edge][1]; + err_msg << " Edge: " << edge << " error = " << ((edge_error & (1< 0) << " pos = " << pos << " sign = " << sign << " node0_ord = " << node0_ord << " node1_ord = " << node1_ord << std::endl; + } + krinolog << err_msg.str(); + throw std::runtime_error(err_msg.str()); + } + + return nodeSigns; +} + +static std::shared_ptr +build_triangle_cutting_surface(const std::vector & nodeSigns, const std::vector & nodeParamCoords) +{ + const int case_id = (nodeSigns[0]+1) + + (nodeSigns[1]+1)*3 + + (nodeSigns[2]+1)*9; + + static const unsigned case_permutations[] = + { 0, 0, 0, 2, 0, 0, 2, 1, 1, 1, // 0-9 + 1, 2, 2, 0, 2, 2, 1, 1, 1, 1, // 10-19 + 2, 0, 0, 2, 0, 0, 0 }; // 20-26 + + stk::topology topo = stk::topology::TRIANGLE_6_2D; + std::vector permute(6); + topo.permutation_node_ordinals(case_permutations[case_id], permute.begin()); + + const unsigned i0 = permute[0]; + const unsigned i1 = permute[1]; + const unsigned i2 = permute[2]; + const unsigned i3 = permute[3]; + const unsigned i5 = permute[5]; + + const int permute_case_id = + (nodeSigns[i0]+1) + + (nodeSigns[i1]+1)*3 + + (nodeSigns[i2]+1)*9; + + switch (permute_case_id) + { + case 1: // ls[0]==0 && ls[1]<0 && ls[2]<0 + case 25: // ls[0]==0 && ls[1]>0 && ls[2]>0 + { + ThrowAssert((nodeSigns[i0] == 0 && nodeSigns[i1] < 0 && nodeSigns[i2] < 0) || (nodeSigns[i0] == 0 && nodeSigns[i1] > 0 && nodeSigns[i2] > 0)); + return (permute_case_id==1) ? + (std::make_shared(nodeParamCoords[i0], (nodeParamCoords[i0] + nodeParamCoords[i1] - nodeParamCoords[i2]))) : + (std::make_shared(nodeParamCoords[i0], (nodeParamCoords[i0] + nodeParamCoords[i2] - nodeParamCoords[i1]))); + } + break; + + case 2: // ls[0]>0 && ls[1]<0 && ls[2]<0 + case 24: // ls[0]<0 && ls[1]>0 && ls[2]>0) + { + ThrowAssert((nodeSigns[i0] > 0 && nodeSigns[i1] < 0 && nodeSigns[i2] < 0) || (nodeSigns[i0] < 0 && nodeSigns[i1] > 0 && nodeSigns[i2] > 0)); + return (permute_case_id==2) ? + (std::make_shared(nodeParamCoords[i5], nodeParamCoords[i3])) : + (std::make_shared(nodeParamCoords[i3], nodeParamCoords[i5])); + } + break; + + case 5: // ls[0]>0 && ls[1]==0 && ls[2]<0 + case 21: // ls[0]<0 && ls[1]==0 && ls[2]>0 + { + ThrowAssert((nodeSigns[i0] > 0 && nodeSigns[i1] == 0 && nodeSigns[i2] < 0) || (nodeSigns[i0] < 0 && nodeSigns[i1] == 0 && nodeSigns[i2] > 0)); + return (permute_case_id==5) ? + (std::make_shared(nodeParamCoords[i5], nodeParamCoords[i1])) : + (std::make_shared(nodeParamCoords[i1], nodeParamCoords[i5])); + } + break; + + case 4: // ls[0]==0 && ls[1]==0 && ls[2]<0 + case 22: // ls[0]==0 && ls[1]==0 && ls[2]>0 + { + ThrowAssert((nodeSigns[i0] == 0 && nodeSigns[i1] == 0 && nodeSigns[i2] < 0) || (nodeSigns[i0] == 0 && nodeSigns[i1] == 0 && nodeSigns[i2] > 0)); + return (permute_case_id==4) ? + (std::make_shared(nodeParamCoords[i0], nodeParamCoords[i1])) : + (std::make_shared(nodeParamCoords[i1], nodeParamCoords[i0])); + } + break; + + default: + { + krinolog << "Case id " << case_id << " " << permute_case_id << stk::diag::dendl; + ThrowRuntimeError("Subelement decomposition error."); + } + } + + ThrowRuntimeError("Subelement decomposition error."); +} + +static std::shared_ptr +build_tetrahedral_cutting_surface(const std::vector & nodeSigns, + const std::vector & nodeParamCoords, + const std::function &)> & intersectingPlanesDiagonalPicker) +{ + const int case_id = (nodeSigns[0]+1) + + (nodeSigns[1]+1)*3 + + (nodeSigns[2]+1)*9 + + (nodeSigns[3]+1)*27; + + static const unsigned case_permutations[] = + { 0, 0, 0, 1, 0, 0, 1, 5, 0, 2, // 0-9 + 2, 6, 1, 0, 0, 1, 1,10, 2, 2, // 10-19 + 2,11, 2, 4, 1, 8, 4, 4, 3, 3, // 20-29 + 4, 3, 3, 9, 5, 7, 7, 6, 6, 9, // 30-39 + 0, 9, 9, 6, 7, 7, 7, 9,11, 3, // 40-49 + 4, 3, 3, 4, 4, 8, 3, 4, 4,11, // 50-59 + 4, 2, 2,10, 8, 1,10, 0, 1, 6, // 60-69 + 2, 2, 7, 5, 1, 0, 0, 1, 0, 0, // 70-79 + 0 }; + + stk::topology full_topology = stk::topology::TETRAHEDRON_10; + std::vector permute_node_ordinals(10); + full_topology.permutation_node_ordinals(case_permutations[case_id], permute_node_ordinals.begin()); + + const unsigned i0 = permute_node_ordinals[0]; + const unsigned i1 = permute_node_ordinals[1]; + const unsigned i2 = permute_node_ordinals[2]; + const unsigned i3 = permute_node_ordinals[3]; + const unsigned i4 = permute_node_ordinals[4]; + const unsigned i5 = permute_node_ordinals[5]; + const unsigned i6 = permute_node_ordinals[6]; + const unsigned i7 = permute_node_ordinals[7]; + const unsigned i8 = permute_node_ordinals[8]; + + const int permute_case_id = (nodeSigns[i0]+1) + + (nodeSigns[i1]+1)*3 + + (nodeSigns[i2]+1)*9 + + (nodeSigns[i3]+1)*27; + + switch (permute_case_id) + { + case 1: // ls[0]=0 && ls[1]<0 && ls[2]<0 && ls[3]<0 + case 79: // ls[0]=0 && ls[1]>0 && ls[2]>0 && ls[3]>0 + { + const Vector3d x0p13 = nodeParamCoords[i0] + nodeParamCoords[i3]-nodeParamCoords[i1]; + const Vector3d x0p12 = nodeParamCoords[i0] + nodeParamCoords[i2]-nodeParamCoords[i1]; + return (permute_case_id==1) ? + (std::make_shared(nodeParamCoords[i0],x0p13,x0p12)) : + (std::make_shared(nodeParamCoords[i0],x0p12,x0p13)); + } + break; + + case 4: // ls[0]=0 && ls[1]=0 && ls[2]<0 && ls[3]<0 + case 76: // ls[0]=0 && ls[1]=0 && ls[2]>0 && ls[3]>0 + { + const Vector3d x0p23 = nodeParamCoords[i0] + nodeParamCoords[i3]-nodeParamCoords[i2]; + return (permute_case_id==4) ? + (std::make_shared(nodeParamCoords[i0],nodeParamCoords[i1],x0p23)) : + (std::make_shared(nodeParamCoords[i1],nodeParamCoords[i0],x0p23)); + } + break; + + case 2: // ls[0]>0 && ls[1]<0 && ls[2]<0 && ls[3]<0 + case 78: // ls[0]<0 && ls[1]>0 && ls[2]>0 && ls[3]>0 + { + ThrowAssert((nodeSigns[i0] > 0 && nodeSigns[i1] < 0 && nodeSigns[i2] < 0 && nodeSigns[i3] < 0) || (nodeSigns[i0] < 0 && nodeSigns[i1] > 0 && nodeSigns[i2] > 0 && nodeSigns[i3] > 0)); + + return (permute_case_id==2) ? + (std::make_shared(nodeParamCoords[i4],nodeParamCoords[i7],nodeParamCoords[i6])) : + (std::make_shared(nodeParamCoords[i4],nodeParamCoords[i6],nodeParamCoords[i7])); + } + break; + + case 5: // ls[0]>0 && ls[1]=0 && ls[2]<0 && ls[3]<0 + case 75: // ls[0]<0 && ls[1]=0 && ls[2]>0 && ls[3]>0 + { + return (permute_case_id==5) ? + (std::make_shared(nodeParamCoords[i1],nodeParamCoords[i7],nodeParamCoords[i6])) : + (std::make_shared(nodeParamCoords[i1],nodeParamCoords[i6],nodeParamCoords[i7])); + } + break; + + case 8: // ls[0]>0 && ls[1]>0 && ls[2]<0 && ls[3]<0 + { + const bool face_diag = intersectingPlanesDiagonalPicker({{i0,i1,i2,i3}}); + return (face_diag) ? + (std::make_shared(nodeParamCoords[i8],nodeParamCoords[i7],nodeParamCoords[i6],nodeParamCoords[i5])) : + (std::make_shared(nodeParamCoords[i7],nodeParamCoords[i6],nodeParamCoords[i5],nodeParamCoords[i8])); + } + break; + + case 13: // ls[0]=0 && ls[1]=0 && ls[2]=0 && ls[3]<0 + case 67: // ls[0]=0 && ls[1]=0 && ls[2]=0 && ls[3]>0 + { + return (permute_case_id==13) ? + (std::make_shared(nodeParamCoords[i0],nodeParamCoords[i2],nodeParamCoords[i1])) : + (std::make_shared(nodeParamCoords[i0],nodeParamCoords[i1],nodeParamCoords[i2])); + } + break; + + case 14: // ls[0]>0 && ls[1]=0 && ls[2]=0 && ls[3]<0 + { + ThrowAssert(nodeSigns[i0] > 0 && nodeSigns[i1] == 0 && nodeSigns[i2] == 0 && nodeSigns[i3] < 0); + return std::make_shared(nodeParamCoords[i2],nodeParamCoords[i1],nodeParamCoords[i7]); + } + break; + + default: + { + krinolog << "Case id " << case_id << " " << permute_case_id << stk::diag::dendl; + ThrowRuntimeError("Subelement decomposition error."); + } + } + + ThrowRuntimeError("Subelement decomposition error."); +} + +static std::shared_ptr +build_cutting_surface(const MasterElement & masterElem, + const std::vector & cutEdges, + const std::function &)> & intersectingPlanesDiagonalPicker) +{ + const stk::topology baseTopology = masterElem.get_topology().base(); + const std::vector< std::array > elemEdgeNodeOrdinals = get_element_edge_node_ordinals(baseTopology); + + const auto nodeParamCoords = get_parametric_node_coords_on_element_nodes_and_cut_edges(masterElem, cutEdges, elemEdgeNodeOrdinals); + const auto nodeSigns = get_node_signs_on_element_nodes(baseTopology, cutEdges, elemEdgeNodeOrdinals); + + if (baseTopology == stk::topology::TRIANGLE_3_2D || baseTopology == stk::topology::TRIANGLE_3) + { + ThrowRequire(cutEdges.size() == 2); + return build_triangle_cutting_surface(nodeSigns, nodeParamCoords); + } + + ThrowRequireMsg(baseTopology == stk::topology::TETRAHEDRON_4, "Unsupported base topology: " << baseTopology.name()); + ThrowRequire(cutEdges.size() == 3 || cutEdges.size() == 4); + return build_tetrahedral_cutting_surface(nodeSigns, nodeParamCoords, intersectingPlanesDiagonalPicker); +} + +bool node_captures_intersection_point_domains(const std::vector & nodeDomains, const std::vector & intersectionPointDomains) +{ + return first_sorted_vector_of_domains_contains_all_domains_in_second_vector(nodeDomains, intersectionPointDomains); +} + +template +bool any_node_captures_intersection_point_domains(const NODEDOMAINS & nodesSnappedDomains, const std::vector & intersectionPointDomains) +{ + if (nodesSnappedDomains.empty()) + return false; + for (auto && nodeSnappedDomains : nodesSnappedDomains) + if (node_captures_intersection_point_domains(*nodeSnappedDomains, intersectionPointDomains)) + return true; + return false; +} + +bool intersection_is_already_captured(const std::vector*> & elementNodesSnappedDomains, const std::vector & nodes, const std::vector & intersectionPointDomains) +{ + if (elementNodesSnappedDomains.empty()) + return false; + for (auto && node : nodes) + if (node_captures_intersection_point_domains(*elementNodesSnappedDomains[node], intersectionPointDomains)) + return true; + return false; +} + +LS_Per_Interface_Cutter::LS_Per_Interface_Cutter(const MasterElement & masterElem, + const std::vector & parentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker) + : Element_Cutter(masterElem) +{ + for(auto && interface : get_interfaces_present(parentEdges)) + { + const std::vector cutEdges = build_cut_edges(interface, parentEdges, areParentEdgesOrientedSameAsElementEdges); + ThrowRequire(!cutEdges.empty()); + + cutting_surfaces[interface] = build_cutting_surface(masterElem, cutEdges, intersectingPlanesDiagonalPicker); + } +} + +bool +LS_Per_Interface_Cutter::have_crossing(const InterfaceID interface, const Segment3d & edge) const +{ + const auto iter = cutting_surfaces.find(interface); + ThrowRequire(iter != cutting_surfaces.end()); + const Cutting_Surface & interface_surface = *(iter->second); + return interface_surface.sign_at_position(edge.GetNode(0)) == -interface_surface.sign_at_position(edge.GetNode(1)); +} + +int LS_Per_Interface_Cutter::get_ls_per_interface_phase_at_location(const Vector3d & pCoords) const +{ + ThrowRequireMsg(false, "Improper usage of LS_Per_Interface_Cutter."); + return -1; +} + +void LS_Per_Interface_Cutter::add_interfaces_with_uncaptured_intersection_within_element(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains, + std::set & interfacesWithUncapturedCrossings) const +{ + // For LS_Per_Interface_Cutter, internal intersections only happen if there are edge intersections so nothing to do here. +} + +std::vector +LS_Per_Interface_Cutter::build_cut_edges(const InterfaceID & interface, + const std::vector & parentEdges, + const std::vector & parentEdgeIsOrientedSameAsElementEdge) +{ + std::vector cutEdges; + + for(unsigned i=0; i < parentEdges.size(); ++i) + { + const CDFEM_Parent_Edge * parent_edge = parentEdges[i]; + if(parent_edge) + { + const int crossing_sign = parent_edge->get_crossing_sign(interface); + const double crossing_pos = parent_edge->get_crossing_position(interface); + + if( crossing_pos >= 0. ) + { + const double pos = parentEdgeIsOrientedSameAsElementEdge[i] ? crossing_pos : (1 -crossing_pos); + const int sign = parentEdgeIsOrientedSameAsElementEdge[i] ? crossing_sign : (-crossing_sign); + cutEdges.push_back(Edge_Crossing(i,pos,sign)); + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "Crossing of edge " << i << " at position " << pos << "\n"; + } + } + } + return cutEdges; +} + +std::vector +One_LS_Per_Phase_Cutter::build_cut_edges(const InterfaceID & interface, + const std::vector & parentEdges, + const std::vector & parentEdgeIsOrientedSameAsElementEdge) +{ + std::vector cutEdges; + + for(unsigned i=0; i < parentEdges.size(); ++i) + { + const CDFEM_Parent_Edge * parent_edge = parentEdges[i]; + if(parent_edge) + { + double crossing_pos; + int crossing_sign; + bool is_fake; + std::tie(crossing_pos, crossing_sign, is_fake) = parent_edge->get_crossing_position_and_sign(interface); + + if( crossing_pos >= 0. ) + { + const double pos = parentEdgeIsOrientedSameAsElementEdge[i] ? crossing_pos : (1 -crossing_pos); + const int sign = parentEdgeIsOrientedSameAsElementEdge[i] ? crossing_sign : (-crossing_sign); + cutEdges.push_back(Edge_Crossing(i,pos,sign)); + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "Crossing of edge " << i << " for interface " << interface << " at position " << pos << " fake = " << std::boolalpha << is_fake << "\n"; + } + } + } + return cutEdges; +} + +std::shared_ptr One_LS_Per_Phase_Cutter::attempt_to_build_cutting_surface(const MasterElement & masterElem, + const std::vector & parentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker, + const InterfaceID& interface) +{ + std::shared_ptr cuttingSurface; + try + { + const std::vector cutEdges = build_cut_edges(interface, parentEdges, areParentEdgesOrientedSameAsElementEdges); + ThrowRequire(!cutEdges.empty()); + cuttingSurface = build_cutting_surface(masterElem, cutEdges, intersectingPlanesDiagonalPicker); + } + catch (const std::exception & err) + { + std::stringstream err_msg; + err_msg << "Error while cutting element with interface " << interface << std::endl; + err_msg << err.what() << std::endl; + throw std::runtime_error(err_msg.str()); + } + return cuttingSurface; +} + +static std::set determine_optimal_phases_at_location(const Vector3d & location, + const InterfaceToSurface & allSurfaces) +{ + // optimal phases are phases those that are on the "right" side of at least one surface, + // and never on the "wrong" side of an interface + std::set toPhases; + std::set fromPhases; + + for (auto && surface : allSurfaces) + { + const int sign = surface.second->sign_at_position(location); + const int toPhase = (sign < 0) ? surface.first.first_ls() : surface.first.second_ls(); + const int fromPhase = (sign < 0) ? surface.first.second_ls() : surface.first.first_ls(); + toPhases.insert(toPhase); + fromPhases.insert(fromPhase); + } + + for (int phase : fromPhases) + toPhases.erase(phase); + + return toPhases; +} + +bool intersection_point_is_real(const Vector3d & intersectionPoint, + const InterfaceToSurface & allSurfaces, + const std::vector & sortedDomains) +{ + for (auto && surface : allSurfaces) + { + if (!surface.second->on_surface(intersectionPoint, Element_Cutter::theSnapToNodeTol)) + { + const int sign = surface.second->sign_at_position(intersectionPoint); + const int toPhase = (sign < 0) ? surface.first.first_ls() : surface.first.second_ls(); + const int fromPhase = (sign < 0) ? surface.first.second_ls() : surface.first.first_ls(); + const bool toPhaseIsInDomains = std::binary_search(sortedDomains.begin(), sortedDomains.end(), toPhase); + const bool fromPhaseIsInDomains = std::binary_search(sortedDomains.begin(), sortedDomains.end(), fromPhase); + if (!toPhaseIsInDomains && fromPhaseIsInDomains) + return false; + } + } + return true; +} + +static void add_triple_point_interfaces(const bool isOneLSPerPhase, const std::vector & triplePointDomains, std::set & interfaces) +{ + ThrowRequire(triplePointDomains.size() == 3); + if (isOneLSPerPhase) + { + interfaces.insert(InterfaceID(triplePointDomains[0],triplePointDomains[1])); + interfaces.insert(InterfaceID(triplePointDomains[1],triplePointDomains[2])); + interfaces.insert(InterfaceID(triplePointDomains[0],triplePointDomains[2])); + } + else + { + interfaces.insert(InterfaceID(triplePointDomains[0],triplePointDomains[0])); + interfaces.insert(InterfaceID(triplePointDomains[1],triplePointDomains[1])); + interfaces.insert(InterfaceID(triplePointDomains[2],triplePointDomains[2])); + } +} + +static void add_interfaces_that_have_uncaptured_intersection_within_sides_of_tet(const std::vector & elemNodesCoords, + const std::vector*> & nodesSnappedDomains, + const InterfaceToSurface & allSurfaces, + std::set & interfacesWithUncapturedCrossings) +{ + Vector3d intersectionPoint; + std::vector sortedDomains; + constexpr bool oneLSPerPhase(true); + + stk::topology topology = stk::topology::TETRAHEDRON_4; + std::vector faceNodes(3); + + for (auto && surface1 : allSurfaces) + { + for (auto && surface2 : allSurfaces) + { + if (surface1.first < surface2.first && + (interfacesWithUncapturedCrossings.count(surface1.first) == 0 || interfacesWithUncapturedCrossings.count(surface2.first))) + { + fill_sorted_domains(oneLSPerPhase, surface1.first, surface2.first, sortedDomains); + + if (sorted_domains_form_triple_point(oneLSPerPhase, sortedDomains)) + { + for (int iFace=0; iFace<4; ++iFace) + { + topology.face_node_ordinals(iFace, faceNodes); + const std::array faceNodeCoords{elemNodesCoords[faceNodes[0]], elemNodesCoords[faceNodes[1]], elemNodesCoords[faceNodes[2]]}; + const Plane3d facePlane{elemNodesCoords[faceNodes[0]], elemNodesCoords[faceNodes[1]], elemNodesCoords[faceNodes[2]]}; + if (!intersection_is_already_captured(nodesSnappedDomains, faceNodes, sortedDomains) && + find_intersection_of_three_planes(facePlane, surface1.second->get_plane(), surface2.second->get_plane(), intersectionPoint) && + intersection_point_is_real(intersectionPoint, allSurfaces, sortedDomains)) + { + const Vector3d faceCoords = triangle_parametric_coordinates_of_projected_point(faceNodeCoords, intersectionPoint); + if (within_tri_bounds(faceCoords)) + add_triple_point_interfaces(oneLSPerPhase, sortedDomains, interfacesWithUncapturedCrossings); + } + } + } + } + } + } +} + +static void add_interfaces_that_have_uncaptured_intersection_within_sides_of_tet(const std::vector*> & nodesSnappedDomains, + const InterfaceToSurface & allSurfaces, + std::set & interfacesWithUncapturedCrossings) +{ + Vector3d intersectionPoint; + std::vector sortedDomains; + constexpr bool oneLSPerPhase(true); + + stk::topology topology = stk::topology::TETRAHEDRON_4; + std::vector faceNodes(3); + + for (auto && surface1 : allSurfaces) + { + for (auto && surface2 : allSurfaces) + { + if (surface1.first < surface2.first && + (interfacesWithUncapturedCrossings.count(surface1.first) == 0 || interfacesWithUncapturedCrossings.count(surface2.first))) + { + fill_sorted_domains(oneLSPerPhase, surface1.first, surface2.first, sortedDomains); + if (sorted_domains_form_triple_point(oneLSPerPhase, sortedDomains)) + { + for (int iFace=0; iFace<4; ++iFace) + { + topology.face_node_ordinals(iFace, faceNodes); + if (!intersection_is_already_captured(nodesSnappedDomains, faceNodes, sortedDomains) && + find_intersection_of_two_planes_and_side_of_tet(iFace, surface1.second->get_plane(), surface2.second->get_plane(), intersectionPoint) && + intersection_point_is_real(intersectionPoint, allSurfaces, sortedDomains)) + add_triple_point_interfaces(oneLSPerPhase, sortedDomains, interfacesWithUncapturedCrossings); + } + } + } + } + } +} + +static void add_interfaces_that_have_uncaptured_intersection_within_tri(const std::vector & elemNodesCoords, + const std::vector*> & nodesSnappedDomains, + const InterfaceToSurface & allSurfaces, + std::set & interfacesWithUncapturedCrossings) +{ + Vector3d intersectionPoint; + std::vector sortedDomains; + constexpr bool oneLSPerPhase(true); + const std::vector triNodes = {0,1,2}; + + for (auto && surface1 : allSurfaces) + { + for (auto && surface2 : allSurfaces) + { + if (surface1.first < surface2.first && + (interfacesWithUncapturedCrossings.count(surface1.first) == 0 || interfacesWithUncapturedCrossings.count(surface2.first))) + { + fill_sorted_domains(oneLSPerPhase, surface1.first, surface2.first, sortedDomains); + if (sorted_domains_form_triple_point(oneLSPerPhase, sortedDomains) && + !intersection_is_already_captured(nodesSnappedDomains, triNodes, sortedDomains) && + find_intersection_of_two_2D_planes_within_tri(surface1.second->get_plane(), surface2.second->get_plane(), intersectionPoint) && // ASSUME THAT PROVIDED TRIANGLE CONTAINED WITHIN PARENT TRIANGLE + intersection_point_is_real(intersectionPoint, allSurfaces, sortedDomains)) + { + const std::array triNodeCoords{elemNodesCoords[0], elemNodesCoords[1], elemNodesCoords[2]}; + const Vector3d triCoords = triangle_parametric_coordinates_of_projected_point(triNodeCoords, intersectionPoint); + if (within_tri_bounds(triCoords)) + add_triple_point_interfaces(oneLSPerPhase, sortedDomains, interfacesWithUncapturedCrossings); + } + } + } + } +} + +static void add_interfaces_that_have_uncaptured_intersection_within_tri(const std::vector*> & nodesSnappedDomains, + const InterfaceToSurface& allSurfaces, + std::set & interfacesWithUncapturedCrossings) +{ + Vector3d intersectionPoint; + std::vector sortedDomains; + constexpr bool oneLSPerPhase(true); + const std::vector triNodes = {0,1,2}; + + for (auto && surface1 : allSurfaces) + { + for (auto && surface2 : allSurfaces) + { + if (surface1.first < surface2.first && + (interfacesWithUncapturedCrossings.count(surface1.first) == 0 || interfacesWithUncapturedCrossings.count(surface2.first))) + { + fill_sorted_domains(oneLSPerPhase, surface1.first, surface2.first, sortedDomains); + if (sorted_domains_form_triple_point(oneLSPerPhase, sortedDomains) && + !intersection_is_already_captured(nodesSnappedDomains, triNodes, sortedDomains) && + find_intersection_of_two_2D_planes_within_tri(surface1.second->get_plane(), surface2.second->get_plane(), intersectionPoint) && + intersection_point_is_real(intersectionPoint, allSurfaces, sortedDomains)) + add_triple_point_interfaces(oneLSPerPhase, sortedDomains, interfacesWithUncapturedCrossings); + } + } + } +} + +static void add_interfaces_that_have_uncaptured_intersection_within_element(const stk::topology & baseTopology, + const std::vector*> & nodesSnappedDomains, + const InterfaceToSurface & allSurfaces, + std::set & interfacesWithUncapturedCrossings) +{ + if (baseTopology == stk::topology::TETRAHEDRON_4) + add_interfaces_that_have_uncaptured_intersection_within_sides_of_tet(nodesSnappedDomains, allSurfaces, interfacesWithUncapturedCrossings); + else if (baseTopology == stk::topology::TRIANGLE_3 || baseTopology == stk::topology::TRIANGLE_3_2D) + add_interfaces_that_have_uncaptured_intersection_within_tri(nodesSnappedDomains, allSurfaces, interfacesWithUncapturedCrossings); + else + ThrowRequireMsg(false, "Unsupported topology " << baseTopology); +} + +static void add_interfaces_that_have_uncaptured_intersection_within_element(const stk::topology & baseTopology, + const std::vector & elemNodesCoords, + const std::vector*> & nodesSnappedDomains, + const InterfaceToSurface & allSurfaces, + std::set & interfacesWithUncapturedCrossings) +{ + if (baseTopology == stk::topology::TETRAHEDRON_4) + add_interfaces_that_have_uncaptured_intersection_within_sides_of_tet(elemNodesCoords, nodesSnappedDomains, allSurfaces, interfacesWithUncapturedCrossings); + else if (baseTopology == stk::topology::TRIANGLE_3 || baseTopology == stk::topology::TRIANGLE_3_2D) + add_interfaces_that_have_uncaptured_intersection_within_tri(elemNodesCoords, nodesSnappedDomains, allSurfaces, interfacesWithUncapturedCrossings); + else + ThrowRequireMsg(false, "Unsupported topology " << baseTopology); +} + +void One_LS_Per_Phase_Cutter::add_interfaces_with_uncaptured_intersection_within_element(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains, + std::set & interfacesWithUncapturedCrossings) const +{ + // Does not consider uncaptured edge crossings, these should be considered already + add_interfaces_that_have_uncaptured_intersection_within_element(myTopology.base(), elemNodesCoords, elemNodesSnappedDomains, all_cutting_surfaces, interfacesWithUncapturedCrossings); +} + +bool +One_LS_Per_Phase_Cutter::have_crossing(const InterfaceID interface, const Segment3d & edge) const +{ + const auto iter = cutting_surfaces.find(interface); + ThrowRequire(iter != cutting_surfaces.end()); + const Cutting_Surface & interface_surface = *(iter->second); + if (interface_surface.sign_at_position(edge.GetNode(0)) == -interface_surface.sign_at_position(edge.GetNode(1))) + { + const double loc = interface_crossing_position(interface, edge); + const Vector3d intersectionPoint = (1.-loc)*edge.GetNode(0) + loc*edge.GetNode(1); + return krino::intersection_point_is_real(intersectionPoint, all_cutting_surfaces, {interface.first_ls(), interface.second_ls()}); + } + return false; +} + +int One_LS_Per_Phase_Cutter::get_ls_per_interface_phase_at_location(const Vector3d & pCoords) const +{ + const std::set optimalPhases = determine_optimal_phases_at_location(pCoords, all_cutting_surfaces); + ThrowRequireMsg(optimalPhases.size()==1, "Unexpected phase configuration with " << optimalPhases.size() << " optimal phases when evaluated phase at " << pCoords << "\n" << visualize()); + return *optimalPhases.begin(); +} + +bool One_LS_Per_Phase_Cutter::intersection_point_is_real(const Vector3d & intersectionPoint, + const std::vector & sortedDomains) const +{ + return krino::intersection_point_is_real(intersectionPoint, all_cutting_surfaces, sortedDomains); +} + +One_LS_Per_Phase_Cutter::One_LS_Per_Phase_Cutter(const MasterElement & masterElem, + const std::vector & parentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker) + : Element_Cutter(masterElem) +{ + std::set edgePhases = get_phases_present_on_edges_and_interior(parentEdges); + for (int phase1 : edgePhases) + { + for (int phase2 : edgePhases) + { + if (phase2 > phase1) + { + const InterfaceID interface(phase1, phase2); + all_cutting_surfaces[interface] = attempt_to_build_cutting_surface(masterElem, parentEdges, areParentEdgesOrientedSameAsElementEdges, intersectingPlanesDiagonalPicker, interface); + } + } + } + + std::set interfacesWithRealCrossings; + for (auto && interface : get_interfaces_present(parentEdges)) + interfacesWithRealCrossings.insert(interface); + + const std::vector*> emptyNodesSnappedDomains; + add_interfaces_that_have_uncaptured_intersection_within_element(myTopology.base(), emptyNodesSnappedDomains, all_cutting_surfaces, interfacesWithRealCrossings); + + for (auto && interface : interfacesWithRealCrossings) + cutting_surfaces[interface] = all_cutting_surfaces.at(interface); +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_Element_Cutter.hpp b/packages/krino/krino/krino_lib/Akri_Element_Cutter.hpp new file mode 100644 index 000000000000..0d5a0c2e907f --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Element_Cutter.hpp @@ -0,0 +1,132 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_ELEMENT_CUTTER_H_ +#define KRINO_INCLUDE_AKRI_ELEMENT_CUTTER_H_ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +class Cutting_Surface; +class CDFEM_Parent_Edge; +class ElementIntersection; + +typedef std::function &)> ElementIntersectionPointFilter; +typedef std::map> InterfaceToSurface; + +class Element_Cutter +{ +public: + Element_Cutter(const MasterElement & masterElement) : myTopology(masterElement.get_topology()) {} + virtual ~Element_Cutter() {} + double interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const; + int sign_at_position(const InterfaceID interface, const Vector3d & p_coords) const; + virtual bool have_crossing(const InterfaceID interface, const Segment3d & edge) const = 0; + virtual int get_ls_per_interface_phase_at_location(const Vector3d & pCoords) const = 0; + virtual void add_interfaces_with_uncaptured_intersection_within_element(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains, + std::set & interfacesWithUncapturedCrossings) const = 0; + + int get_num_cutting_surfaces() const {return cutting_surfaces.size(); } + bool have_cutting_surface(const InterfaceID interface) const; + void fill_interfaces_with_cutting_surface(std::vector & interfaces) const; + Cutting_Surface * get_cutting_surface(const InterfaceID interface) const; + + virtual bool is_one_ls_per_phase() const = 0; + void fill_interior_intersections(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const; + void fill_interior_intersections(std::vector & intersections) const; + void fill_tetrahedron_face_interior_intersections(const std::array & faceNodes, const InterfaceID & interface1, const InterfaceID & interface2, const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const; + static std::string visualize_cutting_surfaces(const stk::topology & topology, const InterfaceToSurface & cuttingSurfaces); + virtual std::string visualize() const { return visualize_cutting_surfaces(myTopology, cutting_surfaces); } + + int get_starting_phase_for_cutting_surfaces() const; + + struct Edge_Crossing { + Edge_Crossing(unsigned e, double p, int s) : edge(e), pos(p), sign(s) {} + unsigned edge; + double pos; + int sign; + }; + + static constexpr double theSnapToNodeTol{1.e-12}; + +protected: + stk::topology myTopology; + InterfaceToSurface cutting_surfaces; + +private: + virtual bool intersection_point_is_real(const Vector3d & intersection, const std::vector & sortedDomains) const = 0; + void fill_triangle_interior_intersection_points(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const; + void fill_tetrahedron_interior_intersection_points(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const; +}; + +class LS_Per_Interface_Cutter : public Element_Cutter +{ +public: + LS_Per_Interface_Cutter(const MasterElement & masterElem, + const std::vector & parentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker); + virtual bool is_one_ls_per_phase() const override { return false; } + virtual bool have_crossing(const InterfaceID interface, const Segment3d & edge) const override; + virtual int get_ls_per_interface_phase_at_location(const Vector3d & pCoords) const override; + virtual void add_interfaces_with_uncaptured_intersection_within_element(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains, + std::set & interfacesWithUncapturedCrossings) const override; +private: + virtual bool intersection_point_is_real(const Vector3d & intersection, const std::vector & sortedDomains) const override { return true; } + static std::vector build_cut_edges(const InterfaceID & interface, + const std::vector & parentEdges, + const std::vector & parentEdgeIsOrientedSameAsElementEdge); +}; + +class One_LS_Per_Phase_Cutter : public Element_Cutter +{ +public: + One_LS_Per_Phase_Cutter(const MasterElement & masterElem, + const std::vector & parentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker); + virtual bool is_one_ls_per_phase() const override { return true; } + virtual bool have_crossing(const InterfaceID interface, const Segment3d & edge) const override; + virtual int get_ls_per_interface_phase_at_location(const Vector3d & pCoords) const override; + virtual void add_interfaces_with_uncaptured_intersection_within_element(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains, + std::set & interfacesWithUncapturedCrossings) const override; + virtual std::string visualize() const override { return visualize_cutting_surfaces(myTopology, all_cutting_surfaces); } + +private: + virtual bool intersection_point_is_real(const Vector3d & intersection, const std::vector & sortedDomains) const override; + static std::vector build_cut_edges(const InterfaceID & interface, + const std::vector & parentEdges, + const std::vector & parentEdgeIsOrientedSameAsElementEdge); + static std::shared_ptr attempt_to_build_cutting_surface(const MasterElement & masterElem, + const std::vector & parentEdges, + const std::vector & areParentEdgesOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker, + const InterfaceID& interface); + + InterfaceToSurface all_cutting_surfaces; +}; + +std::unique_ptr create_element_cutter(const bool oneLSPerPhase, + const MasterElement & masterElem, + const std::vector & elementParentEdges, + const std::vector & areParentEdgesAreOrientedSameAsElementEdges, + const std::function &)> & intersectingPlanesDiagonalPicker); + +} + +#endif /* KRINO_INCLUDE_AKRI_ELEMENT_CUTTER_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_Element_Intersections.cpp b/packages/krino/krino/krino_lib/Akri_Element_Intersections.cpp new file mode 100644 index 000000000000..04d59b4e3da8 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Element_Intersections.cpp @@ -0,0 +1,28 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include + + +namespace krino { + +std::ostream & operator<<(std::ostream & os, const ElementIntersection & elementIntersection) +{ + os << elementIntersection.parametricCoords << ", interfaces={ "; + for (int domain : elementIntersection.sortedDomains) + os << domain << " "; + os << "}"; + return os; +} + +} + diff --git a/packages/krino/krino/krino_lib/Akri_Element_Intersections.hpp b/packages/krino/krino/krino_lib/Akri_Element_Intersections.hpp new file mode 100644 index 000000000000..2931938ffc81 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Element_Intersections.hpp @@ -0,0 +1,34 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_ELEMENT_INTERSECTIONS_H_ +#define KRINO_INCLUDE_AKRI_ELEMENT_INTERSECTIONS_H_ + +#include +#include +#include + +namespace krino { + +class Element_Cutter; + +struct ElementIntersection +{ + ElementIntersection(const Vector3d & coords, const std::vector & domains) + : parametricCoords(coords), + sortedDomains(domains) {} + + Vector3d parametricCoords; + std::vector sortedDomains; +}; + +std::ostream & operator<<(std::ostream & os, const ElementIntersection& elementIntersection); + +} + +#endif /* KRINO_INCLUDE_AKRI_ELEMENT_INTERSECTIONS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_EntityIdPool.cpp b/packages/krino/krino/krino_lib/Akri_EntityIdPool.cpp new file mode 100644 index 000000000000..6fcebc0157f7 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_EntityIdPool.cpp @@ -0,0 +1,92 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +namespace krino{ + +EntityIdPool::EntityIdPool(stk::mesh::MetaData & meta_data) + : my_meta_data(meta_data), + my_entity_id_pool(meta_data.entity_rank_count()) +{ +} + +stk::mesh::EntityId +EntityIdPool::get_EntityId(stk::mesh::EntityRank rank) +{ + ThrowAssert(static_cast(rank) < my_entity_id_pool.size()); + ThrowRequireMsg(!my_entity_id_pool[rank].empty(), "EntityIdPool is empty for " << rank << "."); + + stk::mesh::EntityId entity_id = my_entity_id_pool[rank].back(); + my_entity_id_pool[rank].pop_back(); + return entity_id; +} + +void +EntityIdPool::reserve(stk::mesh::EntityRank rank, size_t count, bool assert_32bit_ids, bool make_64bit_ids) +{ + my_meta_data.mesh_bulk_data().generate_new_ids( rank, count, my_entity_id_pool[rank] ); + ThrowAssert(!make_64bit_ids || !assert_32bit_ids); + if (make_64bit_ids) + { + push_ids_to_64_bit(my_entity_id_pool[rank]); + } + if (assert_32bit_ids && (rank == stk::topology::NODE_RANK || rank == stk::topology::ELEMENT_RANK)) // Only worry about node and elements since they are output + { + check_ids_are_32_bit(rank, my_entity_id_pool[rank]); + } +} + +void +EntityIdPool::generate_new_ids(stk::mesh::BulkData & mesh, stk::mesh::EntityRank rank, size_t count, std::vector & ids, bool assert_32bit_ids, bool make_64bit_ids) +{ + mesh.generate_new_ids( rank, count, ids ); + ThrowAssert(!make_64bit_ids || !assert_32bit_ids); + if (make_64bit_ids) + { + push_ids_to_64_bit(ids); + } + if (assert_32bit_ids && (rank == stk::topology::NODE_RANK || rank == stk::topology::ELEMENT_RANK)) // Only worry about node and elements since they are output + { + check_ids_are_32_bit(rank, ids); + } +} + +void +EntityIdPool::push_ids_to_64_bit(std::vector & ids) +{ + const uint64_t max_32_bit_id = std::numeric_limits::max(); + const bool ids_are_32_bit = !ids.empty() && ids[0] <= max_32_bit_id; + if (ids_are_32_bit) + { + for (auto && id : ids) + { + ThrowRequireMsg(id <= max_32_bit_id, "Mixture of ids above and below 32 bit limit not allowed in push_ids_to_64_bit."); + id += max_32_bit_id; + } + } +} + +void +EntityIdPool::check_ids_are_32_bit(stk::mesh::EntityRank rank, std::vector & ids) +{ + static const uint64_t max_allowed_id = std::numeric_limits::max(); + bool have_bad_id = false; + for (auto && id : ids) + { + if (id > max_allowed_id) + { + have_bad_id = true; + break; + } + } + ThrowRequireMsg(!have_bad_id, "Exhausted valid ids for rank " << rank << "!"); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_EntityIdPool.hpp b/packages/krino/krino/krino_lib/Akri_EntityIdPool.hpp new file mode 100644 index 000000000000..4ecea3088548 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_EntityIdPool.hpp @@ -0,0 +1,39 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_EntityIdPool_h +#define Akri_EntityIdPool_h + +#include +#include + +namespace stk { namespace mesh { class BulkData; } } + +namespace krino { + +class EntityIdPool { +public: + EntityIdPool(stk::mesh::MetaData & meta_data); + ~EntityIdPool() {} + + static void generate_new_ids(stk::mesh::BulkData & mesh, stk::mesh::EntityRank rank, size_t count, std::vector & ids, bool assert_32bit_ids, bool make_64bit_ids); + + void reserve(stk::mesh::EntityRank rank, size_t count, bool assert_32bit_ids, bool make_64bit_ids); + stk::mesh::EntityId get_EntityId(stk::mesh::EntityRank rank); +private: + static void push_ids_to_64_bit(std::vector & ids); + static void check_ids_are_32_bit(stk::mesh::EntityRank rank, std::vector & ids); + +protected: + stk::mesh::MetaData & my_meta_data; + std::vector< std::vector > my_entity_id_pool; +}; + +} // namespace krino + +#endif // Akri_EntityIdPool_h diff --git a/packages/krino/krino/krino_lib/Akri_Facet.cpp b/packages/krino/krino/krino_lib/Akri_Facet.cpp new file mode 100644 index 000000000000..9744abd1fca0 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Facet.cpp @@ -0,0 +1,197 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +namespace krino{ + +// +//-------------------------------------------------------------------------------- + +std::unique_ptr +Facet::unpack_from_buffer( stk::CommBuffer & b ) +{ /* %TRACE% */ /* %TRACE% */ + std::unique_ptr facet; + + int dim = 0; + b.unpack(dim); + + switch(dim) + { + case 2: facet = Facet2d::unpack_from_buffer(b); break; + case 3: facet = Facet3d::unpack_from_buffer(b); break; + default: ThrowRuntimeError("Unrecognized facet dimension."); + } + + ThrowRequire(facet); + return facet; +} + +//-------------------------------------------------------------------------------- + +void +Facet2d::pack_into_buffer(stk::CommBuffer & b) const +{ + const int dim = 2; + b.pack(dim); + for (int n = 0; n < 2; ++n ) + { + const Vector3d & pt = facet_vertex(n); + b.pack(pt[0]); + b.pack(pt[1]); + } +} + +std::unique_ptr +Facet2d::unpack_from_buffer( stk::CommBuffer & b ) +{ /* %TRACE% */ /* %TRACE% */ + std::unique_ptr facet; + + double vx, vy; + b.unpack(vx); + b.unpack(vy); + Vector3d x0( vx, vy, 0.0 ); + b.unpack(vx); + b.unpack(vy); + Vector3d x1( vx, vy, 0.0 ); + + facet = std::make_unique( x0, x1 ); + return facet; +} + +void +Facet3d::pack_into_buffer(stk::CommBuffer & b) const +{ + const int dim = 3; + b.pack(dim); + for (int n = 0; n < 3; ++n ) + { + const Vector3d & pt = facet_vertex(n); + b.pack(pt[0]); + b.pack(pt[1]); + b.pack(pt[2]); + } +} + +std::unique_ptr +Facet3d::unpack_from_buffer( stk::CommBuffer & b ) +{ /* %TRACE% */ /* %TRACE% */ + std::unique_ptr facet; + + double vx, vy, vz; + b.unpack(vx); + b.unpack(vy); + b.unpack(vz); + Vector3d x0( vx, vy, vz ); + b.unpack(vx); + b.unpack(vy); + b.unpack(vz); + Vector3d x1( vx, vy, vz ); + b.unpack(vx); + b.unpack(vy); + b.unpack(vz); + Vector3d x2( vx, vy, vz ); + + facet = std::make_unique( x0, x1, x2 ); + return facet; +} + +std::ostream & Facet3d::put( std::ostream & os ) const +{ /* %TRACE% */ /* %TRACE% */ + // facet info + os << ": facet description: " << std::endl + << " facet point 0 = (" + << facet_vertex(0)[0] << "," + << facet_vertex(0)[1] << "," + << facet_vertex(0)[2] << ")" << std::endl + << " facet point 1 = (" + << facet_vertex(1)[0] << "," + << facet_vertex(1)[1] << "," + << facet_vertex(1)[2] << ")" << std::endl + << " facet point 2 = (" + << facet_vertex(2)[0] << "," + << facet_vertex(2)[1] << "," + << facet_vertex(2)[2] << ")" << std::endl + << " facet area = " << facet_area() << std::endl; + + return os ; +} + +void Facet3d::apply_transformation(const Transformation & transformation) +{ + Vector3d pt0 = facet_vertex(0); + Vector3d pt1 = facet_vertex(1); + Vector3d pt2 = facet_vertex(2); + + transformation.apply(pt0); + transformation.apply(pt1); + transformation.apply(pt2); + + my_facet_tri = Triangle3d( pt0, pt1 , pt2 ); + + my_bounding_box.clear(); + my_bounding_box.accommodate(pt0); + my_bounding_box.accommodate(pt1); + my_bounding_box.accommodate(pt2); +} + +void Facet2d::apply_transformation(const Transformation & transformation) +{ + Vector3d pt0 = facet_vertex(0); + Vector3d pt1 = facet_vertex(1); + + transformation.apply(pt0); + transformation.apply(pt1); + + // create facet segment + my_facet_segment = Segment3d( pt0, pt1 ); + + my_bounding_box.clear(); + my_bounding_box.accommodate(pt0); + my_bounding_box.accommodate(pt1); +} + +std::ostream & Facet2d::put( std::ostream & os ) const +{ /* %TRACE% */ /* %TRACE% */ + // facet info + os << ": facet description: " << std::endl + << " facet point 0 = (" + << facet_vertex(0)[0] << "," + << facet_vertex(0)[1] << ")" << std::endl + << " facet point 1 = (" + << facet_vertex(1)[0] << "," + << facet_vertex(1)[1] << ")" << std::endl + << " facet area = " << facet_area() << std::endl; + + return os ; +} + +Facet3d::Facet3d( const Vector3d & pt0, + const Vector3d & pt1, + const Vector3d & pt2 ) + : Facet(), + my_facet_tri( pt0, pt1, pt2 ) +{ /* %TRACE% */ /* %TRACE% */ + my_bounding_box.accommodate(pt0); + my_bounding_box.accommodate(pt1); + my_bounding_box.accommodate(pt2); +} + +Facet2d::Facet2d( const Vector3d & pt0, + const Vector3d & pt1 ) + : Facet(), + my_facet_segment(pt0, pt1) +{ /* %TRACE% */ /* %TRACE% */ + my_bounding_box.accommodate(pt0); + my_bounding_box.accommodate(pt1); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Facet.hpp b/packages/krino/krino/krino_lib/Akri_Facet.hpp new file mode 100644 index 000000000000..b9da02cdb513 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Facet.hpp @@ -0,0 +1,158 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Facet_h +#define Akri_Facet_h + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace stk { class CommBuffer; } + +namespace krino { + +class Facet; +class Transformation; + +typedef std::vector< Facet * > FacetVec; +typedef std::vector< std::unique_ptr > FacetOwningVec; + +class Facet { +public: + Facet() {} + virtual ~Facet() {} + + // for debugging memory usage + virtual size_t storage_size() const = 0; + + virtual std::ostream & put( std::ostream& os ) const = 0; + friend std::ostream & operator << ( std::ostream &os , const Facet &f ) { return f.put(os); } + + // methods for off-processor communication + static std::unique_ptr unpack_from_buffer( stk::CommBuffer & b ); // static method that builds facet from data in buffer for off-processor communication + virtual void pack_into_buffer(stk::CommBuffer & b) const = 0; // pack into buffer for off-processor communication + + virtual Vector3d facet_vertex(const int i) const = 0; + virtual double facet_area() const = 0; + virtual Vector3d facet_normal() const = 0; + virtual bool degenerate() const = 0; + + virtual double point_distance_squared( const Vector3d & x ) const = 0; + virtual double point_distance_squared( const Vector3d & x, Vector2d & parametric_coords ) const = 0; + virtual int point_distance_sign( const Vector3d & x ) const = 0; + virtual Vector3d real_coordinates(const Vector2d & parametric_coords) const = 0; + virtual Vector3d weights(const Vector2d & parametric_coords) const = 0; + + virtual void apply_transformation(const Transformation & transformation) = 0; + + const BoundingBox & bounding_box() const { ThrowAssert (my_bounding_box.valid()); return my_bounding_box; } + static const BoundingBox & get_bounding_box(const Facet * facet) { return facet->bounding_box(); } + +protected: + BoundingBox my_bounding_box; +}; + +class Facet3d : public Facet { +public: + Facet3d( const Vector3d & x0, + const Vector3d & x1, + const Vector3d & x2 ); + Facet3d() = delete; + virtual ~Facet3d() {} + + virtual size_t storage_size() const { return sizeof(Facet3d); } + virtual std::ostream & put( std::ostream& os ) const; + + static std::unique_ptr unpack_from_buffer( stk::CommBuffer & b ); // static method that builds facet from data in buffer for off-processor communication + virtual void pack_into_buffer(stk::CommBuffer & b) const; + + virtual Vector3d facet_vertex(const int i) const { return my_facet_tri.GetNode(i); } + virtual double facet_area() const { return my_facet_tri.area(); } + virtual Vector3d facet_normal() const { return my_facet_tri.normal(); } + virtual bool degenerate() const { return my_facet_tri.normal_dir().zero_length(); } + virtual double point_distance_squared( const Vector3d & x ) const + { return my_facet_tri.DistanceSquared( x ); } + virtual double point_distance_squared( const Vector3d & x, Vector2d & parametric_coords ) const + { return my_facet_tri.DistanceSquared( x, parametric_coords ); } + virtual int point_distance_sign( const Vector3d & x ) const + { return (Dot(my_facet_tri.normal_dir(),x-facet_vertex(0)) < 0.0) ? -1 : 1; } + virtual Vector3d real_coordinates(const Vector2d & parametric_coords) const + { return my_facet_tri.ParametricToRealCoords(parametric_coords); } + virtual Vector3d weights(const Vector2d & parametric_coords) const + { return Vector3d(1.0-parametric_coords[0]-parametric_coords[1],parametric_coords[0],parametric_coords[1]); } + virtual void apply_transformation(const Transformation & transformation); +private: + Triangle3d my_facet_tri; +}; + +class Facet2d : public Facet { +public: + Facet2d( const Vector3d & x0, + const Vector3d & x1 ); + Facet2d() = delete; + virtual ~Facet2d() {} + + virtual size_t storage_size() const { return sizeof(Facet2d); } + virtual std::ostream & put( std::ostream& os ) const; + + static std::unique_ptr unpack_from_buffer( stk::CommBuffer & b ); // static method that builds facet from data in buffer for off-processor communication + virtual void pack_into_buffer(stk::CommBuffer & b) const; + + virtual Vector3d facet_vertex(const int i) const { return my_facet_segment.GetNode(i); } + virtual double facet_area() const { return my_facet_segment.Length(); } + virtual Vector3d facet_normal() const + { return (crossZ(facet_vertex(1)-facet_vertex(0))).unit_vector(); } + virtual bool degenerate() const { return (0.0 == my_facet_segment.Length()); } + virtual double point_distance_squared( const Vector3d & x ) const + { return my_facet_segment.DistanceSquared(x); } + virtual double point_distance_squared( const Vector3d & x, Vector2d & parametric_coords ) const + { return my_facet_segment.DistanceSquared(x, parametric_coords[0]); } + virtual int point_distance_sign( const Vector3d & x ) const + { return (Dot(crossZ(facet_vertex(1)-facet_vertex(0)), x-facet_vertex(0)) < 0.0) ? -1 : 1; } + virtual Vector3d real_coordinates(const Vector2d & parametric_coords) const + { return (1.0-parametric_coords[0])*my_facet_segment.GetNode(0) + parametric_coords[0]*my_facet_segment.GetNode(1); } + virtual Vector3d weights(const Vector2d & parametric_coords) const + { return Vector3d(1.0-parametric_coords[0],parametric_coords[0],0.0); } + virtual void apply_transformation(const Transformation & transformation); +private: + Segment3d my_facet_segment; +}; + +class FacetDistanceQuery { +public: + FacetDistanceQuery() : my_facet(nullptr), my_sqr_distance(0.0) {} + FacetDistanceQuery(const Facet & in_facet, const Vector3d & x) : my_facet(&in_facet), my_query_pt(x) + { + my_sqr_distance = my_facet->point_distance_squared(x, my_parametric_coords); + } + + bool empty() const { return my_facet == nullptr; } + const Facet & facet() const { return *my_facet; } + double distance_squared() const { return my_sqr_distance; } + Vector3d closest_point() const { return my_facet->real_coordinates(my_parametric_coords); } + double signed_distance() const { return std::sqrt(my_sqr_distance)*my_facet->point_distance_sign(my_query_pt); } + Vector3d closest_point_weights() const { return my_facet->weights(my_parametric_coords); } + +private: + const Facet* my_facet; + Vector3d my_query_pt; + double my_sqr_distance; + Vector2d my_parametric_coords; +}; + +} // namespace krino + +#endif // Akri_Facet_h diff --git a/packages/krino/krino/krino_lib/Akri_Faceted_Surface.cpp b/packages/krino/krino/krino_lib/Akri_Faceted_Surface.cpp new file mode 100644 index 000000000000..1c8eca3243f1 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Faceted_Surface.cpp @@ -0,0 +1,494 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include + +namespace krino{ + +Faceted_Surface::Faceted_Surface(const std::string & sn) + : SurfaceThatTakesAdvantageOfNarrowBandAndThereforeMightHaveWrongSign(), + my_name(sn) {} + +void +Faceted_Surface::parallel_distribute_facets(const size_t batch_size, const std::vector & proc_bboxes) +{ + const int num_procs = stk::EnvData::parallel_size(); + if ( num_procs == 1 ) return; + + const int me = stk::EnvData::parallel_rank(); + ThrowRequire(me != 0 || batch_size <= my_local_facets.size()); + + stk::CommSparse comm_sparse(stk::EnvData::parallel_comm()); + + std::vector dest_procs; + std::vector proc_facet_counts; + size_t start = 0; + if (me == 0) + { + dest_procs.resize(batch_size, 0); + proc_facet_counts.resize(num_procs, 0); + start = my_local_facets.size() - batch_size; + for ( size_t index = 0; index < batch_size; ++index ) + { + const Facet * facet = my_local_facets[start+index].get(); + for ( int dest_proc = 1; dest_proc < num_procs; ++dest_proc ) + { + if ( proc_bboxes[dest_proc].intersects(facet->bounding_box()) ) + { + dest_procs[index] = dest_proc; + ++(proc_facet_counts[dest_proc]); + break; + } + } + if (dest_procs[index] == 0) + { + ++(proc_facet_counts[0]); + } + } + } + + // Communication involves two steps, the first one sizes the messages, the second one actually packs and sends the messages + for ( int comm_step = 0; comm_step < 2; ++comm_step) + { + if (me == 0) + { + for ( int dest_proc = 0; dest_proc < num_procs; ++dest_proc ) + { + if (comm_step == 0 && krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "P" << me << ":" + << " Packaging " << proc_facet_counts[dest_proc] + << " facets for proc#" << dest_proc << stk::diag::dendl; + } + + stk::CommBuffer & b = comm_sparse.send_buffer(dest_proc); + b.pack(proc_facet_counts[dest_proc]); + + for ( size_t index = 0; index < batch_size; ++index ) + { + if (dest_procs[index] == dest_proc) + { + const Facet * facet = my_local_facets[start+index].get(); + facet->pack_into_buffer(b); + } + } + } + } + if (comm_step == 0) + { //allocation step + comm_sparse.allocate_buffers(); + } + else + { //communication step + comm_sparse.communicate(); + } + } + + if (me == 0) + { + // delete facets that have been sent away (even if they are headed back to 0) + my_local_facets.erase(my_local_facets.begin()+start, my_local_facets.end()); + } + + // unload, creating locally owned copy of facet + const int recv_proc = 0; + stk::CommBuffer & b = comm_sparse.recv_buffer(recv_proc); + + size_t proc_num_facets_recvd = 0; + b.unpack(proc_num_facets_recvd); + + if(krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "P" << stk::EnvData::parallel_rank() << ":" << " Receiving " << proc_num_facets_recvd << " facets from proc#" << recv_proc << stk::diag::dendl; + + for ( size_t n = 0; n < proc_num_facets_recvd; ++n ) + { + std::unique_ptr facet = Facet::unpack_from_buffer( b ); + my_local_facets.emplace_back( std::move(facet) ); + } + ThrowAssert( 0 == b.remaining() ); +} + +void +Faceted_Surface::pack_into_buffer(stk::CommBuffer & b) const +{ + ThrowRuntimeError("pack_into_buffer should not be called for a Faceted_Surface."); +} + +void +Faceted_Surface::prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) +{ /* %TRACE[ON]% */ Trace trace__("krino::Faceted_Surface::prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length)"); /* %TRACE% */ + + build_local_facets(point_bbox); + + if (my_transformation != nullptr) + { + for ( auto&& facet : my_local_facets ) + { + facet->apply_transformation(*my_transformation); + } + } + + my_bounding_box.clear(); + for (auto && facet : my_local_facets) + { + my_bounding_box.accommodate(facet->bounding_box()); + } + + my_all_facets.clear(); + for ( auto&& facet : my_local_facets ) + { + my_all_facets.push_back(facet.get()); + } + + // Get all remote facets that might be closest to this processors query points + gather_nonlocal_facets(point_bbox, truncation_length); + + if(krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "P" << stk::EnvData::parallel_rank() << ":" << " Building facet tree for " << my_all_facets.size() << " facets." << stk::diag::dendl; + + my_facet_tree = std::make_unique>( my_all_facets, Facet::get_bounding_box ); + + if ( krinolog.shouldPrint(LOG_DEBUG) ) + krinolog << "P" << stk::EnvData::parallel_rank() << ": After building search tree, storage size is " << storage_size()/(1024.*1024.) << " Mb." << stk::diag::dendl; +} + +void +Faceted_Surface::gather_nonlocal_facets(const BoundingBox & point_bbox, const double truncation_length) +{ /* %TRACE[ON]% */ Trace trace__("krino::Faceted_Surface::get_nonlocal_descendants(const BoundingBox & point_bbox, const double truncation_length)"); /* %TRACE% */ + + // If truncation length is specified, get all of the facets that are within + // our padded processor's bounding box. + // To do this, see if any local facets lie in the padded nodal bounding box + // of another proc. if so, send them a copy of those facets. + + my_nonlocal_facets.clear(); + + const int num_procs = stk::EnvData::parallel_size(); + if ( num_procs == 1) return; + + // We estimate the padding based on the uppoer bound of the distance from this processor's nodal bounding box + // to the other processors bounding box of facets. This calculation requires that that local + // descendants are already gathered. + + std::vector facet_bboxes; + BoundingBox::gather_bboxes( my_bounding_box, facet_bboxes ); + + double bbox_padding = std::numeric_limits::max(); + for ( int p = 0; p < num_procs; ++p ) + { + const BoundingBox & proc_facet_bbox = facet_bboxes[p]; + if (proc_facet_bbox.valid()) + { + const double upperBnd = std::sqrt(proc_facet_bbox.SqrDistUpperBnd(point_bbox)); + bbox_padding = std::min(bbox_padding,upperBnd); + } + } + if (std::numeric_limits::max() == bbox_padding) + { + bbox_padding = truncation_length; // only should happen for no facets anywhere + } + if (truncation_length > 0.0) + { + bbox_padding = std::min(bbox_padding, truncation_length); + } + + // gather the bounding box sizes for all procs + BoundingBox local_bbox = point_bbox; + std::vector bboxes; + local_bbox.pad(bbox_padding); + BoundingBox::gather_bboxes( local_bbox, bboxes ); + + // determine which facets will be sent to each processor and formulate message sizes + const int me = stk::EnvData::parallel_rank(); + size_t me_intersecting_facet_counts = 0; + std::vector intersecting_facets; + intersecting_facets.reserve(my_all_facets.size()); + + // Perform communication in stages. In the nth stage, processor, p, + // sends facets to processor p+n and receives from p-n. + // In the 0th stage, each processor count how many of its own facets are + // are within that processor's nodal bounding box. + for ( int comm_partner = 0; comm_partner < num_procs; ++comm_partner ) + { + stk::CommSparse comm_sparse(stk::EnvData::parallel_comm()); + + const int dest_proc = (me+comm_partner) % num_procs; + + // Communication involves two steps, the first one sizes the messages, the second one actually packs and sends the messages + for ( int comm_step = 0; comm_step < 2; ++comm_step) + { + if (comm_step == 0) + { + const BoundingBox & proc_bbox = bboxes[dest_proc]; + intersecting_facets.clear(); + if (proc_bbox.intersects(my_bounding_box)) + { + for ( auto&& facet : my_all_facets ) + { + if ( proc_bbox.intersects(facet->bounding_box()) ) + { + intersecting_facets.push_back(facet); + } + } + } + if (dest_proc == me) me_intersecting_facet_counts = intersecting_facets.size(); + + if(krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "P" << me << ":" << " Packaging " << intersecting_facets.size() << " facets for proc#" << dest_proc << stk::diag::dendl; + } + + stk::CommBuffer & b = comm_sparse.send_buffer(dest_proc); + if (dest_proc != me) // Don't talk to yourself, it's embarrassing + { + const size_t intersecting_facets_size = intersecting_facets.size(); + b.pack(intersecting_facets_size); + for ( auto&& facet : intersecting_facets ) + { + facet->pack_into_buffer(b); + } + } + + if (comm_step == 0) + { //allocation step + comm_sparse.allocate_buffers(); + } + else + { //communication step + comm_sparse.communicate(); + } + } + + // unload, creating local copies of nonlocal facets + + const int recv_proc = (me+num_procs-comm_partner) % num_procs; + + if (recv_proc != me) + { + stk::CommBuffer & b = comm_sparse.recv_buffer(recv_proc); + + size_t proc_num_facets_recvd = 0; + b.unpack(proc_num_facets_recvd); + + if(krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "P" << stk::EnvData::parallel_rank() << ":" << " Receiving " << proc_num_facets_recvd << " facets from proc#" << recv_proc << stk::diag::dendl; + + for ( size_t n = 0; n < proc_num_facets_recvd; ++n ) + { + std::unique_ptr facet = Facet::unpack_from_buffer( b ); + my_nonlocal_facets.emplace_back( std::move(facet) ); + } + ThrowAssert( 0 == b.remaining() ); + } + } + + // only retain intersecting local descendants + if (my_all_facets.size() != me_intersecting_facet_counts) + { + FacetVec local_descendants; + local_descendants.reserve(me_intersecting_facet_counts); + for ( auto&& facet : my_all_facets ) + { + if ( local_bbox.intersects(facet->bounding_box()) ) + { + local_descendants.push_back( facet ); + } + } + my_all_facets.swap(local_descendants); + } + + // copy nonlocal facets into my_descendants + my_all_facets.reserve(my_all_facets.size()+ my_nonlocal_facets.size()); + for (auto && nonlocal_descendant : my_nonlocal_facets) { my_all_facets.push_back(nonlocal_descendant.get()); } +} + +double +Faceted_Surface::point_distance(const Vector3d &x, const double narrow_band_size, const double far_field_value, const bool compute_signed_distance) const +{ /* %TRACE% */ /* %TRACE% */ + ThrowAssertMsg(my_facet_tree, "ERROR: Empty facet tree"); + + // get all facets we need to check + FacetVec facets; + my_facet_tree->find_closest_entities( x, facets, narrow_band_size ); + + if (facets.empty()) + { + ThrowRequire( 0.0 != narrow_band_size || my_facet_tree->empty() ); + return far_field_value; + } + + double dist = 0.0; + if (compute_signed_distance) + { + dist = compute_point_to_facets_distance_by_average_normal(x, facets); + if (0.0 != narrow_band_size && std::abs(dist) > narrow_band_size) + { + dist = far_field_value; + } + } + else + { + double min_sqr_dist = std::numeric_limits::max(); + for ( auto&& facet : facets ) + { + const double sqr_dist = facet->point_distance_squared(x); + if (sqr_dist < min_sqr_dist) + { + min_sqr_dist = sqr_dist; + } + } + if (0.0 != narrow_band_size && min_sqr_dist > narrow_band_size*narrow_band_size) + { + dist = far_field_value; + } + else + { + dist = std::sqrt(min_sqr_dist); + } + } + + return dist; +} + +double +Faceted_Surface::compute_point_to_facets_distance_by_average_normal(const Vector3d &x, const FacetVec & facets) const +{ /* %TRACE% */ /* %TRACE% */ + + // If the closest_point weights are all larger than this value, then the closest point + // is considered to be on the face of the closest facet rather than on the edges of the facet, and + // therefore only the closest facet is considered in the distance calculation. Otherwise, all of the + // facets are considered to compute an average normal in order to compute the distance. + const double edge_tol = 1.e-6; + + std::vector facet_queries; + facet_queries.reserve(facets.size()); + for ( auto&& facet : facets ) + { + if (facet->degenerate()) continue; // Skip zero-sized facets + facet_queries.emplace_back(*facet, x); + } + + ThrowRequireMsg(!facet_queries.empty(), "All facets are degenerate in compute_point_to_facets_distance_by_average_normal."); + + unsigned nearest = 0; + for ( unsigned index=0; index(facets[nearest]) ? 3 : 2; + + bool closest_point_on_edge = false; + for (int d=0; d 0) + { + return std::sqrt(min_sqr_dist); + } + else + { + return -std::sqrt(min_sqr_dist); + } + } +} + +Vector3d +Faceted_Surface::compute_pseudo_normal(const unsigned dim, const std::vector & facet_queries, const unsigned nearest) const +{ + const double tol = 1.e-6; + Vector3d pseudo_normal = Vector3d::ZERO; + Vector3d average_normal = Vector3d::ZERO; + + const Vector3d nearest_closest_point = facet_queries[nearest].closest_point(); + const double nearest_size2 = facet_queries[nearest].facet().bounding_box().SqrSize(); + unsigned close_count = 0; + for ( auto&& query : facet_queries ) + { + const Vector3d closest_point = query.closest_point(); + const double dist2_from_nearest = (closest_point-nearest_closest_point).length_squared(); + if (dist2_from_nearest < tol*tol*nearest_size2) + { + ++close_count; + const Facet & facet = query.facet(); + + average_normal += facet.facet_normal(); + + if (3 == dim) + { + const Vector3d closest_pt_wts = query.closest_point_weights(); + const int closest_node = (closest_pt_wts[0] > closest_pt_wts[1]) ? ((closest_pt_wts[0] > closest_pt_wts[2]) ? 0 : 2) : ((closest_pt_wts[1] > closest_pt_wts[2]) ? 1 : 2); + + const int n0 = closest_node; + const int n1 = (n0<2) ? (n0+1) : 0; + const int n2 = (n1<2) ? (n1+1) : 0; + const Vector3d edge0 = facet.facet_vertex(n1) - facet.facet_vertex(n0); + const Vector3d edge1 = facet.facet_vertex(n2) - facet.facet_vertex(n0); + const double facet_angle = std::acos(Dot(edge0, edge1)/(edge0.length()*edge1.length())); + + pseudo_normal += facet.facet_normal()*facet_angle; + } + } + } + ThrowRequireMsg(close_count>0,"Issue with tolerance in compute_pseudo_normal. No facet found within tolerance of closest point."); + + return (3 == dim && close_count > 2) ? pseudo_normal : average_normal; +} + +size_t +Faceted_Surface::storage_size() const +{ + size_t store_size = sizeof(Faceted_Surface); + if (my_facet_tree) + { + store_size += my_facet_tree->storage_size(); + } + + store_size += utility::storage_size(my_local_facets); + store_size += utility::storage_size(my_nonlocal_facets); + store_size += utility::storage_size(my_all_facets); + + return store_size; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Faceted_Surface.hpp b/packages/krino/krino/krino_lib/Akri_Faceted_Surface.hpp new file mode 100644 index 000000000000..9ff26e6f0142 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Faceted_Surface.hpp @@ -0,0 +1,69 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Faceted_Surface_h +#define Akri_Faceted_Surface_h + +#include +#include +#include + +namespace krino { + +class Faceted_Surface : public SurfaceThatTakesAdvantageOfNarrowBandAndThereforeMightHaveWrongSign { + +public: + Faceted_Surface(const std::string & sn); + + virtual Surface_Type type() const override { return FACETED_SURFACE; } + virtual size_t storage_size() const override; + virtual void pack_into_buffer(stk::CommBuffer & b) const; // pack into buffer for off-processor communication + virtual void prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) override; + virtual double truncated_point_signed_distance(const Vector3d &x, const double narrow_band_size, const double far_field_value) const override + { + return point_distance(x, narrow_band_size, far_field_value, true); + } + + // query/modify facets + void add( std::unique_ptr facet ) { my_local_facets.emplace_back(std::move(facet)); } + void reserve(unsigned size_) { my_local_facets.reserve(size_); } + unsigned size() const { return my_local_facets.size(); } + unsigned nonlocal_size() const { return my_nonlocal_facets.size(); } + Facet * operator()( const unsigned index ) const { return my_local_facets[index].get(); } + void clear() { my_local_facets.clear(); } + void swap(Faceted_Surface & other) { my_local_facets.swap(other.my_local_facets); } + const FacetOwningVec & get_facets() const { return my_local_facets; } + void parallel_distribute_facets(const size_t batch_size, const std::vector & proc_bboxes); + +public: + void prepare_to_compute(const BoundingBox & point_bbox, const double truncation_length) + { ThrowAssert(nullptr == my_transformation); prepare_to_compute(0.0, point_bbox, truncation_length); } + double point_unsigned_distance(const Vector3d &x, const double narrow_band_size, const double far_field_value) const + { + return point_distance(x, narrow_band_size, far_field_value, false); + } + +private: + virtual void build_local_facets(const BoundingBox & proc_bbox) {} + double point_distance(const Vector3d &x, const double narrow_band_size, const double far_field_value, const bool compute_signed_distance) const; + double compute_point_to_facets_distance_by_average_normal(const Vector3d &x, const FacetVec & facets) const; + Vector3d compute_pseudo_normal(const unsigned dim, const std::vector & facet_queries, const unsigned nearest) const; + void gather_nonlocal_facets(const BoundingBox & local_bbox, const double truncation_length); + + std::string my_name; + FacetOwningVec my_local_facets; + + mutable std::unique_ptr> my_facet_tree; + mutable FacetOwningVec my_nonlocal_facets; + mutable FacetVec my_all_facets; + BoundingBox my_bounding_box; +}; + +} // namespace krino + +#endif // Akri_Faceted_Surface_h diff --git a/packages/krino/krino/krino_lib/Akri_Fast_Marching.cpp b/packages/krino/krino/krino_lib/Akri_Fast_Marching.cpp new file mode 100644 index 000000000000..2a09003eaf2c --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Fast_Marching.cpp @@ -0,0 +1,726 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace krino{ + +Fast_Marching::Fast_Marching(LevelSet & ls, const stk::mesh::Selector & selector, stk::diag::Timer & parent_timer) + : my_ls(ls), + my_selector(selector), + my_timer("Fast Marching", parent_timer), + my_tri_timer("Update Triangle", my_timer), + my_tet_timer("Update Tetrahedron", my_timer), + my_fm_node_less(ls.mesh()), + trial_nodes(my_fm_node_less) +{ + ParallelErrorMessage err(mesh().parallel()); + stk::mesh::Selector fieldSelector(my_ls.get_distance_field()); + const stk::mesh::BucketVector& buckets = selector.get_buckets(stk::topology::ELEMENT_RANK); + for (auto && bucket : buckets) + { + if (fieldSelector(*bucket) && + bucket->topology() != stk::topology::TRIANGLE_3_2D && + bucket->topology() != stk::topology::TETRAHEDRON_4) + { + err << "Topology " << bucket->topology().name() << " is not supported in Fast_Marching.\n"; + } + } + check_error(err, "Checking topology"); +} + +void +Fast_Marching::check_error(const ParallelErrorMessage& err, const std::string & context) const +{ + auto globalError = err.gather_message(); + if (globalError.first) + { + krinolog<< "Error in " << context << ":" << stk::diag::dendl; + krinolog << globalError.second << stk::diag::dendl; + } + ThrowRequireMsg(!globalError.first, "Error in " << context << "."); +} + +void Fast_Marching::redistance() +{ + stk::diag::TimeBlock timer__(my_timer); + const FieldRef& dRef = my_ls.get_distance_field(); + const FieldRef& olddRef = my_ls.get_old_distance_field(); + + // make sure field is parallel consistent to start out + { + std::vector parallel_fields(1, &dRef.field()); + stk::mesh::copy_owned_to_shared(mesh(), parallel_fields); + } + + ParallelErrorMessage err(mesh().parallel()); + + initialize(err); + + // neighbors of initial nodes are trial nodes + for ( auto && fm_node : fm_nodes ) + { + if (fm_node.status() == STATUS_INITIAL) + { + update_neighbors(fm_node, err); + } + } + + check_error(err, "Fast Marching Initialization"); + + stk::mesh::Selector globally_shared_selector = my_selector & mesh().mesh_meta_data().globally_shared_part(); + std::vector< stk::mesh::Entity> shared_nodes; + stk::mesh::get_selected_entities( globally_shared_selector, mesh().buckets( stk::topology::NODE_RANK ), shared_nodes ); + + bool done = false; + + const size_t local_num_nodes = fm_nodes.size(); + size_t max_num_nodes = 0; + stk::all_reduce_max(mesh().parallel(), &local_num_nodes, &max_num_nodes, 1); + + const unsigned max_outer_steps = max_num_nodes; // should be overkill + unsigned num_outer_steps = 0; + while (!done) + { + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "num_trial nodes = " << trial_nodes.size() << "\n"; + + if (num_outer_steps++ > max_outer_steps) + { + err << "Error: Outer loop of Fast_Marching::redistance did not converge!\n"; + break; + } + + const unsigned max_inner_steps = 10*max_outer_steps; // should be overkill + unsigned num_inner_steps = 0; + while(!trial_nodes.empty()) + { + auto begin = trial_nodes.begin(); + Fast_Marching_Node & fm_node = **begin; + trial_nodes.erase(begin); + fm_node.set_status(STATUS_ACCEPTED); + update_neighbors(fm_node, err); + if (num_inner_steps++ > max_inner_steps) + { + err << "Error: Inner loop of Fast_Marching::redistance did not converge! Number of trial nodes = " << trial_nodes.size() << "\n"; + } + if (err.have_local_error()) break; + } + + check_error(err, "Fast Marching Iteration"); + + unsigned num_locally_updated = 0; + if (mesh().parallel_size() > 1) + { + for ( auto && shared_node : shared_nodes ) + { + Fast_Marching_Node * fm_node = get_fm_node(shared_node); + if (nullptr != fm_node && fm_node->status() != STATUS_UNUSED) + { + double & node_dist = *field_data(dRef, fm_node->node()); + node_dist = fm_node->signed_dist()*fm_node->sign(); + } + } + stk::mesh::parallel_min(mesh(), {&dRef.field()}); + + for ( auto && shared_node : shared_nodes ) + { + Fast_Marching_Node * fm_node = get_fm_node(shared_node); + if (nullptr != fm_node && fm_node->status() != STATUS_UNUSED) + { + stk::mesh::Entity node = fm_node->node(); + const double min_node_unsigned_dist = *field_data(dRef, node); + const double fm_node_unsigned_dist = fm_node->signed_dist()*fm_node->sign(); + if(min_node_unsigned_dist < fm_node_unsigned_dist) + { + ThrowAssertMsg(fm_node->status() != STATUS_INITIAL || fm_node->status() != STATUS_TRIAL, "Unexpected node to have INITIAL or TRIAL status."); + fm_node->set_signed_dist(min_node_unsigned_dist*fm_node->sign()); + add_trial_node(*fm_node); + ++num_locally_updated; + } + } + } + } + + unsigned num_globally_updated = 0; + stk::all_reduce_sum(mesh().parallel(), &num_locally_updated, &num_globally_updated, 1); + + done = (num_globally_updated == 0); + } + + for ( auto && fm_node : fm_nodes ) + { + if (fm_node.status() == STATUS_TRIAL || fm_node.status() == STATUS_FAR) + { + err << "Node " << mesh().identifier(fm_node.node()) << " with status " << fm_node.status() << " with distance " << fm_node.signed_dist() << " did not get updated!\n"; + } + if (fm_node.status() != STATUS_UNUSED) + { + double & node_dist = *field_data(dRef, fm_node.node()); + node_dist = fm_node.signed_dist(); + } + } + + check_error(err, "Fast Marching Update"); + stk::mesh::field_copy(dRef, olddRef); +} + +Fast_Marching_Node * Fast_Marching::get_fm_node(stk::mesh::Entity node) +{ + if (mesh().is_valid(node) && mesh().local_id(node) < fm_nodes.size()) + { + return &fm_nodes[mesh().local_id(node)]; + } + else + { + return nullptr; + } +} + +void Fast_Marching::initialize(ParallelErrorMessage& err) +{ + stk::mesh::BulkData& stk_mesh = mesh(); + const FieldRef xRef = my_ls.get_coordinates_field(); + const FieldRef dRef = my_ls.get_distance_field(); + + const int dim = mesh().mesh_meta_data().spatial_dimension(); + + fm_nodes.clear(); + fm_nodes.resize(stk::mesh::count_selected_entities(stk_mesh.mesh_meta_data().universal_part(), stk_mesh.buckets(stk::topology::NODE_RANK))); + + std::vector field_nodes; + stk::mesh::Selector field_not_ghost = aux_meta().active_not_ghost_selector() & my_selector & stk::mesh::selectField(dRef); + stk::mesh::get_selected_entities( field_not_ghost, stk_mesh.buckets( stk::topology::NODE_RANK ), field_nodes ); + + for ( auto&& node : field_nodes ) + { + const double * curr_node_dist = field_data(dRef, node); + ThrowAssert(nullptr != curr_node_dist); + + Vector3d coords = Vector3d::ZERO; + const double * xptr = field_data(xRef, node); + ThrowAssert(nullptr != xptr); + for ( int d = 0; d < dim; d++ ) + { + coords[d] = xptr[d]; + } + + Fast_Marching_Node * fm_node = get_fm_node(node); + ThrowAssert(nullptr != fm_node); + *fm_node = Fast_Marching_Node(node,STATUS_FAR,LevelSet::sign(*curr_node_dist) * std::numeric_limits::max(),LevelSet::sign(*curr_node_dist),coords); + } + + // To start the nodes of elements that have interfaces will be redistanced. + // I have tried a few different methods for this distance calculation with varying success. + // We can: + // 1. Use only local facets to redistance the nodes of the element. This appears to not work too + // well because the closest facet to a node might be in an element that does not support the node. + // 2. Use local facets, but use the normal distance instead of the facet distance. This seems to work + // better than (1) usually. However, it seems prone to pathalogical behavior when small facets run + // parallel to an element side, producing inaccurate distance measures on this side. + // 3. Use the standard parallel redistance calculation used in "normal" redistancing. This is susceptible + // to seeing through walls if the walls are thinner than the distance of the nodes of the cut element to + // the surface. + // 4. Start the redistancing from the subelement facets, progressing through the subelements. + // Somewhat surprisingly, this does not work too well. I think this may be due to the obtuse angles + // in the subelement decomposition. The results are very similar to that produced by local redistancing (#1). + // 5. Rescale each cut element so that it has a unit gradient (or prescribed gradient) and set the nodal + // distance to the minimum (magnitude) from all of the supporting elements. This seems to work pretty + // well in the test cases and is completely local, and is not susceptible to seeing through walls. + + { + // Initialize using method #5 (element rescaling) + std::vector field_elems; + stk::mesh::get_selected_entities( field_not_ghost, stk_mesh.buckets( stk::topology::ELEMENT_RANK ), field_elems ); + for (auto&& elem : field_elems) + { + if (have_crossing(elem)) + { + const double speed = my_ls.get_time_of_arrival_speed(elem, err); + initialize_element(elem, speed); + } + } + + if (mesh().parallel_size() > 1) + { + stk::mesh::Selector globally_shared_selector = my_selector & mesh().mesh_meta_data().globally_shared_part(); + std::vector< stk::mesh::Entity> shared_nodes; + stk::mesh::get_selected_entities( globally_shared_selector, stk_mesh.buckets( stk::topology::NODE_RANK ), shared_nodes ); + + for ( auto && shared_node : shared_nodes ) + { + Fast_Marching_Node * fm_node = get_fm_node(shared_node); + if (nullptr != fm_node && fm_node->status() != STATUS_UNUSED) + { + double & node_dist = *field_data(dRef, fm_node->node()); + node_dist = fm_node->signed_dist()*fm_node->sign(); + } + } + stk::mesh::parallel_min(mesh(), {&dRef.field()}); + + for ( auto && shared_node : shared_nodes ) + { + Fast_Marching_Node * fm_node = get_fm_node(shared_node); + if (nullptr != fm_node && fm_node->status() != STATUS_UNUSED) + { + stk::mesh::Entity node = fm_node->node(); + const double min_node_unsigned_dist = *field_data(dRef, node); + const double fm_node_unsigned_dist = fm_node->signed_dist()*fm_node->sign(); + fm_node->set_signed_dist(min_node_unsigned_dist*fm_node->sign()); + if (min_node_unsigned_dist < fm_node_unsigned_dist) + { + fm_node->set_status(STATUS_INITIAL); + } + } + } + } + } +} + +bool +Fast_Marching::have_crossing(const stk::mesh::Entity & elem) const +{ + const FieldRef dRef = my_ls.get_distance_field(); + const unsigned npe = mesh().bucket(elem).topology().num_nodes(); + ThrowAssert(npe > 0); + + const stk::mesh::Entity * elem_nodes = mesh().begin(elem, stk::topology::NODE_RANK); + const double * dist0 = field_data(dRef, elem_nodes[0]); + ThrowAssert(nullptr != dist0); + for (unsigned n=1; n(dRef, elem_nodes[n]); + ThrowAssert(nullptr != dist); + if (LevelSet::sign_change(*dist0, *dist)) + { + return true; + } + } + return false; +} + +static std::function build_get_fm_node_coordinates(Fast_Marching * fm) +{ + return [fm](stk::mesh::Entity node) -> const Vector3d & + { + Fast_Marching_Node * fm_node = fm->get_fm_node(node); + ThrowAssert(fm_node); + return fm_node->coords(); + }; +} + +static double calculate_gradient_magnitude(const int npe, + const stk::mesh::Entity * elem_nodes, + const FieldRef dRef, + const std::function & get_coordinates) +{ + double mag_grad = 1.0; + + if (3 == npe) + { + const double d0 = *field_data(dRef, elem_nodes[0]); + const double d10 = *field_data(dRef, elem_nodes[1])-d0; + const double d20 = *field_data(dRef, elem_nodes[2])-d0; + + const Vector3d x0 = get_coordinates(elem_nodes[0]); + const Vector3d x10 = get_coordinates(elem_nodes[1]) - x0; + const Vector3d x20 = get_coordinates(elem_nodes[2]) - x0; + + const double detJ = (x10[0]*x20[1]-x20[0]*x10[1]); + const Vector3d grad = d10*Vector3d(x20[1],-x20[0],0.0) + d20*Vector3d(-x10[1],x10[0],0.0); + mag_grad = grad.length()/detJ; + } + else + { + ThrowAssert(4 == npe); + + const double d0 = *field_data(dRef, elem_nodes[0]); + const double d10 = *field_data(dRef, elem_nodes[1])-d0; + const double d20 = *field_data(dRef, elem_nodes[2])-d0; + const double d30 = *field_data(dRef, elem_nodes[3])-d0; + + const Vector3d x0 = get_coordinates(elem_nodes[0]); + const Vector3d x10 = get_coordinates(elem_nodes[1]) - x0; + const Vector3d x20 = get_coordinates(elem_nodes[2]) - x0; + const Vector3d x30 = get_coordinates(elem_nodes[3]) - x0; + + const Vector3d x10_x_x20 = Cross(x10,x20); + const Vector3d x20_x_x30 = Cross(x20,x30); + const Vector3d x30_x_x10 = Cross(x30,x10); + + const double detJ = Dot(x30,x10_x_x20); + const Vector3d grad = d10*x20_x_x30 + d20*x30_x_x10 + d30*x10_x_x20; + mag_grad = grad.length()/detJ; + } + + return mag_grad; +} + +void +Fast_Marching::initialize_element(const stk::mesh::Entity & elem, const double speed) +{ + // Still another way to initialize fast marching. + // Here we go to each cut element and find the current distance gradient. + // By comparing this to the desired gradient, we rescale each element. The nodal + // distance is set to the minimum (magnitude) for each of the rescaled elements that + // support the node. + const FieldRef dRef = my_ls.get_distance_field(); + const stk::mesh::Entity * elem_nodes = mesh().begin(elem, stk::topology::NODE_RANK); + const int npe = mesh().bucket(elem).topology().num_nodes(); + + auto get_coordinates = build_get_fm_node_coordinates(this); + + const double mag_grad = calculate_gradient_magnitude(npe, elem_nodes, dRef, get_coordinates); + + for (int inode=0; inodestatus() != STATUS_UNUSED); + const double elem_node_dist = *field_data(dRef, elem_nodes[inode]) / (mag_grad * speed); + const int sign = LevelSet::sign(fm_node->signed_dist()); + fm_node->set_signed_dist(sign * std::min(std::abs(fm_node->signed_dist()), std::abs(elem_node_dist))); + fm_node->set_status(STATUS_INITIAL); + fm_node->set_sign(sign); + } +} + +void +Fast_Marching::update_neighbors(Fast_Marching_Node & accepted_node, ParallelErrorMessage& err) +{ + const FieldRef dRef = my_ls.get_distance_field(); + const stk::mesh::Selector active_field_not_aura = my_selector & aux_meta().active_not_ghost_selector() & stk::mesh::selectField(dRef); + + stk::mesh::Entity node = accepted_node.node(); + + ThrowAssertMsg(STATUS_ACCEPTED == accepted_node.status() || STATUS_INITIAL == accepted_node.status(), "Expected ACCEPTED OR INITIAL status"); + + const int dim = mesh().mesh_meta_data().spatial_dimension(); + ThrowAssert(2 == dim || 3 == dim); + const int npe_dist = (2==dim) ? 3 : 4; + std::vector elem_nodes(npe_dist); + + const unsigned num_node_elems = mesh().num_elements(node); + const stk::mesh::Entity* node_elems = mesh().begin_elements(node); + for (unsigned node_elem_index=0; node_elem_indexstatus() || STATUS_ACCEPTED == fm_nbr->status() || STATUS_FAR == fm_nbr->status() || STATUS_TRIAL == fm_nbr->status()), "Unexpected node status."); + elem_nodes[i] = fm_nbr; + bool do_add_trial_node = fm_nbr->status() == STATUS_FAR; + if (fm_nbr->status() == STATUS_ACCEPTED) + { + const double accepted_node_unsigned_dist = accepted_node.signed_dist()*accepted_node.sign(); + const double nbr_unsigned_dist = fm_nbr->signed_dist()*fm_nbr->sign(); + if(nbr_unsigned_dist > accepted_node_unsigned_dist) + { + do_add_trial_node = true; + } + } + if (do_add_trial_node) + { + add_trial_node(*fm_nbr); + } + if (fm_nbr->status() == STATUS_TRIAL) + { + ++num_trial; + node_to_update = i; + } + } + + if (1 == num_trial) + { + update_node(elem_nodes, node_to_update, speed); + } + } +} + +void +Fast_Marching::add_trial_node(Fast_Marching_Node & trial_node) +{ + ThrowAssertMsg(trial_node.status() == STATUS_ACCEPTED || trial_node.status() == STATUS_FAR, "Expected ACCEPTED or FAR when adding trial node"); + trial_nodes.insert(&trial_node); + trial_node.set_status(STATUS_TRIAL); +} + +void +Fast_Marching::update_trial_node(Fast_Marching_Node & trial_node, const double dist) +{ + ThrowAssertMsg(trial_node.status() == STATUS_TRIAL, "Unexpected node status when updating trial node"); + + auto it = trial_nodes.find(&trial_node); + ThrowAssertMsg(it != trial_nodes.end(), "Can't find trial node"); + + trial_nodes.erase(it); + + trial_node.set_signed_dist(dist); + trial_nodes.insert(&trial_node); +} + +void +Fast_Marching::update_node(std::vector & elem_nodes, int node_to_update, const double speed) +{ + // update distance + double dist = std::numeric_limits::max(); + const int npe_dist = elem_nodes.size(); + + if (3 == npe_dist) + { + dist = update_triangle(elem_nodes, node_to_update, speed); + } + else if (4 == npe_dist) + { + dist = update_tetrahedron(elem_nodes, node_to_update, speed); + } + else + { + ThrowAssertMsg(false, "Unexpected number of nodes per element: " << npe_dist); + } + + Fast_Marching_Node & fm_node = *elem_nodes[node_to_update]; + if (dist*fm_node.sign() < fm_node.signed_dist()*fm_node.sign()) + { + update_trial_node(fm_node, dist); + } +} + +double +Fast_Marching::update_triangle(std::vector & elem_nodes, int node_to_update, const double speed, int side_to_update) +{ + stk::diag::TimeBlock timer__(my_tri_timer); + const int dim = mesh().mesh_meta_data().spatial_dimension(); + ThrowAssert(2 == dim || 3 == dim); + + int lnn[3]; + if (2 == dim) + { + ThrowAssert(-1 == side_to_update); + lnn[0] = (node_to_update + 1) % 3; + lnn[1] = (node_to_update + 2) % 3; + lnn[2] = node_to_update; + } + else // (3 == dim) + { + const stk::topology tet4_topology = stk::topology::TETRAHEDRON_4; + const unsigned * side_node_ordinals = get_side_node_ordinals(tet4_topology, side_to_update); + lnn[0] = side_node_ordinals[(node_to_update + 1) % 3]; + lnn[1] = side_node_ordinals[(node_to_update + 2) % 3]; + lnn[2] = side_node_ordinals[node_to_update]; + } + + const double dist_0 = elem_nodes[lnn[0]]->signed_dist(); + const double dist_1 = elem_nodes[lnn[1]]->signed_dist(); + double dist_2 = elem_nodes[lnn[2]]->signed_dist(); + + const int sign = elem_nodes[lnn[2]]->sign(); + if (sign*(dist_2-dist_0) < 0 || sign*(dist_2-dist_1) < 0) + { + return dist_2; + } + + const Vector3d & coords_0 = elem_nodes[lnn[0]]->coords(); + const Vector3d & coords_1 = elem_nodes[lnn[1]]->coords(); + const Vector3d & coords_2 = elem_nodes[lnn[2]]->coords(); + + const double sqr_speed = speed*speed; + const double d10 = dist_1 - dist_0; + const Vector3d x10 = coords_1 - coords_0; + const Vector3d x20 = coords_2 - coords_0; + const double h10 = x10.length(); + const double h20 = x20.length(); + + double detJ = 0; + if (2 == dim) + { + detJ = (x10[0]*x20[1]-x20[0]*x10[1]); + } + else // (3 == dim) + { + detJ = Cross(x10,x20).length(); + } + ThrowAssert(detJ > 0.0); + + const double a = h10*h10; + const double b = -2.0 * sign*d10 * Dot(x10,x20); + const double c = d10*d10 * h20*h20 - detJ*detJ/sqr_speed; + + bool elem_is_defining = false; + + const double det = b*b-4.0*a*c; + if (det > 0.0) + { + // solve quadratic equation, roots are q/a and c/q + const int sign_b = ( b < 0.0 ) ? -1 : 1; + const double q = -0.5*(b + sign_b*std::sqrt(det)); + + const double d20 = sign*std::max(c/q,q/a); + + const bool causal = (sign*d20 > 0.0 && sign*(d20-d10) > 0.0); + + if (causal) + { + const double loc = (Dot(x10,x20) - sqr_speed*d10*d20) / (h10*h10 - sqr_speed*d10*d10); + elem_is_defining = (loc > 0.0 && loc < 1.0); + + if (elem_is_defining) + { + dist_2 = sign * std::min(sign*dist_2,sign*(dist_0 + d20)); + } + } + } + + if (!elem_is_defining) + { + const double h21 = (coords_2 - coords_1).length(); + dist_2 = sign * std::min(sign*dist_2,std::min(sign*dist_0+h20/speed,sign*dist_1+h21/speed)); + // Enforce causality - This is to catch the corner case (literally) where the characteristic is marching along the edges of the element + dist_2 = sign * std::max(sign*dist_2,std::max(sign*dist_0,sign*dist_1)); + } + + ThrowAssert(sign*(dist_2-dist_0)>=0 && sign*(dist_2-dist_1)>=0); + + return dist_2; +} + +double +Fast_Marching::update_tetrahedron(std::vector & elem_nodes, int node_to_update, const double speed) +{ + stk::diag::TimeBlock timer__(my_tet_timer); + static const unsigned node_permute_0[] = { 1,3,2,0 }; + static const unsigned node_permute_1[] = { 0,2,3,1 }; + static const unsigned node_permute_2[] = { 0,3,1,2 }; + static const unsigned node_permute_3[] = { 0,1,2,3 }; + static const unsigned * node_permute_table[] = { node_permute_0, node_permute_1, node_permute_2, node_permute_3 }; + + int lnn[4]; + lnn[0] = node_permute_table[node_to_update][0]; + lnn[1] = node_permute_table[node_to_update][1]; + lnn[2] = node_permute_table[node_to_update][2]; + lnn[3] = node_permute_table[node_to_update][3]; + + const double dist_0 = elem_nodes[lnn[0]]->signed_dist(); + const double dist_1 = elem_nodes[lnn[1]]->signed_dist(); + const double dist_2 = elem_nodes[lnn[2]]->signed_dist(); + double dist_3 = elem_nodes[lnn[3]]->signed_dist(); + + const int sign = elem_nodes[lnn[3]]->sign(); + if (sign*(dist_3-dist_0) < 0 || sign*(dist_3-dist_1) < 0 || sign*(dist_3-dist_2) < 0) + { + return dist_3; + } + + const Vector3d & coords_0 = elem_nodes[lnn[0]]->coords(); + const Vector3d & coords_1 = elem_nodes[lnn[1]]->coords(); + const Vector3d & coords_2 = elem_nodes[lnn[2]]->coords(); + const Vector3d & coords_3 = elem_nodes[lnn[3]]->coords(); + + const double sqr_speed = speed*speed; + const double d10 = dist_1-dist_0; + const double d20 = dist_2-dist_0; + + const Vector3d x10 = coords_1 - coords_0; + const Vector3d x20 = coords_2 - coords_0; + const Vector3d x30 = coords_3 - coords_0; + + const Vector3d x10_x_x20 = Cross(x10,x20); + const Vector3d x20_x_x30 = Cross(x20,x30); + const Vector3d x30_x_x10 = Cross(x30,x10); + + const double detJ = Dot(x30,x10_x_x20); + ThrowAssert(detJ > 0); + + const Vector3d contrib12 = sign * (d10*x20_x_x30 + d20*x30_x_x10); + + const double a = x10_x_x20.length_squared(); + const double b = 2.0 * Dot(x10_x_x20,contrib12); + const double c = contrib12.length_squared() - detJ*detJ/sqr_speed; + + bool elem_is_defining = false; + + const double det = b*b-4.0*a*c; + if (det > 0.0) + { + // solve quadratic equation, roots are q/a and c/q + const int sign_b = ( b < 0.0 ) ? -1 : 1; + const double q = -0.5*(b + sign_b*std::sqrt(det)); + + const double d30 = sign*std::max(c/q,q/a); + + const bool causal = (sign*d30 > 0.0 && sign*(d30-d10) > 0.0 && sign*(d30-d20) > 0.0); + + if (causal) + { + // Solve 2x2 system for parametric coords of intersection of gradient and 0-1-2 + // A1 r + B1 s == C1 + // A2 r + B2 s == C2 + const double A1 = x10.length_squared() - sqr_speed*d10*d10; + const double B1 = Dot(x10,x20) - sqr_speed*d10*d20; + const double C1 = Dot(x10,x30) - sqr_speed*d10*d30; + const double A2 = B1; + const double B2 = x20.length_squared() - sqr_speed*d20*d20; + const double C2 = Dot(x20,x30) - sqr_speed*d20*d30; + const double denom = A2*B1 - A1*B2; + const double loc_r = (C2*B1 - C1*B2)/denom; + const double loc_s = (A2*C1 - A1*C2)/denom; + const double loc_t = 1.0 - loc_r - loc_s; + elem_is_defining = (loc_r > 0.0 && loc_s > 0.0 && loc_t > 0.0); + + if (elem_is_defining) + { + dist_3 = sign * std::min(sign*dist_3,sign*(dist_0 + d30)); + } + } + } + + if (!elem_is_defining) + { + const stk::topology tet4_topology = stk::topology::TETRAHEDRON_4; + for (int iside=0; iside<4; ++iside) + { + const unsigned * side_node_lnn = get_side_node_ordinals(tet4_topology, iside); + for (int inode=0; inode<3; ++inode) + { + if (node_to_update == (int)side_node_lnn[inode]) + { + dist_3 = sign * std::min(sign*dist_3, sign*update_triangle(elem_nodes,inode,speed,iside)); + } + } + } + // Enforce causality - This is to catch the corner case (literally) where the characteristic is marching along the edges of the element + // This is not completely covered by the 2d check because 1 face might still predict a value that is less than the neighbors. + dist_3 = sign * std::max(sign*dist_3,std::max(sign*dist_2,std::max(sign*dist_1,sign*dist_0))); + } + + ThrowAssert(sign*(dist_3-dist_0)>=0 && sign*(dist_3-dist_1)>=0 && sign*(dist_3-dist_2)>=0); + + return dist_3; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Fast_Marching.hpp b/packages/krino/krino/krino_lib/Akri_Fast_Marching.hpp new file mode 100644 index 000000000000..233bec1d38a7 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Fast_Marching.hpp @@ -0,0 +1,104 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Fast_Marching_h +#define Akri_Fast_Marching_h + +#include +#include +#include + +namespace krino { + +class SubElement; +class Mesh_Element; +class AuxMetaData; +class ParallelErrorMessage; + +enum Enum_Fast_Marching_Node_Status{STATUS_UNUSED=0, STATUS_INITIAL, STATUS_ACCEPTED, STATUS_TRIAL, STATUS_FAR}; + +class Fast_Marching_Node +{ +public: + Fast_Marching_Node() + : my_node(), my_status(STATUS_UNUSED), my_signed_dist(std::numeric_limits::max()), my_on_neg_side(false) {} + Fast_Marching_Node(stk::mesh::Entity in_node, Enum_Fast_Marching_Node_Status in_status, double in_dist, int in_sign, Vector3d in_coords) + : my_node(in_node), my_status(in_status), my_signed_dist(in_dist), my_on_neg_side(in_sign<0), my_coords(in_coords) {} + + int sign() const { return (my_on_neg_side ? (-1) : 1); } + void set_sign(const int in_sign) { my_on_neg_side = (in_sign<0); } + stk::mesh::Entity node() const { return my_node; } + Enum_Fast_Marching_Node_Status status() const { return my_status; } + const Vector3d & coords() const { return my_coords; } + double signed_dist() const { return my_signed_dist; } + void set_signed_dist(double dist) { my_signed_dist = dist; } + void set_status(Enum_Fast_Marching_Node_Status status) { my_status = status; } + +private: + stk::mesh::Entity my_node; + Enum_Fast_Marching_Node_Status my_status; + double my_signed_dist; + bool my_on_neg_side; + Vector3d my_coords; +}; + +class Fast_Marching_Node_Less +{ +public: + Fast_Marching_Node_Less(const stk::mesh::BulkData & mesh) : mMesh(mesh) {} + bool operator() (const Fast_Marching_Node *a, const Fast_Marching_Node *b) const + { + const double unsignedDistA = a->signed_dist()*a->sign(); + const double unsignedDistB = b->signed_dist()*b->sign(); + if (unsignedDistA < unsignedDistB) return true; + if (unsignedDistB < unsignedDistA) return false; + return mMesh.identifier(a->node()) < mMesh.identifier(b->node()); + } +private: + const stk::mesh::BulkData & mMesh; +}; + +class Fast_Marching { +public: + Fast_Marching(LevelSet & ls, const stk::mesh::Selector & selector, stk::diag::Timer & parent_timer); + + void redistance(); + void initialize(ParallelErrorMessage& err); + void update_neighbors(Fast_Marching_Node & accepted_node, ParallelErrorMessage & err); + void update_node(std::vector & elem_nodes, int node_to_update, const double speed); + + bool have_crossing(const stk::mesh::Entity & elem) const; + void initialize_subelement(const SubElement & subelem, const int side, const double speed); + void initialize_element(const stk::mesh::Entity & elem, const double speed); + double update_triangle(std::vector & elem_nodes, int node_to_update, const double speed, int side_to_update=-1); + double update_tetrahedron(std::vector & elem_nodes, int node_to_update, const double speed); + + void add_trial_node(Fast_Marching_Node & add_trial_node); + void update_trial_node(Fast_Marching_Node & add_trial_node, const double dist); + Fast_Marching_Node * get_fm_node(stk::mesh::Entity node); + + const AuxMetaData& aux_meta() const { return my_ls.aux_meta(); } + AuxMetaData& aux_meta() { return my_ls.aux_meta(); } + const stk::mesh::BulkData& mesh() const { return my_ls.mesh(); } + stk::mesh::BulkData& mesh() { return my_ls.mesh(); } +private: + LevelSet & my_ls; + stk::mesh::Selector my_selector; + stk::diag::Timer my_timer; + stk::diag::Timer my_tri_timer; + stk::diag::Timer my_tet_timer; + std::vector fm_nodes; + Fast_Marching_Node_Less my_fm_node_less; + std::set trial_nodes; //set sorted by distance, then by id to break "ties" + + void check_error(const ParallelErrorMessage& err, const std::string & context) const; +}; + +} // namespace krino + +#endif // Akri_Fast_Marching_h diff --git a/packages/krino/krino/krino_lib/Akri_FieldRef.cpp b/packages/krino/krino/krino_lib/Akri_FieldRef.cpp new file mode 100644 index 000000000000..c10c1dd8a22f --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_FieldRef.cpp @@ -0,0 +1,54 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include // for operator<<, stringstream, etc + +namespace krino{ + +bool FieldRef::operator < ( const FieldRef & rhs ) const +{ + if ( my_field == rhs.my_field || rhs.my_field == NULL ) + { + return false; // identical + } + else if (my_field == NULL) + { + return true; + } + else if ( my_field->field_state(stk::mesh::StateNone) == rhs.field().field_state(stk::mesh::StateNone) ) + { + return my_field->state() < rhs.state(); + } + return name() < rhs.name(); +} + +std::string +state_string(stk::mesh::FieldState state) +{ + static const char * const local_state_name[] = { + "STATE_NONE/NEW/NP1", + "STATE_OLD/N", + "STATE_NM1", + "STATE_NM2", + "STATE_NM3", + "STATE_NM4" + }; + + if ((unsigned) state < (unsigned)sizeof(local_state_name)/(unsigned)sizeof(local_state_name[0])) + return local_state_name[state]; + else { + std::stringstream strout; + strout << "(" << (unsigned) state << ")"; + return strout.str().c_str(); + } +} + +//---------------------------------------------------------------------- + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_FieldRef.hpp b/packages/krino/krino/krino_lib/Akri_FieldRef.hpp new file mode 100644 index 000000000000..b64fcdf37f36 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_FieldRef.hpp @@ -0,0 +1,168 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_FieldRef_h +#define Akri_FieldRef_h + +#include // for NULL +#include // for string +#include "stk_mesh/base/DataTraits.hpp" // for DataTraits +#include "stk_mesh/base/Entity.hpp" // for Entity +#include "stk_mesh/base/Field.hpp" // for Field +#include "stk_mesh/base/FieldBase.hpp" // for FieldBase, etc +#include "stk_mesh/base/FieldState.hpp" // for FieldState, etc +#include "stk_mesh/base/Types.hpp" // for EntityRank +#include "stk_util/util/ReportHandler.hpp" // for ThrowAssertMsg, etc +namespace stk { namespace mesh { class Bucket; } } + +namespace krino{ + +std::string +state_string(stk::mesh::FieldState state); + +class FieldRef +{ +public: + // Only default constructed FieldRef (or copies of a default constructed FieldRef) should ever have my_field==NULL + FieldRef() : my_field(NULL) {} + + FieldRef(const FieldRef &) = default; + FieldRef & operator= (const FieldRef &) = default; + + FieldRef(const stk::mesh::FieldBase * in_field) + : my_field(in_field) + { + ThrowAssertMsg(NULL != in_field, "Cannot set FieldRef with NULL field."); + } + FieldRef(const stk::mesh::FieldBase * in_field, const stk::mesh::FieldState in_state) + : my_field(in_field) + { + ThrowAssertMsg(NULL != in_field, "Cannot set FieldRef with NULL field."); + my_field = in_field->field_state(in_state); + ThrowAssertMsg(NULL != my_field, "Invalid state."); + } + FieldRef(const stk::mesh::FieldBase & in_fieldref) + { + my_field = &in_fieldref; + } + FieldRef(const FieldRef in_fieldref, const stk::mesh::FieldState in_state) + { + ThrowAssertMsg(NULL != in_fieldref.my_field, "Cannot set FieldRef with NULL field."); + my_field = in_fieldref.my_field->field_state(in_state); + } + + FieldRef field_state(stk::mesh::FieldState in_state) const + { + return FieldRef(my_field, in_state); + } + + // assignment operators + FieldRef& operator=( const stk::mesh::FieldBase * in_field ) + { + ThrowAssertMsg(NULL != in_field, "Cannot set FieldRef with NULL field."); + my_field = in_field; + return *this; + } + FieldRef& operator=( const stk::mesh::FieldBase & in_field ) + { + my_field = &in_field; + return *this; + } + + bool valid() const { return NULL != my_field; } + bool valid_restriction_size() const { return NULL != my_field && 0 != my_field->restrictions().size(); } + void assert_valid() const { ThrowAssertMsg(NULL != my_field, "Attempt to access field of uninitialized FieldRef."); } + + // stk access + const stk::mesh::FieldBase & field() const { assert_valid(); return *my_field; } + stk::mesh::FieldBase & field() { assert_valid(); return const_cast(*my_field); } + operator const stk::mesh::FieldBase &() const { assert_valid(); return *my_field; } + + // pass through methods + unsigned number_of_states() const { assert_valid(); return my_field->number_of_states(); } + stk::mesh::FieldState state() const { assert_valid(); return my_field->state(); } + stk::mesh::EntityRank entity_rank() const { assert_valid(); return my_field->entity_rank(); } + + template bool type_is() const + { return my_field->type_is(); } + + // name_with_state() includes any suffix indicating the state, ie "_STKFS_OLD" + const std::string & name_with_state() const { assert_valid(); return my_field->name(); } + // name() is the basic field name without any suffix for state, regardless of the state of the field + std::string name() const { assert_valid(); return my_field->field_state(stk::mesh::StateNone)->name(); } + + unsigned length(const stk::mesh::Bucket& b) const + { + assert_valid(); + const unsigned type_stride = my_field->data_traits().stride_of; + const unsigned fieldSize = stk::mesh::field_bytes_per_entity(*my_field, b) / type_stride; + return fieldSize; + } + + unsigned length(stk::mesh::Entity e) const + { + assert_valid(); + const unsigned type_stride = my_field->data_traits().stride_of; + const unsigned fieldSize = stk::mesh::field_bytes_per_entity(*my_field, e) / type_stride; + return fieldSize; + } + + // Only safe if field has same length on all entities + unsigned length() const + { + ThrowAssert(valid()); + return my_field->max_size(my_field->entity_rank()); + } + + // testing + bool operator ==(const FieldRef & rhs) const { return my_field == rhs.my_field; } + bool operator !=(const FieldRef & rhs) const { return my_field != rhs.my_field; } + bool operator < (const FieldRef & rhs) const; + +private: + const stk::mesh::FieldBase * my_field; +}; + +typedef std::set FieldSet; + +inline std::ostream & operator << (std::ostream & os, const FieldRef & field_ref) +{ + const bool valid = field_ref.valid(); + os << "FieldRef valid = " << std::boolalpha << valid; + if(valid) + { + os << " field = " << field_ref.name_with_state(); + } + return os; +} + +template< class pod_type > +pod_type * field_data( const stk::mesh::FieldBase & field, stk::mesh::Entity entity ) +{ + return stk::mesh::field_data(static_cast< const stk::mesh::Field& >(field), entity); +} + +template< class pod_type > +pod_type * field_data( const stk::mesh::FieldBase & field, const stk::mesh::Bucket & bucket ) +{ + return stk::mesh::field_data(static_cast< const stk::mesh::Field& >(field), bucket); +} + +inline bool has_field_data( const FieldRef fieldref, stk::mesh::Entity entity ) +{ + return stk::mesh::field_is_allocated_for_bucket(fieldref.field(), fieldref.field().get_mesh().bucket(entity)); +} + +inline bool has_field_data( const FieldRef fieldref, const stk::mesh::Bucket & bucket ) +{ + return stk::mesh::field_is_allocated_for_bucket(fieldref.field(), bucket); +} + +} // namespace krino + +#endif // Akri_FieldRef_h diff --git a/packages/krino/krino/krino_lib/Akri_IC_Alg.cpp b/packages/krino/krino/krino_lib/Akri_IC_Alg.cpp new file mode 100644 index 000000000000..0050bcc786fc --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_IC_Alg.cpp @@ -0,0 +1,225 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace krino{ + +namespace { +double relative_crossing_position(const double ls0, const double ls1) +{ + return LevelSet::sign_change(ls0, ls1) ? ls0 / ( ls0 - ls1 ) : -10.; +} +} + +//---------------------------------------------------------------- +void IC_Alg::execute(const double time, const bool requires_additional_initialization) +{ /* %TRACE[ON]% */ Trace trace__("krino::IC_Analytic_Alg::execute()"); /* %TRACE% */ + + if (surface_list.size() == 0 && my_calculators.empty()) + { + return; + } + + const stk::mesh::BulkData& mesh = levelSet.mesh(); + const stk::mesh::MetaData& meta = mesh.mesh_meta_data(); + const FieldRef xField = levelSet.get_coordinates_field(); + const FieldRef dField = levelSet.get_distance_field(); + const int spatial_dim = meta.spatial_dimension(); + + BoundingBox node_bbox; + levelSet.compute_nodal_bbox( mesh.mesh_meta_data().universal_part(), node_bbox ); + surface_list.prepare_to_compute(time, node_bbox, levelSet.narrow_band_size()); + + stk::mesh::BucketVector const& buckets = mesh.get_buckets( stk::topology::NODE_RANK, stk::mesh::selectField(dField) ); + + for ( auto && bucket_ptr : buckets ) + { + const stk::mesh::Bucket & b = *bucket_ptr; + const int length = b.size(); + double *dist = field_data(dField, b); + double * coord = field_data(xField, b); + + for (int n = 0; n < length; ++n) + { + ThrowAssert(&(dist[n]) != NULL); + + const Vector3d x(&coord[spatial_dim*n], spatial_dim); + + dist[n] = surface_list.point_signed_distance_with_narrow_band(x, levelSet.narrow_band_size()); + } + } + + stk::mesh::communicate_field_data(mesh, {&dField.field()}); + + if (levelSet.narrow_band_size() > 0. && surface_list.truncated_distance_may_have_wrong_sign()) + { + DistanceSweeper::fix_sign_by_sweeping(mesh, dField, surface_list.get_signed_narrow_band_size(levelSet.narrow_band_size())); + } + + if(CDFEM_Support::get(meta).get_nonconformal_adapt_target_count() > 0) + { + compute_IC_error_indicator(); + } + + for (auto && calc : my_calculators) + { + calc->compute_signed_distance(levelSet); + } + + if (!levelSet.get_keep_IC_surfaces() && !requires_additional_initialization) + { + clear(); + } +} + +void IC_Alg::compute_IC_error_indicator() +{ + const stk::mesh::BulkData& mesh = levelSet.mesh(); + const stk::mesh::MetaData& meta = mesh.mesh_meta_data(); + const FieldRef xField = levelSet.get_coordinates_field(); + const FieldRef dField = levelSet.get_distance_field(); + stk::mesh::Selector fieldSelector(dField); + const int spatial_dim = meta.spatial_dimension(); + const auto & cdfem_support = CDFEM_Support::get(meta); + + const auto & aux_meta = AuxMetaData::get(meta); + const auto & indicator_field_name = cdfem_support.get_nonconformal_adapt_indicator_name(); + auto indicator_field = + aux_meta.get_field(stk::topology::ELEMENT_RANK, indicator_field_name); + const auto & elem_buckets = + mesh.get_buckets(stk::topology::ELEMENT_RANK, meta.locally_owned_part() & fieldSelector); + + std::vector nodal_signed_distances; + std::vector nodal_coordinates; + std::vector edge_midpoints; + std::vector midpoint_signed_distances, midpoint_interp_signed_distances; + int edge_nodes[] = {0, 0}; + + for(auto && b_ptr : elem_buckets) + { + const auto & b = *b_ptr; + + const int size = b.size(); + double * indicator_data = field_data(indicator_field, b); + + const stk::topology & topo = b.topology(); + + const auto & master_elem = MasterElementDeterminer::getMasterElement(*b_ptr, xField); + const int num_nodes = master_elem.num_nodes(); + + nodal_signed_distances.resize(num_nodes); + nodal_coordinates.resize(num_nodes); + + for(int el=0; el < size; ++el) + { + const auto * nodes = mesh.begin_nodes(b[el]); + for(int n=0; n < num_nodes; ++n) + { + auto node = nodes[n]; + nodal_signed_distances[n] = *field_data(dField, node); + const double * coords_data = field_data(xField, node); + nodal_coordinates[n] = Vector3d(coords_data, spatial_dim); + } + + // Iterate edges, find location of crossing on the edge and compare to location of crossing + // if we add a node at the midpoint with the exact signed distance there. + const int num_edges = topo.num_edges(); + edge_midpoints.resize(num_edges); + midpoint_signed_distances.resize(num_edges); + midpoint_interp_signed_distances.resize(num_edges); + double err = 0.; + for (int e=0; e < num_edges; ++e) + { + ThrowAssert(topo.edge_topology(e).num_nodes() == 2); + topo.edge_node_ordinals(e, edge_nodes); + + const Vector3d & x0 = nodal_coordinates[edge_nodes[0]]; + const Vector3d & x1 = nodal_coordinates[edge_nodes[1]]; + edge_midpoints[e] = 0.5*(x0+x1); + const double edge_length_sqr = (x1-x0).length_squared(); + + double midpoint_signed_distance = + surface_list.point_signed_distance_with_narrow_band(edge_midpoints[e], levelSet.narrow_band_size()); + midpoint_signed_distances[e] = midpoint_signed_distance; + + const double ls0 = nodal_signed_distances[edge_nodes[0]]; + const double ls1 = nodal_signed_distances[edge_nodes[1]]; + midpoint_interp_signed_distances[e] = 0.5 * (ls0 + ls1); + const double orig_crossing_pos = relative_crossing_position(ls0, ls1); + const double left_half_crossing_pos = + 0.5 * relative_crossing_position(ls0, midpoint_signed_distance); + const double right_half_crossing_pos = + 0.5 * (1. + relative_crossing_position(midpoint_signed_distance, ls1)); + + if(orig_crossing_pos >= 0.) + { + if(left_half_crossing_pos >= 0.) + { + const double delta = orig_crossing_pos - left_half_crossing_pos; + err += edge_length_sqr * delta * delta; + } + if(right_half_crossing_pos >= 0.) + { + const double delta = orig_crossing_pos - right_half_crossing_pos; + err += edge_length_sqr * delta * delta; + } + } + else + { + if(left_half_crossing_pos >= 0. || right_half_crossing_pos >= 0.) + { + err += edge_length_sqr; + } + } + } + + for(int ei=0; ei < num_edges; ++ei) + { + for(int ej=ei+1; ej < num_edges; ++ej) + { + const double edge_length_sqr = (edge_midpoints[ei] - edge_midpoints[ej]).length_squared(); + const double interp_crossing_pos = + relative_crossing_position(midpoint_interp_signed_distances[ei], + midpoint_interp_signed_distances[ej]); + const double crossing_pos = + relative_crossing_position(midpoint_signed_distances[ei], + midpoint_signed_distances[ej]); + + const bool has_interp_crossing = interp_crossing_pos >= 0.; + const bool has_crossing = crossing_pos >= 0.; + if(has_interp_crossing != has_crossing) err += edge_length_sqr; + if(has_crossing && has_interp_crossing) + { + const double delta = crossing_pos - interp_crossing_pos; + err += edge_length_sqr * delta * delta; + } + } + } + indicator_data[el] += err; + } + } +} + +//---------------------------------------------------------------- +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_IC_Alg.hpp b/packages/krino/krino/krino_lib/Akri_IC_Alg.hpp new file mode 100644 index 000000000000..b74bb2ebf174 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_IC_Alg.hpp @@ -0,0 +1,55 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_IC_Alg_h +#define Akri_IC_Alg_h + +#include +#include +#include + +namespace krino { class LevelSet; } + +namespace krino { + +//---------------------------------------------------------------- +class IC_Alg { + +public: + IC_Alg( LevelSet & ls ) : levelSet( ls ), surface_list("IC Surface List") {} + ~IC_Alg() {} + + void set_composition_method(Composite_Surface::CompositionMethod composition_method) { + surface_list.set_composition_method(composition_method); + } + + // query number of surfaces + unsigned numberSurfaces() { return surface_list.size();} + + // push a surface onto our container + void addSurface(Surface * surf) { surface_list.add(surf); } + + // remove all surfaces and calculators + void clear() { surface_list.clear(); my_calculators.clear(); } + + void addCalculator(std::unique_ptr calc) { my_calculators.emplace_back(std::move(calc)); } + + BoundingBox get_surface_bounding_box() { return surface_list.get_bounding_box(); } + + void execute(const double time, const bool requires_additional_initialization); +private: + void compute_IC_error_indicator(); +private: + LevelSet & levelSet; + Composite_Surface surface_list; + std::vector> my_calculators; +}; +//---------------------------------------------------------------- +} // namespace krino + +#endif // Akri_IC_Alg_h diff --git a/packages/krino/krino/krino_lib/Akri_IC_Calculator.cpp b/packages/krino/krino/krino_lib/Akri_IC_Calculator.cpp new file mode 100644 index 000000000000..6ff101371964 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_IC_Calculator.cpp @@ -0,0 +1,100 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +namespace krino{ + +void IC_Binder::compute_signed_distance(const LevelSet &ls) const +{ + const stk::mesh::MetaData & meta = ls.meta(); + const stk::mesh::BulkData & mesh = ls.mesh(); + const FieldRef distRef = ls.get_distance_field(); + + const stk::mesh::Selector selector = stk::mesh::selectField(distRef); + + const LevelSetManager & region_ls = LevelSetManager::get(meta); + const unsigned num_ls = region_ls.numberLevelSets(); + + std::vector otherDistRefs; + for (unsigned i=0; i otherDist(num_other_dist, nullptr); + for ( auto && bucket : mesh.get_buckets( stk::topology::NODE_RANK, selector) ) + { + const stk::mesh::Bucket & b = *bucket; + const size_t length = b.size(); + + double * dist = field_data(distRef, b); + for (unsigned i=0; i(otherDistRefs[i], b); + } + + for ( size_t n = 0; n < length; ++n ) + { + dist[n] = std::numeric_limits::max(); + for (unsigned i=0; i<(num_other_dist-my_binder_type); ++i) + { + for (unsigned j=i+1; j<(num_other_dist-my_binder_type); ++j) + { + //ensure binder separates particles by a distance of my_interface_size + if (my_binder_type==0) + { + dist[n] = std::min(dist[n], std::max(otherDist[i][n]-my_interface_size, otherDist[j][n]-my_interface_size)); + } + + //create smooth connection between particles if bridge size specified + //take the product of two offset particle level sets and move the zero crossing by bridge_size + if (my_binder_type==1) + { + if (my_smooth_bridge_size > 0.0) + { + double bridge = (otherDist[i][n]+my_smooth_bridge_offset)*(otherDist[j][n]+my_smooth_bridge_offset)-my_smooth_bridge_size; + if (my_root_smooth_bridge) + { + //root the level set product to bring it back to distance function magnitudes + const int sign = (bridge >= 0.0) ? 1 : -1; + bridge = sign*std::sqrt(std::abs(bridge)); + } + dist[n] = std::min(dist[n], bridge); + } + } + } + } + for (unsigned i=0; i + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace krino{ + +Block_Surface_Connectivity::Block_Surface_Connectivity(const stk::mesh::MetaData & meta) +{ + /* %TRACE[ON]% */ + Trace trace__("Block_Surface_Connectivity::Block_Surface_Connectivity(const stk::mesh::MetaData & meta)"); /* %TRACE% */ + + std::vector surfacesInMap = meta.get_surfaces_in_surface_to_block_map(); + for(auto && surfaceInMap : surfacesInMap) + { + std::set touching_block_ordinals; + for (auto && touching_part : meta.get_blocks_touching_surface(surfaceInMap)) + { + touching_block_ordinals.insert(touching_part->mesh_meta_data_ordinal()); + } + add_surface(surfaceInMap->mesh_meta_data_ordinal(), touching_block_ordinals); + } +} + +Block_Surface_Connectivity::Block_Surface_Connectivity(const stk::mesh::MetaData & meta, const Ioss::Region & io_region) +{ + /* %TRACE[ON]% */ + Trace trace__("Block_Surface_Connectivity::Block_Surface_Connectivity(const Ioss::Region & reg)"); /* %TRACE% */ + + std::vector side_block_names; + std::vector side_block_ordinals; + + for(auto sideset : io_region.get_sidesets()) + { + side_block_names.clear(); + sideset->block_membership(side_block_names); + side_block_ordinals.clear(); + for (auto && block_name : side_block_names) + { + const stk::mesh::Part * side_block_part = meta.get_part(block_name); + ThrowRequire(nullptr != side_block_part); + side_block_ordinals.push_back(side_block_part->mesh_meta_data_ordinal()); + } + const stk::mesh::Part * side_part = meta.get_part(sideset->name()); + ThrowRequire(nullptr != side_part); + add_surface(side_part->mesh_meta_data_ordinal(), std::set(side_block_ordinals.begin(), side_block_ordinals.end())); + + if (!sideset->get_side_blocks().empty()) + { + for (auto&& side_subset : sideset->get_side_blocks()) + { + // Fmwk only creates subset if more than 1 sideblock, but stk always creates them, so just check. + const stk::mesh::Part * side_subset_part = meta.get_part(side_subset->name()); + if (nullptr == side_subset_part) continue; + side_block_names.clear(); + side_subset->block_membership(side_block_names); + side_block_ordinals.clear(); + for (auto && block_name : side_block_names) + { + const stk::mesh::Part * side_block_part = meta.get_part(block_name); + ThrowRequire(nullptr != side_block_part); + side_block_ordinals.push_back(side_block_part->mesh_meta_data_ordinal()); + } + add_surface(side_subset_part->mesh_meta_data_ordinal(), std::set(side_block_ordinals.begin(), side_block_ordinals.end())); + } + } + } +} + +std::vector +get_input_mesh_block_names( const Ioss::Region & io_region ) +{ + std::vector mesh_elem_blocks; + for(auto && elem_block : io_region.get_element_blocks()) + { + if (stk::io::include_entity(elem_block)) + { + mesh_elem_blocks.push_back(elem_block->name()); + } + } + return mesh_elem_blocks; +} + // namespace krino +} diff --git a/packages/krino/krino/krino_lib/Akri_IO_Helpers.hpp b/packages/krino/krino/krino_lib/Akri_IO_Helpers.hpp new file mode 100644 index 000000000000..2ec5005ffd08 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_IO_Helpers.hpp @@ -0,0 +1,66 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_IO_Helpers_h +#define Akri_IO_Helpers_h +// +#include +#include + +#include + +#include + +namespace stk { namespace mesh { class BulkData; } } +namespace stk { namespace mesh { class MetaData; } } +namespace stk { namespace diag { class Timer; } } +namespace Ioss { class Region; } + +namespace krino { + +class AuxMetaData; + +class Block_Surface_Connectivity +{ +public: + Block_Surface_Connectivity() {} + Block_Surface_Connectivity(const stk::mesh::MetaData & meta); + Block_Surface_Connectivity(const stk::mesh::MetaData & meta, const Ioss::Region & io_region); + void get_surfaces_touching_block(const stk::mesh::PartOrdinal & block_ordinal, + std::set & surface_ordinals) const + { + auto it = block_to_surface_map.find(block_ordinal); + if(it == block_to_surface_map.end()) return; + surface_ordinals.insert(it->second.begin(), it->second.end()); + } + void get_blocks_touching_surface(const stk::mesh::PartOrdinal & surface_ordinal, + std::set & block_ordinals) const + { + block_ordinals.clear(); + auto it = surface_to_block_map.find(surface_ordinal); + if(it == surface_to_block_map.end()) return; + block_ordinals.insert(it->second.begin(), it->second.end()); + } + + void add_surface(const stk::mesh::PartOrdinal & surf_ordinal, const std::set touching_blocks) + { + surface_to_block_map[surf_ordinal].insert(touching_blocks.begin(), touching_blocks.end()); + for(auto && block : touching_blocks) + { + block_to_surface_map[block].insert(surf_ordinal); + } + } +private: + std::map< stk::mesh::PartOrdinal, std::set > block_to_surface_map; + std::map< stk::mesh::PartOrdinal, std::set > surface_to_block_map; +}; + +std::vector get_input_mesh_block_names( const Ioss::Region & io_region ); +} // namespace krino + +#endif // Akri_IO_Helpers_h diff --git a/packages/krino/krino/krino_lib/Akri_InterfaceID.hpp b/packages/krino/krino/krino_lib/Akri_InterfaceID.hpp new file mode 100644 index 000000000000..a72328ac6b8b --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_InterfaceID.hpp @@ -0,0 +1,98 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_INTERFACEID_H_ +#define AKRI_INTERFACEID_H_ + +#include +#include +#include +#include + +#include + +namespace krino { + +class InterfaceID; +typedef std::map CrossingMap; +typedef std::map CrossingSignMap; + +class InterfaceID +{ +public: + InterfaceID() : ls_1(-1), ls_2(-1) {} + InterfaceID(const int first, const int second) : ls_1(std::min(first, second)), + ls_2(std::max(first, second)) {} + + bool is_single_ls() const { return ls_1 == ls_2; } + int first_ls() const { return ls_1; } + int second_ls() const {return ls_2; } + void fill_sorted_domains(std::vector & sortedDomains) const; + + static std::vector all_phase_pairs(const std::set & phases); +private: + int ls_1; + int ls_2; +}; + +inline bool operator<(const InterfaceID lhs, const InterfaceID rhs) +{ + if(lhs.first_ls() < rhs.first_ls()) return true; + if(rhs.first_ls() < lhs.first_ls()) return false; + if(lhs.second_ls() < rhs.second_ls()) return true; + return false; +} + +inline bool operator==(const InterfaceID lhs, const InterfaceID rhs) +{ + return (lhs.first_ls() == rhs.first_ls()) && (lhs.second_ls() == rhs.second_ls()); +} + +inline bool operator!=(const InterfaceID lhs, const InterfaceID rhs) +{ + return !(lhs == rhs); +} + +inline std::ostream & operator<<(std::ostream &os, const InterfaceID lhs) +{ + os << "InterfaceID(" << lhs.first_ls() << ", " << lhs.second_ls() << ")"; + return os; +} + +inline std::vector InterfaceID::all_phase_pairs(const std::set & phases) +{ + std::vector result; + std::set::const_iterator end_phase = phases.end(); + for(std::set::const_iterator it = phases.begin(); it != end_phase; ++it) + { + std::set::const_iterator it2 = it; + for(++it2; it2 != end_phase; ++it2) + { + result.push_back(InterfaceID(*it, *it2)); + } + } + return result; +} + +inline void InterfaceID::fill_sorted_domains(std::vector & sortedDomains) const +{ + sortedDomains.clear(); + if (first_ls() == second_ls()) + { + sortedDomains.push_back(first_ls()); + } + else + { + sortedDomains.push_back(first_ls()); + sortedDomains.push_back(second_ls()); + } +} + +} // namespace krino + +#endif /* AKRI_INTERFACEID_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_Interface_Name_Generator.hpp b/packages/krino/krino/krino_lib/Akri_Interface_Name_Generator.hpp new file mode 100644 index 000000000000..98e9bde5c0e7 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Interface_Name_Generator.hpp @@ -0,0 +1,71 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Interface_Name_Generator_h +#define Akri_Interface_Name_Generator_h +// + +#include + +namespace krino { + +// Interface_Name_Generator are used in Phase_Support::decompose_blocks() to determine the name of the +// interface sideset within a block so that cdfem death and LS-like problems can have different naming conventions +// that are more user-friendly for each case. +class Interface_Name_Generator +{ +public: + virtual ~Interface_Name_Generator() {} + virtual std::string interface_name(const std::string & io_part_name, const std::string & phase_name) const = 0; + virtual std::string interface_superset_name(const std::string & phase_name) const = 0; + virtual std::string interface_subset_name(const std::string & io_part_name, const std::string & phase_name) const = 0; +}; + +class LS_Name_Generator : public Interface_Name_Generator +{ +public: + virtual ~LS_Name_Generator() {} + virtual std::string interface_name(const std::string & io_part_name, const std::string & phase_name) const + { + return "surface_"+io_part_name+"_"+phase_name; + } + virtual std::string interface_superset_name(const std::string & phase_name) const + { + return "surface_"+phase_name; + } + virtual std::string interface_subset_name(const std::string & io_part_name, const std::string & phase_name) const + { + return "surface_"+io_part_name+"_"+phase_name; + } +}; + +class Death_Name_Generator : public Interface_Name_Generator +{ +public: + Death_Name_Generator(std::string spec_name) : death_spec_name(spec_name) {} + virtual ~Death_Name_Generator() {} + virtual std::string interface_name(const std::string & io_part_name, const std::string & phase_name) const + { + return "surface_"+io_part_name+"_"+death_spec_name; + } + virtual std::string interface_superset_name(const std::string & phase_name) const + { + return "surface_"+death_spec_name; + } + virtual std::string interface_subset_name(const std::string & io_part_name, const std::string & phase_name) const + { + return "surface_"+io_part_name+"_"+death_spec_name; + } +private: + std::string death_spec_name; +}; + + +} // namespace krino + +#endif // Akri_Interface_Name_Generator_h diff --git a/packages/krino/krino/krino_lib/Akri_Intersection_Points.cpp b/packages/krino/krino/krino_lib/Akri_Intersection_Points.cpp new file mode 100644 index 000000000000..f75cfc998e3c --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Intersection_Points.cpp @@ -0,0 +1,267 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include + +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" + +namespace krino { + +EdgeIntersection::EdgeIntersection(const IntersectionPoint & intersectionPt) +{ + const std::vector & intPtNodes = intersectionPt.get_nodes(); + ThrowAssert(intPtNodes.size() == 2); + nodes = {intPtNodes[0], intPtNodes[1]}; + crossingLocation = intersectionPt.get_weights()[1]; + const auto & domains = intersectionPt.get_sorted_domains(); + ThrowAssert(domains.size() == 1 || domains.size() == 2); + interface = (domains.size() == 1) ? InterfaceID(domains[0],domains[0]) : InterfaceID(domains[0], domains[1]); +} + +std::string debug_output(const stk::mesh::BulkData & mesh, const IntersectionPoint & intersectionPoint) +{ + const std::vector & nodes = intersectionPoint.get_nodes(); + const std::vector & weights = intersectionPoint.get_weights(); + const std::vector & domains = intersectionPoint.get_sorted_domains(); + std::ostringstream os; + os << "intersection point domains={ "; + for (int domain : domains) + os << domain << " "; + os << "} stencil={ "; + for (size_t i=0; i & firstVec, const std::vector & secondVec) +{ + for (int domain : secondVec) + if (!std::binary_search(firstVec.begin(), firstVec.end(), domain)) + return false; + return true; +} + +bool any_node_already_captures_intersection_point(const std::vector & nodes, + const std::vector & intersectionPointSortedDomains, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + for (auto node : nodes) + { + auto iter = nodesToCapturedDomains.find(node); + if (iter != nodesToCapturedDomains.end()) + { + const std::vector & nodeSortedDomains = iter->second; + if (first_sorted_vector_of_domains_contains_all_domains_in_second_vector(nodeSortedDomains, intersectionPointSortedDomains)) + return true; + } + } + return false; +} + +bool domains_already_snapped_to_node_are_also_at_intersection_point(const NodeToCapturedDomainsMap & nodesToCapturedDomains, stk::mesh::Entity node, const std::vector & intersectionPointDomains) +{ + const auto iter = nodesToCapturedDomains.find(node); + if (iter == nodesToCapturedDomains.end()) + return true; + return first_sorted_vector_of_domains_contains_all_domains_in_second_vector(intersectionPointDomains, iter->second); +} + +static bool any_entity_in_first_vector_is_contained_in_second_sorted_vector(const std::vector & firstVec, const std::vector & secondVec) +{ + for (auto && first : firstVec) + if (std::binary_search(secondVec.begin(), secondVec.end(), first)) + return true; + return false; +} + +IntersectionPointFilter +keep_all_intersection_points_filter() +{ + auto filter = + [](const std::vector & intersectionPointNodes, const std::vector & intersectionPointSortedDomains) + { + return true; + }; + return filter; +} + +static +void pack_intersection_points_for_owners_of_nodes(const stk::mesh::BulkData & mesh, + const std::vector & intersectionPoints, + stk::CommSparse &commSparse) +{ + std::vector intersectionPointNodeOwners; + stk::pack_and_communicate(commSparse,[&]() + { + for (auto && intersectionPoint : intersectionPoints) + { + intersectionPointNodeOwners.clear(); + for (auto && node : intersectionPoint.get_nodes()) + { + const int procId = mesh.parallel_owner_rank(node); + if (procId != commSparse.parallel_rank()) + intersectionPointNodeOwners.push_back(procId); + } + stk::util::sort_and_unique(intersectionPointNodeOwners); + for (int procId : intersectionPointNodeOwners) + { + commSparse.send_buffer(procId).pack(intersectionPoint.get_nodes().size()); + for (auto && node : intersectionPoint.get_nodes()) + commSparse.send_buffer(procId).pack(mesh.identifier(node)); + for (auto && weight : intersectionPoint.get_weights()) + commSparse.send_buffer(procId).pack(weight); + commSparse.send_buffer(procId).pack(intersectionPoint.get_sorted_domains().size()); + for (auto && domain : intersectionPoint.get_sorted_domains()) + commSparse.send_buffer(procId).pack(domain); + } + } + }); +} + +static +void unpack_intersection_points(const stk::mesh::BulkData & mesh, + std::vector & intersectionPoints, + stk::CommSparse &commSparse) +{ + std::vector intersectionPointNodes; + std::vector intersectionPointWeights; + std::vector intersectionPointDomains; + stk::mesh::EntityId nodeId; + const bool intersectionPointIsOwned = false; + + stk::unpack_communications(commSparse, [&](int procId) + { + stk::CommBuffer & buffer = commSparse.recv_buffer(procId); + + while ( buffer.remaining() ) + { + size_t numNodes; + commSparse.recv_buffer(procId).unpack(numNodes); + intersectionPointNodes.resize(numNodes); + for (auto && node : intersectionPointNodes) + { + commSparse.recv_buffer(procId).unpack(nodeId); + node = mesh.get_entity(stk::topology::NODE_RANK, nodeId); + } + intersectionPointWeights.resize(numNodes); + for (auto && weight : intersectionPointWeights) + commSparse.recv_buffer(procId).unpack(weight); + size_t numDomains; + commSparse.recv_buffer(procId).unpack(numDomains); + intersectionPointDomains.resize(numDomains); + for (auto && domain : intersectionPointDomains) + { + commSparse.recv_buffer(procId).unpack(domain); + } + intersectionPoints.emplace_back(intersectionPointIsOwned, intersectionPointNodes, intersectionPointWeights, intersectionPointDomains); + } + }); +} + +void communicate_intersection_points(const stk::mesh::BulkData & mesh, std::vector & intersectionPoints) +{ + stk::CommSparse commSparse(mesh.parallel()); + pack_intersection_points_for_owners_of_nodes(mesh, intersectionPoints, commSparse); + unpack_intersection_points(mesh, intersectionPoints, commSparse); +} + +static std::vector build_intersection_points(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & geometry, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + const IntersectionPointFilter & intersectionPointFilter) +{ + std::vector elementsToIntersect; + stk::mesh::get_entities( mesh, stk::topology::ELEMENT_RANK, mesh.mesh_meta_data().locally_owned_part(), elementsToIntersect, false); + + std::vector intersectionPoints; + geometry.append_element_intersection_points(mesh, nodesToCapturedDomains, elementsToIntersect, intersectionPointFilter, intersectionPoints); + communicate_intersection_points(mesh, intersectionPoints); + + return intersectionPoints; +} + +std::vector build_all_intersection_points(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & geometry, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + const IntersectionPointFilter intersectionPointFilter = keep_all_intersection_points_filter(); + return build_intersection_points(mesh, geometry, nodesToCapturedDomains, intersectionPointFilter); +} + +static IntersectionPointFilter +filter_intersection_points_to_those_not_already_handled(const NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + auto filter = + [&nodesToCapturedDomains](const std::vector & intersectionPointNodes, const std::vector & intersectionPointSortedDomains) + { + return !any_node_already_captures_intersection_point(intersectionPointNodes, intersectionPointSortedDomains, nodesToCapturedDomains); + }; + return filter; +} + +std::vector build_uncaptured_intersection_points(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & geometry, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + IntersectionPointFilter intersectionPointFilter = filter_intersection_points_to_those_not_already_handled(nodesToCapturedDomains); + + return build_intersection_points(mesh, geometry, nodesToCapturedDomains, intersectionPointFilter); +} + +static IntersectionPointFilter +filter_intersection_points_to_those_using_previous_iteration_snap_nodes_but_not_already_handled(const NodeToCapturedDomainsMap & nodesToCapturedDomains, const std::vector & iterationSortedSnapNodes) +{ + auto filter = + [&nodesToCapturedDomains, &iterationSortedSnapNodes](const std::vector & intersectionPointNodes, const std::vector & intersectionPointSortedDomains) + { + return any_entity_in_first_vector_is_contained_in_second_sorted_vector(intersectionPointNodes, iterationSortedSnapNodes) && + !any_node_already_captures_intersection_point(intersectionPointNodes, intersectionPointSortedDomains, nodesToCapturedDomains); + }; + return filter; +} + +std::vector get_owned_elements_using_nodes_knowing_that_nodes_dont_have_common_elements(const stk::mesh::BulkData & mesh, + const std::vector & nodes) +{ + std::vector nodeElements; + for (auto node : nodes) + for (auto element : StkMeshEntities{mesh.begin_elements(node), mesh.end_elements(node)}) + if (mesh.bucket(element).owned()) + nodeElements.push_back(element); + return nodeElements; +} + +void update_intersection_points_after_snap_iteration(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & geometry, + const std::vector & iterationSortedSnapNodes, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + std::vector & intersectionPoints) +{ + const auto intersectionPointFilter = filter_intersection_points_to_those_using_previous_iteration_snap_nodes_but_not_already_handled(nodesToCapturedDomains, iterationSortedSnapNodes); + + size_t newSize=0; + for (auto && intersectionPoint : intersectionPoints) + { + if (intersectionPoint.is_owned() && !any_entity_in_first_vector_is_contained_in_second_sorted_vector(intersectionPoint.get_nodes(), iterationSortedSnapNodes)) + intersectionPoints[newSize++] = intersectionPoint; // FIXME use custom swap? + } + intersectionPoints.erase(intersectionPoints.begin()+newSize, intersectionPoints.end()); + + const std::vector updateElements = get_owned_elements_using_nodes_knowing_that_nodes_dont_have_common_elements(mesh, iterationSortedSnapNodes); + geometry.append_element_intersection_points(mesh, nodesToCapturedDomains, updateElements, intersectionPointFilter, intersectionPoints); + communicate_intersection_points(mesh, intersectionPoints); +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_Intersection_Points.hpp b/packages/krino/krino/krino_lib/Akri_Intersection_Points.hpp new file mode 100644 index 000000000000..0f7c07da5832 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Intersection_Points.hpp @@ -0,0 +1,75 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_INTERSECTION_POINTS_H_ +#define KRINO_INCLUDE_AKRI_INTERSECTION_POINTS_H_ + +#include +#include +#include +#include +#include + +namespace krino { + +class CDFEM_Support; +class Phase_Support; +class CDFEM_Parent_Edge; +class InterfaceGeometry; + +class IntersectionPoint +{ +public: + IntersectionPoint(const bool owned, const std::vector & nodes, const std::vector & weights, const std::vector & sortedDomains) + : mOwned(owned), mNodes(nodes), mWeights(weights), mSortedDomains(sortedDomains) {ThrowAssert(mNodes.size() == mWeights.size());} + bool is_owned() const { return mOwned; } + const std::vector & get_nodes() const { return mNodes; } + const std::vector & get_weights() const { return mWeights; } + const std::vector & get_sorted_domains() const { return mSortedDomains; } +private: + bool mOwned; + std::vector mNodes; + std::vector mWeights; + std::vector mSortedDomains; +}; + +struct EdgeIntersection +{ + EdgeIntersection(const IntersectionPoint & intersectionPt); + std::array nodes; + double crossingLocation; + InterfaceID interface; +}; + +void communicate_intersection_points(const stk::mesh::BulkData & mesh, std::vector & intersectionPoints); + +typedef std::function &, const std::vector &)> IntersectionPointFilter; + +bool first_sorted_vector_of_domains_contains_all_domains_in_second_vector(const std::vector & firstVec, const std::vector & secondVec); +bool domains_already_snapped_to_node_are_also_at_intersection_point(const NodeToCapturedDomainsMap & nodesToCapturedDomains, stk::mesh::Entity node, const std::vector & intersectionPointDomains); +std::string debug_output(const stk::mesh::BulkData & mesh, const IntersectionPoint & intersectionPoint); + +IntersectionPointFilter keep_all_intersection_points_filter(); + +std::vector build_all_intersection_points(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & geometry, + const NodeToCapturedDomainsMap & nodesToCapturedDomains); + +std::vector build_uncaptured_intersection_points(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & geometry, + const NodeToCapturedDomainsMap & nodesToCapturedDomains); + +void update_intersection_points_after_snap_iteration(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & geometry, + const std::vector & iterationSortedSnapNodes, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + std::vector & intersectionPoints); + +} + +#endif /* KRINO_INCLUDE_AKRI_INTERSECTION_POINTS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_LevelSet.cpp b/packages/krino/krino/krino_lib/Akri_LevelSet.cpp new file mode 100644 index 000000000000..64dc628a7d55 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_LevelSet.cpp @@ -0,0 +1,1822 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace krino { + +bool all_nodes_have_field_data(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity entity, const stk::mesh::FieldBase& field) +{ + const unsigned nnodes = stk_bulk.num_nodes(entity); + const stk::mesh::Entity* nodes = stk_bulk.begin_nodes(entity); + for (unsigned i = 0; i < nnodes; ++i ) + { + if (field_bytes_per_entity(field, nodes[i]) == 0) + { + return false; + } + } + return true; +} + +std::vector LevelSet::the_levelSet_names; + +LevelSet_Identifier LevelSet::get_identifier(const std::string & name) +{ + std::string upper_name = name; + std::transform(upper_name.begin(), upper_name.end(), upper_name.begin(), ::toupper); + for (unsigned i=0; i 0; +} + +BoundingBox LevelSet::get_IC_surface_bounding_box() +{ + IC_Alg& ic_alg = get_IC_alg(); + return ic_alg.get_surface_bounding_box(); +} + +IC_Alg& LevelSet::get_IC_alg() +{ + if (!my_IC_alg) + { + my_IC_alg = std::make_unique(*this); + } + return *my_IC_alg; +} + +void LevelSet::set_current_coordinates(stk::mesh::MetaData & meta, const FieldRef ref) +{ + LevelSetManager & ls_manager = LevelSetManager::get(meta); + ls_manager.set_current_coordinates(ref); +} + +FieldRef LevelSet::get_current_coordinates(stk::mesh::MetaData & meta) +{ + LevelSetManager & ls_manager = LevelSetManager::get(meta); + FieldRef current_coords = ls_manager.get_current_coordinates(); + if (!current_coords.valid()) + { + const stk::mesh::FieldBase * meta_coords = meta.coordinate_field(); + ThrowRequireMsg(nullptr != meta_coords, "Coordinates must be defined before calling LevelSet::get_current_coordinates()."); + ls_manager.set_current_coordinates(meta_coords); + current_coords = ls_manager.get_current_coordinates(); + } + return current_coords; +} + +void LevelSet::setup(stk::mesh::MetaData & meta) +{ + const LevelSetManager & region_ls = LevelSetManager::get(meta); + for (auto&& ls : region_ls) + { + ls->setup(); + } +} + +void LevelSet::post_commit_setup(stk::mesh::MetaData & meta) +{ + const double max_elem_size = compute_maximum_element_size(meta.mesh_bulk_data()); + + const LevelSetManager & region_ls = LevelSetManager::get(meta); + for (auto&& ls : region_ls) + { + ls->setup(); + if (ls->my_narrow_band_multiplier > 0.) + { + ls->narrow_band_size(ls->my_narrow_band_multiplier * max_elem_size); + } + else if (ls->narrow_band_size() > 0.) + { + const double narrow_band = ls->narrow_band_size(); + ThrowErrorMsgIf(!(narrow_band > max_elem_size), + "Currently, narrow_band_size must be greater than the maximum element size of " << max_elem_size << std::endl + << "in order to avoid unintentional accuracy degradation. If this feature is needed, please contact krino developers."); + } + } +} + + +void LevelSet::setup(void) +{ + register_fields(); + + facets.reset(new Faceted_Surface("current_facets")); + facets_old.reset(new Faceted_Surface("old_facets")); + + // initializes CDFEM_Support + if (!meta().is_commit()) + { + CDFEM_Support::get(my_meta); + } +} +//-------------------------------------------------------------------------------- +void LevelSet::register_fields(void) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::register_fields(void)"); /* %TRACE% */ + + if (trackIsoSurface || aux_meta().has_field(stk::topology::NODE_RANK, my_distance_name)) + { + // non-krino region + if (trackIsoSurface) + { + if (aux_meta().has_field(stk::topology::NODE_RANK, my_isovar_name)) + { + set_isovar_field( aux_meta().get_field(stk::topology::NODE_RANK, my_isovar_name) ); + } + else if (aux_meta().has_field(stk::topology::ELEMENT_RANK, my_isovar_name)) + { + set_isovar_field( aux_meta().get_field(stk::topology::ELEMENT_RANK, my_isovar_name) ); + } + else + { + ThrowErrorMsgIf( + true, "Isosurface variable '" << my_isovar_name << "' should already be registered."); + } + } + else + { + const FieldRef distance_ref = aux_meta().get_field(stk::topology::NODE_RANK, my_distance_name); + if ( distance_ref.number_of_states() == 1 ) + { + set_distance_field( distance_ref ); + set_old_distance_field( distance_ref ); + } + else + { + set_distance_field( distance_ref.field_state(stk::mesh::StateNew) ); + set_old_distance_field( distance_ref.field_state(stk::mesh::StateOld) ); + } + + set_isovar_field( my_distance_field ); + } + } + else + { + if (aux_meta().using_fmwk()) + { + ThrowRuntimeError("ERROR: field " << my_distance_name << " is not registered for level set " << name() << ". " + << "This can be caused by an incorrect Distance Variable. " + << "Or, in aria, there could be a conflict between the specified subindex and the named species. " + << "If so, try using a subindex greater than the number of species for your level set."); + } + + // krino region + const FieldType & type_double = FieldType::REAL; + + if(krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "KRINO: Registering distance variable with name '" << my_distance_name << "'." << stk::diag::dendl; + + const bool cdfem_is_active = krino::CDFEM_Support::is_active(meta()); + if (cdfem_is_active) + { + Phase_Support phase_support = Phase_Support::get(meta()); + for (auto partPtr : meta().get_mesh_parts()) + { + if (partPtr->primary_entity_rank() == stk::topology::ELEMENT_RANK && + phase_support.level_set_is_used_by_nonconformal_part(this, phase_support.find_nonconformal_part(*partPtr))) + { + FieldRef distance_ref = aux_meta().register_field( my_distance_name, type_double, stk::topology::NODE_RANK, 1, 1, *partPtr ); + set_old_distance_field( distance_ref ); + set_distance_field( distance_ref ); + } + } + } + else + { + FieldRef distance_ref = aux_meta().register_field( my_distance_name, type_double, stk::topology::NODE_RANK, 2, 1, meta().universal_part() ); + set_old_distance_field( FieldRef( distance_ref, stk::mesh::StateOld ) ); + set_distance_field( FieldRef( distance_ref, stk::mesh::StateNew ) ); + } + + set_isovar_field( my_distance_field ); + } + + if (!myTimeOfArrivalBlockSpeedsByName.empty()) + { + myTimeOfArrivalBlockSpeeds.resize(my_meta.get_parts().size()); + for (auto entry : myTimeOfArrivalBlockSpeedsByName) + { + if (my_aux_meta.has_part(entry.first)) + { + myTimeOfArrivalBlockSpeeds[my_aux_meta.get_part(entry.first).mesh_meta_data_ordinal()] = entry.second; + } + else + { + ThrowErrorMsgIf(true, "Could not find block " << entry.first << " when setting speed for computing time-of-arrival."); + } + } + } + + if (!my_time_of_arrival_element_speed_field_name.empty()) + { + const bool hasSpeedField = aux_meta().has_field(stk::topology::ELEMENT_RANK, my_time_of_arrival_element_speed_field_name); + ThrowErrorMsgIf(!hasSpeedField, "Could not find element speed field " << my_time_of_arrival_element_speed_field_name << " for computing time-of-arrival."); + myTimeOfArrivalElementSpeedField = aux_meta().get_field(stk::topology::ELEMENT_RANK, my_time_of_arrival_element_speed_field_name); + ThrowRequireMsg(myTimeOfArrivalBlockSpeeds.empty(), "Speed for time-of-arrival calculation should be specified via element speed or block speed (not both)."); + } +} + +//----------------------------------------------------------------------------------- + +void +LevelSet::set_time_of_arrival_block_speed(const std::string & blockName, const double blockSpeed) +{ + std::string lowerBlockName = blockName; + std::transform(lowerBlockName.begin(), lowerBlockName.end(), lowerBlockName.begin(), ::tolower); + auto entry = myTimeOfArrivalBlockSpeedsByName.find(lowerBlockName); + ThrowRequireMsg(entry == myTimeOfArrivalBlockSpeedsByName.end(), "Speed for block " << blockName << " specified more than once."); + myTimeOfArrivalBlockSpeedsByName[lowerBlockName] = blockSpeed; +} + +//----------------------------------------------------------------------------------- +void +LevelSet::facets_exoii(void) +{ + /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::facets_exoii(void)"); /* %TRACE% */ + Faceted_Surface & f = *facets; + facets_exoii(f); +} + + +void +LevelSet::facets_exoii(Faceted_Surface & cs) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::facets_exoii(Faceted_Surface & cs)"); /* %TRACE% */ + + int nfaces = cs.size(); + const int nodes_per_elem = spatial_dimension; + const int nnodes = nfaces * nodes_per_elem; + const int nelems = nfaces * 1; //writing out faces as elements + + // construct file name + ThrowAssert(my_facetFileIndex <= 99999); + char counterChar[6]; + std::sprintf(counterChar, "%.5d", my_facetFileIndex); + std::string fn = std::string("facets.") + name() + ".e-s" + std::string(counterChar); + + // Type (ExodusII) is hard-wired at this time. + Ioss::DatabaseIO *db = Ioss::IOFactory::create("exodusII", fn, Ioss::WRITE_RESULTS); + Ioss::Region io(db, "FacetRegion"); + + // find offsets for consistent global numbering + int elem_start = 0; + if ( mesh().parallel_size() > 1 ) { + std::vector< int > num_faces; + num_faces.resize( mesh().parallel_size() ); + + // Gather everyone's facet list sizes + // I don't think there is a framework call for this... + MPI_Allgather( &nfaces, 1, MPI_INT, + &(num_faces[0]), 1, MPI_INT, mesh().parallel() ); + for (int i = 0; i< mesh().parallel_rank(); ++i) { + elem_start += num_faces[i]; + } + } + + const std::string description = "level set interface facets"; + io.property_add(Ioss::Property("title", description)); + io.begin_mode(Ioss::STATE_DEFINE_MODEL); + + // if we have no elements bail now + if ( 0 == nelems ) { + io.end_mode(Ioss::STATE_DEFINE_MODEL); + my_facetFileIndex++; + return; + } + + Ioss::NodeBlock *nb = new Ioss::NodeBlock(db, "nodeblock_1", nnodes, spatial_dimension); + io.add(nb); + + std::string el_type; + if (spatial_dimension == 3) + el_type = "trishell3"; + else + el_type = "shellline2d2"; + + Ioss::ElementBlock *eb = new Ioss::ElementBlock(db, "block_1", el_type, nelems); + io.add(eb); + + io.end_mode(Ioss::STATE_DEFINE_MODEL); + io.begin_mode(Ioss::STATE_MODEL); + + const FacetOwningVec & cs_surfaces = cs.get_facets(); + + // loop over elements and node to create maps + { + std::vector< int > nmap, emap; + nmap.reserve(nnodes); + emap.reserve(nelems); + + for ( unsigned n=0, e=0; eput_field_data("ids", nmap); + eb->put_field_data("ids", emap); + } + + // generate coordinates + { + std::vector< double > xyz; + xyz.reserve(spatial_dimension*nnodes); + + for ( auto&& cs_surface : cs_surfaces ) { + for ( int j = 0; j < nodes_per_elem; ++j ) { + const Vector3d & vert = cs_surface->facet_vertex(j); + xyz.push_back(vert[0]); + xyz.push_back(vert[1]); + if (3 == spatial_dimension) xyz.push_back(vert[2]); + } + } + nb->put_field_data("mesh_model_coordinates", xyz); + } + + // generate connectivity + { + std::vector< int > conn; + conn.reserve(nnodes); + for ( int n = 0; n < nnodes; ++n ) { + conn.push_back(nodes_per_elem*elem_start + n+1); + } + eb->put_field_data("connectivity", conn); + } + + io.end_mode(Ioss::STATE_MODEL); + my_facetFileIndex++; +} + +//----------------------------------------------------------------------------------- +void +LevelSet::compute_surface_distance(const double narrowBandSize, const double farFieldValue) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::compute_surface_distance(void)"); /* %TRACE% */ + + stk::mesh::Selector surface_selector = selectUnion(my_compute_surface_distance_parts); + + if (narrowBandSize > 0.0) + { + my_narrow_band_size = narrowBandSize; + } + + const stk::mesh::Field& coords = reinterpret_cast&>(get_coordinates_field().field()); + const stk::mesh::Field& dist = reinterpret_cast&>(get_distance_field().field()); + + Compute_Surface_Distance::calculate( + mesh(), + get_timer(), + coords, + dist, + surface_selector, + my_narrow_band_size, + farFieldValue); + + // output for time 0 is from old + if (!(my_distance_field == my_old_distance_field)) + { + stk::mesh::field_copy(my_distance_field, my_old_distance_field); + } + +} + +//----------------------------------------------------------------------------------- +void +LevelSet::set_surface_distance(std::vector surfaces, const double in_distance) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::compute_surface_distance(void)"); /* %TRACE% */ + + const FieldRef dField = get_distance_field(); + + const stk::mesh::Selector selector = stk::mesh::selectField(dField) & selectUnion(surfaces); + stk::mesh::BucketVector const& buckets = mesh().get_buckets( stk::topology::NODE_RANK, selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + + const size_t length = b.size(); + double *dist = field_data(dField , b); + + for ( size_t n = 0; n < length; ++n ) + { + dist[n] = in_distance; + } + } +} + +//----------------------------------------------------------------------------------- +void +LevelSet::advance_semilagrangian(const double deltaTime) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::advance_semilagrangian(const double deltaTime)"); /* %TRACE% */ + + if(trackIsoSurface) + { + redistance(); + } + else + { + stk::mesh::Selector selector(my_meta.universal_part()); + + // store existing facets in facets_old + facets->swap( *facets_old ); + + // get non-local facets such that we have copies of all old facets + // within the range of this proc's nodes + prepare_to_compute_distance( deltaTime, selector ); + + // compute nodal distances with semi-lagrangian step + stk::mesh::field_copy(my_old_distance_field, my_distance_field); + compute_distance_semilagrangian( deltaTime, selector ); + + // build local facet list + build_facets_locally(my_meta.universal_part()); + + // debugging + if (krinolog.shouldPrint(LOG_FACETS)) + { + facets_exoii(); + } + } +} + +//----------------------------------------------------------------------------------- +void +LevelSet::initialize(stk::mesh::MetaData & meta, const bool requires_additional_initialization) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::initialize(void)"); /* %TRACE% */ + + const LevelSetManager & region_ls = LevelSetManager::get(meta); + for (auto&& ls : region_ls) + { + ls->initialize(0., requires_additional_initialization); + } +} + +//----------------------------------------------------------------------------------- +void +LevelSet::initialize(const double time, const bool requires_additional_initialization) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::initialize(void)"); /* %TRACE% */ + + if (trackIsoSurface) + { + return; + } + + if(get_isovar_field().valid()) get_isovar_field().field().sync_to_host(); + if(get_distance_field().valid()) get_distance_field().field().sync_to_host(); + if(get_old_distance_field().valid()) get_old_distance_field().field().sync_to_host(); + + if(get_isovar_field().valid()) get_isovar_field().field().modify_on_host(); + if(get_distance_field().valid()) get_distance_field().field().modify_on_host(); + if(get_old_distance_field().valid()) get_old_distance_field().field().modify_on_host(); + + if (!my_compute_surface_distance_parts.empty()) + { + compute_surface_distance(); + return; + } + + krinolog << "Initializing levelset " << name() << "..." << stk::diag::dendl; + + /* process analytic surfaces */ + if (my_IC_alg) + { + my_IC_alg->execute(time, requires_additional_initialization); + } + + if (compute_time_of_arrival()) + { + fast_marching_redistance(my_meta.universal_part(), true); + } + else if (my_perform_initial_redistance) + { + constrained_redistance(); + } + + // Offset initialized LS if requested + if (my_ic_offset != 0.0) increment_distance(my_ic_offset, false); + + // Scale initialized LS if requested + if (my_ic_scale != 1.0) scale_distance(my_ic_scale); + + stk::mesh::field_copy(get_distance_field(), get_old_distance_field()); +} + +double LevelSet::constrained_redistance(const bool use_initial_vol) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::constrained_redistance(const bool use_initial_vol)"); /* %TRACE% */ + + // Steps: + // 1. measure current volume + // 2. perform regular redistance + // 3. find offset needed to conserve volume + // 4. increment nodal distance by this amount + + if (get_isovar_field().valid()) get_isovar_field().field().sync_to_host(); + if (get_distance_field().valid()) get_distance_field().field().sync_to_host(); + if (get_old_distance_field().valid()) get_old_distance_field().field().sync_to_host(); + + if (get_isovar_field().valid()) get_isovar_field().field().modify_on_host(); + if (get_distance_field().valid()) get_distance_field().field().modify_on_host(); + if (get_old_distance_field().valid()) get_old_distance_field().field().modify_on_host(); + + // measure current area and volumes + double start_area, start_neg_vol, start_pos_vol; + compute_sizes( start_area,start_neg_vol, start_pos_vol, 0. ); + if ( 0. == start_neg_vol || 0. == start_area ) + { + krinolog << "Skipping redistancing operation. Volume and/or area have zero value. Area: " + << start_area << " Volume: " << start_neg_vol << stk::diag::dendl; + return 0.; + } + + if (use_initial_vol) + { + krinolog << "Performing conserved redistancing..." << stk::diag::dendl; + + if (my_initial_neg_vol <= 0.0) + { + my_initial_neg_vol = start_neg_vol; + } + start_pos_vol = start_neg_vol + start_pos_vol - my_initial_neg_vol; + start_neg_vol = my_initial_neg_vol; + } + else + { + krinolog << "Performing constrained redistancing..." << stk::diag::dendl; + } + + // perform regular redistance + redistance(); + + krinolog << "Correcting for volume change:" << stk::diag::dendl; + + // find correction needed to conserve volume + const double correction = find_redistance_correction( start_area, start_neg_vol, start_pos_vol ); + + // update nodal distance field + increment_distance( -correction, true ); + + return my_initial_neg_vol; +} + +//-------------------------------------------------------------------------------- + +static std::function(const double)> build_volume_error_function_with_derivative(LevelSet & ls, const double startingNegVol, const double startingPosVol) +{ + auto volume_error_function_with_derivative = [&ls, startingNegVol, startingPosVol](const double x) + { + double area, negVol, posVol; + ls.compute_sizes( area, negVol, posVol, x ); + const double totVol = startingNegVol+startingPosVol; + const double relativeError = (negVol - startingNegVol) / totVol; + krinolog << " Correction = " << x + << ", Current volume = " << negVol + << ", Target volume = " << startingNegVol + << ", Relative Error = " << std::abs(relativeError) + << stk::diag::dendl; + const double derivative = area/totVol; + return std::make_pair(relativeError, derivative); + }; + return volume_error_function_with_derivative; +} + +double +LevelSet::find_redistance_correction( const double start_area, + const double start_neg_vol, + const double start_pos_vol, + const int max_iterations, + const double tol ) +{ /* %TRACE% */ /* %TRACE% */ + auto volume_error_function_with_derivative = build_volume_error_function_with_derivative(*this, start_neg_vol, start_pos_vol); + const auto result = find_root_newton_raphson(volume_error_function_with_derivative, 0., max_iterations, tol); + + if (!result.first) + { + stk::RuntimeWarningAdHoc() << "\nConstrained renormalization failed to converge to root within " + << max_iterations << " iterations. Continuing with correction " << result.second << "\n"; + } + return result.second; +} + +void +LevelSet::redistance() +{ + redistance(my_meta.universal_part()); +} + +void +LevelSet::redistance(const stk::mesh::Selector & selector) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::redistance(void)"); /* %TRACE% */ + + ThrowErrorMsgIf(!my_time_of_arrival_element_speed_field_name.empty(), "Redistancing a time-of-arrival field will corrupt it."); + + if (get_isovar_field().valid()) get_isovar_field().field().sync_to_host(); + if (get_distance_field().valid()) get_distance_field().field().sync_to_host(); + if (get_old_distance_field().valid()) get_old_distance_field().field().sync_to_host(); + + if (get_isovar_field().valid()) get_isovar_field().field().modify_on_host(); + if (get_distance_field().valid()) get_distance_field().field().modify_on_host(); + if (get_old_distance_field().valid()) get_old_distance_field().field().modify_on_host(); + + if (FAST_MARCHING == my_redistance_method) + { + fast_marching_redistance(selector); + return; + } + ThrowRequire(CLOSEST_POINT == my_redistance_method); + + krinolog << "Redistancing the level set field..." << stk::diag::dendl; + + // our starting point is a nodal variable (like distance or temperature) + // that needs to be contoured to form the surface + // after forming the surface, the nodal distance needs to be calculated + // the newly formed surface should be remain in the vector facets + build_facets_locally(selector); + + // debugging + if (krinolog.shouldPrint(LOG_FACETS)) + { + facets_exoii(); + } + + // swap these facets into facet_old to take advantage of routines + // that are expecting the facets there + facets->swap( *facets_old ); + + // get non-local facets such that we have copies of all "old" facets + // within the range of this proc's nodes + prepare_to_compute_distance( 0., selector ); + + // compute nodal distances with semi-lagrangian step + compute_distance_semilagrangian( 0., selector ); + + // swap so that the facets that were formed remain in the vector facets + facets->swap( *facets_old ); + +} + +double +LevelSet::get_time_of_arrival_speed(stk::mesh::Entity elem, ParallelErrorMessage& err) const +{ + double speed = 1.0; + if (myTimeOfArrivalBlockSpeeds.empty()) + { + if (myTimeOfArrivalElementSpeedField.valid()) + { + const double * speedFieldData = field_data(myTimeOfArrivalElementSpeedField, elem); + if (!speedFieldData) err << "Missing element speed on element " << mesh().identifier(elem) << "\n"; + else speed = *speedFieldData; + + if (speed <= 0.0) + { + err << "Non positive-definite speed " << speed << " found on element " << mesh().identifier(elem) << "\n"; + } + } + } + else + { + const stk::mesh::Part & elemPart = find_element_part(mesh(), elem); + speed = myTimeOfArrivalBlockSpeeds[elemPart.mesh_meta_data_ordinal()]; + ThrowAssert(speed >= 0.0); // Negative speeds should have already been caught and generated error. + if (speed == 0.0) + err << "Speed not specified for block " << elemPart.name() << "\n"; + } + + return speed; +} + +void +LevelSet::fast_marching_redistance(const stk::mesh::Selector & selector, const bool compute_time_of_arrival) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::fast_marching_redistance(const stk::mesh::Selector & selector)"); /* %TRACE% */ + + // Unlike redistance() this method provides an approximate (not exact) distance to the isosurface. + // On the other hand, this method provide solve the Eikonal equation on the mesh. This is slightly + // different than a pure distance function because it provides the distance through domain. It can't see + // through walls like the redistance() method does. + + if (compute_time_of_arrival) krinolog << "Initializing the level set field to be the time-of-arrival using a fast marching method..." << stk::diag::dendl; + else krinolog << "Redistancing the level set field using a fast marching method..." << stk::diag::dendl; + Fast_Marching fm(*this, selector, get_timer()); + fm.redistance(); +} + +//-------------------------------------------------------------------------------- + +void +LevelSet::set_distance( const double & in_distance ) const +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::set_distance( const double & in_distance ) const"); /* %TRACE% */ + + stk::mesh::field_fill(in_distance, get_distance_field()); +} + +void +LevelSet::increment_distance( const double increment, const bool enforce_sign ) const +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::increment_distance( const double & increment ) const"); /* %TRACE% */ + // + // increment the distance everywhere by the given value + // + + const FieldRef dField = get_distance_field(); + + const stk::mesh::Selector active_field_selector = aux_meta().active_not_ghost_selector() & stk::mesh::selectField(dField); + stk::mesh::BucketVector const& buckets = mesh().get_buckets( stk::topology::NODE_RANK, active_field_selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + + const size_t length = b.size(); + + double *d = field_data(dField , b); + + for (size_t i = 0; i < length; ++i) + { + const double change = (enforce_sign && sign_change(d[i],d[i]+increment)) ? -0.5*d[i] : increment; + d[i] += change; + } + } // end bucket loop +} + +void +LevelSet::scale_distance( const double scale) const +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::scale_distance( const double & scale ) const"); /* %TRACE% */ + // + // increment the distance everywhere by the given value + // + + const FieldRef dField = get_distance_field(); + + const stk::mesh::Selector active_field_selector = aux_meta().active_not_ghost_selector() & stk::mesh::selectField(dField); + stk::mesh::BucketVector const& buckets = mesh().get_buckets( stk::topology::NODE_RANK, active_field_selector); + + stk::mesh::BucketVector::const_iterator ib = buckets.begin(); + stk::mesh::BucketVector::const_iterator ib_end = buckets.end(); + + // iterate nodes, by buckets, and set distance + for ( ; ib != ib_end ; ++ib ) + { + const stk::mesh::Bucket & b = **ib; + + const size_t length = b.size(); + + double *d = field_data(dField , b); + + for (size_t i = 0; i < length; ++i) + { + d[i] *= scale; + } + } // end bucket loop +} + +void +LevelSet::negate_distance() const +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::increment_distance( const double & increment ) const"); /* %TRACE% */ + + const FieldRef dRef = get_distance_field(); + stk::mesh::field_scale(-1.0, dRef); +} + +//----------------------------------------------------------------------------------- + +void +LevelSet::compute_continuous_gradient() const +{ /* %TRACE[ON]% */ /* %TRACE% */ + // + // Compute a mass-lumped continuous distance gradient + // + + const int dim = mesh().mesh_meta_data().spatial_dimension(); + + const FieldRef contGradRef = aux_meta().get_field(stk::topology::NODE_RANK, "CONT_GRAD"); + const FieldRef nodeAreaRef = aux_meta().get_field(stk::topology::NODE_RANK, "NODE_AREA"); + + + // initialize + stk::mesh::field_fill(0.0, contGradRef); + stk::mesh::field_fill(0.0, nodeAreaRef); + + // intg_pt_locations and intg_weights are just wrappers for framework data + // determinants and grad_distance are arrays containing data that are resized + // only as needed + sierra::Array intg_pt_locations; + sierra::Array intg_weights; + sierra::ArrayContainer determinants; + sierra::ArrayContainer grad_distance; + + // ************************** + // Not-covered-in-nightly RWH + // ************************** + + const FieldRef xField = get_coordinates_field(); + const FieldRef isoField = get_isovar_field(); + + stk::mesh::Selector active_field_selector = stk::mesh::selectField(contGradRef) & aux_meta().active_not_ghost_selector(); + std::vector< stk::mesh::Entity> objs; + stk::mesh::get_selected_entities( active_field_selector, mesh().buckets( stk::topology::ELEMENT_RANK ), objs ); + + for ( auto && elem : objs ) + { + // create element + ContourElement ls_elem( mesh(), elem, xField, isoField ); + + ls_elem.std_intg_pts( intg_pt_locations, intg_weights, determinants, + ls_elem.dist_master_elem() ); + ls_elem.compute_distance_gradient( intg_pt_locations, grad_distance ); + + const unsigned num_intg_pts = intg_pt_locations.dimension(); + const int npe = ls_elem.dist_topology().num_nodes(); + + const double * shape_fcn_ptr = ls_elem.dist_master_elem().shape_fcn(); + const sierra::Array shape_fcn(shape_fcn_ptr,npe,num_intg_pts); + + const stk::mesh::Entity* elem_nodes = mesh().begin_nodes(elem); + + for ( int i = 0; i < npe; ++i ) + { + double * cont_grad = field_data(contGradRef, elem_nodes[i]); + double * area_ptr = field_data(nodeAreaRef, elem_nodes[i]); + double & area = *area_ptr; + + for (unsigned ip = 0; ip < num_intg_pts; ++ip ) + { + const double NdV = shape_fcn(i,ip) * intg_weights(ip) * determinants(ip); + area += NdV; + + for ( int d = 0; d < dim; d++ ) + { + cont_grad[d] += grad_distance(d,ip) * NdV; + //krinolog << "grad_distance(" << d << "," << ip << ") = " << grad_distance(d,ip) << stk::diag::dendl; + } + } + } + } + + // iterate nodes, by buckets, and calculate average gradient + + stk::mesh::BucketVector const& buckets = mesh().get_buckets(stk::topology::NODE_RANK, active_field_selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + + const size_t length = b.size(); + + double * cont_grad = field_data(contGradRef, b); + double * area = field_data(nodeAreaRef, b); + + for (size_t i = 0; i < length; ++i) + { + for ( int d = 0; d < dim; d++ ) + { + cont_grad[dim*i+d] = cont_grad[dim*i+d] / area[i]; + } + } + } +} + +//----------------------------------------------------------------------------------- + +void +LevelSet::compute_nodal_bbox( const stk::mesh::Selector & selector, + BoundingBox & node_bbox, + const Vector3d & displacement ) const +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::compute_nodal_bbox( BoundingBox & node_bboxes, const double & deltaTime ) const"); /* %TRACE% */ + + // find the local nodal bounding box + + const FieldRef xField = get_coordinates_field(); + const FieldRef dField = get_distance_field(); + + const stk::mesh::Selector active_field_selector = aux_meta().active_not_ghost_selector() & selector & stk::mesh::selectField(dField); + stk::mesh::BucketVector const& buckets = mesh().get_buckets( stk::topology::NODE_RANK, active_field_selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + + const size_t length = b.size(); + + double *x = field_data(xField, b); + + for (size_t i = 0; i < length; ++i) + { + + Vector3d x_bw(Vector3d::ZERO); + for ( unsigned dim = 0; dim < spatial_dimension; ++dim ) + { + int index = i*spatial_dimension+dim; + x_bw[dim] = x[index] - displacement[dim]; + } + + // incrementally size bounding box + node_bbox.accommodate( x_bw ); + } + } // end bucket loop +} + +//----------------------------------------------------------------------------------- + +void +LevelSet::prepare_to_compute_distance( const double & deltaTime, const stk::mesh::Selector & selector ) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::get_nonlocal_facets( const double & deltaTime )"); /* %TRACE% */ + + // Get all of the facets that are within our processor's bounding box + // To do this, see if any local facets lie in the nodal bounding box + // of another proc. if so, send them a copy of those facets + + // First, find the bounding box for each proc that contains all of the + // nodes on that proc plus the narrow_band size + + BoundingBox node_bbox; + const Vector3d displacement = deltaTime * get_extension_velocity(); + compute_nodal_bbox( selector, node_bbox, displacement ); + + facets_old->prepare_to_compute(node_bbox, my_narrow_band_size); +} + +//----------------------------------------------------------------------------------- + +void +LevelSet::compute_distance_semilagrangian( const double & deltaTime, const stk::mesh::Selector & selector ) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::compute_distance_semilagrangian( const double & deltaTime ) const"); /* %TRACE% */ + + const double h_avg = compute_average_edge_length(); + + const FieldRef xField = get_coordinates_field(); + const FieldRef dField = get_distance_field(); + const Vector3d extv = get_extension_velocity(); + + const stk::mesh::Selector active_field_selector = aux_meta().active_not_ghost_selector() & selector & stk::mesh::selectField(dField); + stk::mesh::BucketVector const& buckets = mesh().get_buckets(stk::topology::NODE_RANK, active_field_selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + + const size_t length = b.size(); + + double *d = field_data( dField , b); + double *x = field_data( xField , b); + + // Handle special case of ( deltaTime == 0. ) so that we + // can handle situation where velocity is not defined at all + // nodes where distance is defined. (This is currently a requirement + // for regular semilagrangian advancement). + if ( deltaTime == 0. ) + { + for (size_t i = 0; i < length; ++i) + { + Vector3d x_node(Vector3d::ZERO); + for ( unsigned dim = 0; dim < spatial_dimension; ++dim ) + { + int index = i*spatial_dimension+dim; + x_node[dim] = x[index]; + } + + int previous_sign = LevelSet::sign(d[i]); + // If this is too large, then sharp edges can propagate incorrect signs + // (even through walls, etc). + // If this is too small, then a phase can't disappear because the sign + // preservation will prevent it even if the subelement contouring process + // neglects it. So this should be slightly larger than the tolerance in + // compute_subelement_decomposition. + bool enforce_sign = (std::abs(d[i]) > 5.e-4*h_avg); + + d[i] = distance( x_node, previous_sign, enforce_sign ); + } + } + else + { + for (size_t i = 0; i < length; ++i) + { + Vector3d x_bw(Vector3d::ZERO); + for ( unsigned dim = 0; dim < spatial_dimension; ++dim ) + { + int index = i*spatial_dimension+dim; + x_bw[dim] = x[index] - extv[dim] * deltaTime; + } + + int previous_sign = LevelSet::sign(d[i]); + + d[i] = distance( x_bw, previous_sign, false ); + } + } + } // end bucket loop +} + +//----------------------------------------------------------------------------------- + +void +LevelSet::compute_distance( stk::mesh::Entity n, + const double & deltaTime ) const +{ /* %TRACE% */ /* %TRACE% */ + + // use the facet cell array to compute the distance to a node n + + const FieldRef xField = get_coordinates_field(); + const FieldRef dField = get_distance_field(); + const Vector3d extv = get_extension_velocity(); + + double *x = field_data( xField , n); + double *d = field_data( dField , n); + + // Handle special case of ( deltaTime == 0. ) so that we + // can handle situation where velocity is not defined at all + // nodes where distance is defined. (This is currently a requirement + // for regular semilagrangian advancement). + if ( deltaTime == 0. ) + { + Vector3d x_node(Vector3d::ZERO); + for ( unsigned dim = 0; dim < spatial_dimension; ++dim ) + { + x_node[dim] = x[dim]; + } + + int previous_sign = LevelSet::sign(*d); + *d = distance( x_node, previous_sign, true ); + } + else + { + Vector3d x_bw(Vector3d::ZERO); + for ( unsigned dim = 0; dim < spatial_dimension; ++dim ) + { + x_bw[dim] = x[dim] - extv[dim] * deltaTime; + } + + int previous_sign = LevelSet::sign(*d); + *d = distance( x_bw, previous_sign, false ); + } +} + +//----------------------------------------------------------------------------------- + +double +LevelSet::distance( const Vector3d & x, + const int previous_sign, + const bool enforce_sign ) const +{ /* %TRACE% */ /* %TRACE% */ + + if (enforce_sign) + { + return previous_sign * facets_old->point_unsigned_distance(x, my_narrow_band_size, my_narrow_band_size); + } + return facets_old->truncated_point_signed_distance(x, my_narrow_band_size, previous_sign*my_narrow_band_size); +} + +//----------------------------------------------------------------------------------- + +void +LevelSet::snap_to_mesh() const +{ /* %TRACE[ON]% */ Trace trace__("LevelSet::snap_to_mesh()"); /* %TRACE% */ + const double tol = 1.0e-2; + + // Remove sliver subelements by setting nodal values near zero to zero exactly. + // This should probably be an edge-based check. But this poses a problem for higher order + // elements, which we are going to decompose into lower order elements. So we make it + // simpler by compare ALL pairs of nodes within the element. If the crossing between any + // pair of nodes is is within a relative distance of tol, the distance at the nearest node + // is set to zero. + + // This seems like a great way to consistently handle degeneracies. + // One problem, however, is this only handles the near zero's on the original elements. + // We will generate others as we decompose into non-conformal subelements. This won't fix + // those degenerate situations. + + std::vector dist; + + const FieldRef dField = get_distance_field(); + + const stk::mesh::Selector active_field_selector = stk::mesh::selectField(dField) & aux_meta().active_locally_owned_selector(); + stk::mesh::BucketVector const& buckets = mesh().get_buckets(stk::topology::ELEMENT_RANK, active_field_selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + + const stk::topology dist_topology = MasterElementDeterminer::get_field_topology(b, dField); + const int npe_dist = dist_topology.num_nodes(); + dist.resize( npe_dist ); + + const size_t length = b.size(); + for (size_t i = 0; i < length; ++i) + { + stk::mesh::Entity elem = b[i]; + + if (!elem_on_interface(elem)) continue; + + const stk::mesh::Entity* elem_nodes = mesh().begin_nodes(elem); + + for ( int n = 0; n < npe_dist; ++n ) + { + dist[n] = *field_data(dField, elem_nodes[n]); + } + + for ( int n = 0; n < npe_dist; ++n ) + { + for ( int np = n+1; np < npe_dist; ++np ) + { + if (sign_change(dist[n],dist[np])) + { + const double d0 = std::fabs(dist[n]); + const double d1 = std::fabs(dist[np]); + if (d0 < d1) + { + if (d0 / (d0+d1) < tol) + { + *field_data(dField, elem_nodes[n]) = 0.0; + } + } + else + { + if (d1 / (d1+d0) < tol) + { + *field_data(dField, elem_nodes[np]) = 0.0; + } + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------------- + +bool +LevelSet::remove_wall_features() const +{ /* %TRACE[ON]% */ Trace trace__("LevelSet::remove_wall_features()"); /* %TRACE% */ + + std::vector dist; + std::vector coords; + int small_feature_removed = false; + + const FieldRef dField = get_distance_field(); + const FieldRef coordinates_field = get_coordinates_field(); + + stk::mesh::Selector surface_selector = selectUnion(my_surface_parts); + + const stk::mesh::Selector active_field_selector = stk::mesh::selectField(dField) & aux_meta().active_locally_owned_selector() & surface_selector; + stk::mesh::BucketVector const& buckets = mesh().get_buckets(meta().side_rank(), active_field_selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + + const stk::topology dist_topology = MasterElementDeterminer::get_field_topology(b, dField); + const int npe_dist = dist_topology.num_nodes(); + dist.resize( npe_dist ); + coords.resize( npe_dist ); + + const size_t length = b.size(); + for (size_t i = 0; i < length; ++i) + { + stk::mesh::Entity side = b[i]; + + const stk::mesh::Entity* side_nodes = mesh().begin_nodes(side); + + for ( int n = 0; n < npe_dist; ++n ) + { + dist[n] = *field_data(dField, side_nodes[n]); + + double *coord_data = field_data(coordinates_field,side_nodes[n]); + coords[n] = coord_data; + } + + for ( int n = 0; n < npe_dist; ++n ) + { + const stk::mesh::Entity *elems = mesh().begin_elements(side); //get the element + const int num_elems = mesh().num_elements(side); + + for (int l = 0; l < num_elems; ++l ) + { + const stk::mesh::Entity elem = elems[l]; + + //make sure we have the right element (active) and has the distance function + //at every point in the element (to take gradient) + + if(!aux_meta().active_locally_owned_selector()(mesh().bucket(elem))) continue; + if(!elem_has_field_data(dField, elem)) continue; + + //const double elem_len = calc_elem_len_normal(elem, side, coordinates_field); + + if(std::fabs(dist[n]) > my_max_feature_size) continue; + + ContourElement ls_elem( mesh(), elem, coordinates_field, dField ); + const Vector3d p_coords(1/3., 1/3., 1/3.); + const Vector3d grad_dist_vec = ls_elem.distance_gradient(p_coords); + + Vector3d face_normal; + + //assume linear tet or tri elements! + if(spatial_dimension == 2) + { + face_normal = {-coords[1][1]+coords[0][1], coords[1][0]-coords[0][0], 0}; //simple 90 deg rotation + const stk::mesh::Entity* elem_nodes = mesh().begin_nodes(elem); + + int num_elem_nodes = mesh().num_nodes(elem); + + for (int j = 0; j < num_elem_nodes; ++j) //find node in elem not part of side part, confirm normal points into elem + { + if(elem_nodes[j] != side_nodes[0] && elem_nodes[j] != side_nodes[spatial_dimension-1]) + { + double *coord = field_data(coordinates_field,elem_nodes[j]); + const Vector3d vec_check(coord[0]-coords[0][0], coord[1]-coords[0][1], 0); + + if(Dot(face_normal, vec_check) < 0) + { + face_normal *= -1; + break; + } + } + } + } + else + { + const Vector3d x1 (coords[1][0]-coords[0][0], coords[1][1]-coords[0][1],coords[1][2]-coords[0][2]); + const Vector3d x2 (coords[2][0]-coords[0][0], coords[2][1]-coords[0][1],coords[2][2]-coords[0][2]); + face_normal = -1.0*Cross(x1,x2); + } + + face_normal.unitize(); + const double eta = -dist[n]/(Dot(grad_dist_vec, face_normal)); + + if(eta < my_max_feature_size && eta > 0) + { + *field_data(dField, side_nodes[n]) = -1.0 * dist[n]; + small_feature_removed = true; + } + } + } + } + } + + int global_small_feature_removed = false; + stk::all_reduce_sum(mesh().parallel(), &small_feature_removed, &global_small_feature_removed, 1); + + if (global_small_feature_removed) + { + stk::mesh::communicate_field_data(mesh(), {&dField.field()}); + return true; + } + + return false; +} +//-------------------------------------------------------------------------------- + +bool +LevelSet::simple_remove_wall_features() const +{ /* %TRACE[ON]% */ Trace trace__("LevelSet::remove_wall_features()"); /* %TRACE% */ + + int small_feature_removed = false; + const FieldRef dField = get_distance_field(); + + stk::mesh::Selector surface_selector = selectUnion(my_surface_parts); + + const stk::mesh::Selector active_field_selector = stk::mesh::selectField(dField) & aux_meta().active_part() & surface_selector; + stk::mesh::BucketVector const& buckets = mesh().get_buckets(stk::topology::NODE_RANK, active_field_selector); + + for ( auto && bucket : buckets ) + { + const stk::mesh::Bucket & b = *bucket; + const size_t length = b.size(); + for (size_t i = 0; i < length; ++i) + { + stk::mesh::Entity node = b[i]; + double & dist = *field_data(dField, node); + + if(std::fabs(dist) < my_max_feature_size) + { + dist*=-1; + small_feature_removed = true; + } + } + } + + return small_feature_removed; +} +//-------------------------------------------------------------------------------- + +void +LevelSet::set_surface_parts_vector() +{ + Phase_Support my_phase_support = Phase_Support::get(meta()); + + std::vector conformal_parts = my_phase_support.get_conformal_parts(); + + krinolog << "Removing small features less than " << my_max_feature_size << " on surfaces: "; + for (auto && mypart : conformal_parts) + { + if(mypart->primary_entity_rank() == meta().side_rank() && !my_phase_support.is_interface(mypart)) + { + my_surface_parts.push_back(mypart); + krinolog << mypart->name() << " "; + } + } + + krinolog << stk::diag::dendl; +} + +//-------------------------------------------------------------------------------- + +bool +LevelSet::elem_has_field_data(const FieldRef &myField, const stk::mesh::Entity &elem) const +{ + const stk::mesh::Entity* elem_nodes = mesh().begin_nodes(elem); + int num_nodes = mesh().num_nodes(elem); + + for (int k = 0; k < num_nodes; ++k) + { + if(!has_field_data(myField, elem_nodes[k])) return false; + } + + return true; +} +//-------------------------------------------------------------------------------- + +bool +LevelSet::elem_on_interface(stk::mesh::Entity e) const +{ /* %TRACE% */ /* %TRACE% */ + + const FieldRef isoField = get_isovar_field(); + + const unsigned nnodes = mesh().num_nodes(e); + ThrowAssert( 0 < nnodes ); + const stk::mesh::Entity* nodes = mesh().begin_nodes(e); + + // guilty till proven innocent here + bool on_interface = false; + bool have_crossing = false; + + double *d = field_data(isoField, nodes[0]); + double first_value = *d - my_threshold; + bool inside_narrow_band = fabs(first_value) < 0.5*my_narrow_band_size; + + for (unsigned i = 1; i < nnodes; ++i ) + { + d = field_data(isoField, nodes[i]); + if ( NULL == d ) continue; // account for lower order interpolation + double value = *d - my_threshold; + + have_crossing |= sign_change(value, first_value); + inside_narrow_band |= (fabs(value) < 0.5*my_narrow_band_size); + } + + // It is the user's job to make sure that the narrow band is sufficiently + // large that we don't to test if this crossing is within the narrow band + if ( have_crossing ) + on_interface = true; + + return on_interface; +} + +//-------------------------------------------------------------------------------- +void +LevelSet::compute_sizes( double & area, double & neg_vol, double & pos_vol, const double iso_val ) +{ /* %TRACE[ON]% */ /* %TRACE% */ + + // initialize + area = 0.0; + neg_vol = 0.0; + pos_vol = 0.0; + + const double h_avg = compute_average_edge_length(); + + sierra::ArrayContainer intg_pt_locations; + sierra::ArrayContainer intg_weights; + sierra::ArrayContainer determinants; + + const FieldRef xField = get_coordinates_field(); + const FieldRef isoField = get_isovar_field(); + //const Vector3d extv = get_extension_velocity(); + + stk::mesh::Selector active_field_selector = stk::mesh::selectField(isoField) & aux_meta().active_locally_owned_selector(); + std::vector< stk::mesh::Entity> objs; + stk::mesh::get_selected_entities(active_field_selector, mesh().buckets( stk::topology::ELEMENT_RANK ), objs); + + for ( auto && elem : objs ) + { + // create element that is decomposed into subelements + ContourElement ls_elem( mesh(), elem, xField, isoField, iso_val ); + + ls_elem.compute_subelement_decomposition(h_avg); + + // get integration point locations, weights, and determinants for surface + int num_intg_pts = ls_elem.gather_intg_pts( 0, // interface + intg_pt_locations,// gauss pt locations + intg_weights, // integration wts at gauss pts + determinants, // determinant at gauss pts + true ); // map_to_real_coords + for ( int ip = 0; ip < num_intg_pts; ++ip ) + { + area += intg_weights(ip) * determinants(ip); + } + + // get integration point locations, weights, and determinants for negative volume + num_intg_pts = ls_elem.gather_intg_pts( -1, // negative side of interface + intg_pt_locations, // gauss pt locations + intg_weights, // integration wts at gauss pts + determinants, // determinant at gauss pts + true ); // map_to_real_coords + for ( int ip = 0; ip < num_intg_pts; ++ip ) + { + neg_vol += intg_weights(ip) * determinants(ip); + } + + // get integration point locations, weights, and determinants for positive volume + num_intg_pts = ls_elem.gather_intg_pts( 1, // positive side of interface + intg_pt_locations, // gauss pt locations + intg_weights, // integration wts at gauss pts + determinants, // determinant at gauss pts + true ); // map_to_real_coords + for ( int ip = 0; ip < num_intg_pts; ++ip ) + { + pos_vol += intg_weights(ip) * determinants(ip); + } + } + + // communicate global sums + const int vec_length = 3; + std::vector local_sum( vec_length ); + std::vector global_sum( vec_length ); + local_sum[0] = area; + local_sum[1] = neg_vol; + local_sum[2] = pos_vol; + + stk::all_reduce_sum(mesh().parallel(), &local_sum[0], &global_sum[0], vec_length); + + area = global_sum[0]; + neg_vol = global_sum[1]; + pos_vol = global_sum[2]; + +} +//-------------------------------------------------------------------------------- +double +LevelSet::gradient_magnitude_error(void) +{ /* %TRACE[ON]% */ /* %TRACE% */ + + double area = 0., sum_L2 = 0., global_L2 = 0., local_Loo = 0., global_Loo = 0.; + + sierra::ArrayContainer intg_pt_locations; + sierra::ArrayContainer intg_weights; + sierra::ArrayContainer determinants; + sierra::ArrayContainer grad_dist; + + const double h_avg = compute_average_edge_length(); + + const FieldRef xField = get_coordinates_field(); + const FieldRef isoField = get_isovar_field(); + //const Vector3d extv = get_extension_velocity(); + + stk::mesh::Selector active_field_selector = stk::mesh::selectField(isoField) & aux_meta().active_locally_owned_selector(); + std::vector< stk::mesh::Entity> objs; + stk::mesh::get_selected_entities( active_field_selector, mesh().buckets( stk::topology::ELEMENT_RANK ), objs ); + + for ( auto && elem : objs ) + { + // create element that is decomposed into subelements + ContourElement ls_elem( mesh(), elem, xField, isoField ); + + ls_elem.compute_subelement_decomposition(h_avg); + + // get integration point locations, weights, and determinants for surface + int num_intg_pts = ls_elem.gather_intg_pts( 0, // interface + intg_pt_locations,// gauss pt locations + intg_weights, // integration wts at gauss pts + determinants, // determinant at gauss pts + true ); // map_to_real_coords + + ls_elem.compute_distance_gradient( intg_pt_locations, grad_dist ); + for ( int ip = 0; ip < num_intg_pts; ++ip ) + { + double mag_grad_phi = 0.; + for ( unsigned dim = 0; dim < spatial_dimension; ++dim ) mag_grad_phi += grad_dist(dim,ip) * grad_dist(dim,ip); + mag_grad_phi = sqrt(mag_grad_phi); + + sum_L2 += (mag_grad_phi - 1.) * (mag_grad_phi - 1.) * intg_weights(ip) * determinants(ip); + area += intg_weights(ip) * determinants(ip); + + if ( fabs(mag_grad_phi - 1.) > local_Loo ) local_Loo = fabs(mag_grad_phi - 1.); + } + } + + // communicate global norms + const int vec_length = 2; + std::vector local_sum( vec_length ); + std::vector global_sum( vec_length ); + local_sum[0] = sum_L2; + local_sum[1] = area; + + stk::all_reduce_sum(mesh().parallel(), &local_sum[0], &global_sum[0], vec_length); + stk::all_reduce_max(mesh().parallel(), &local_Loo, &global_Loo, 1); + + if ( global_sum[1] > 0. ) + { + global_L2 = global_sum[0] / global_sum[1]; + } + + krinolog << "Gradient norm error for " << name() << ": L2 = " << global_L2 << ", Loo = " << global_Loo << stk::diag::dendl; + + // L2 is the standard now, maybe Loo would be better? + return global_L2; +} +//-------------------------------------------------------------------------------- +double +LevelSet::compute_average_edge_length() const +{ /* %TRACE[ON]% */ /* %TRACE% */ + + double sum_avg_edge_lengths = 0.0; + int num_elems = 0; + + const FieldRef xField = get_coordinates_field(); + const FieldRef isoField = get_isovar_field(); + + stk::mesh::Selector active_field_selector = stk::mesh::selectField(isoField) & aux_meta().active_locally_owned_selector(); + std::vector< stk::mesh::Entity> objs; + stk::mesh::get_selected_entities( active_field_selector, mesh().buckets( stk::topology::ELEMENT_RANK ), objs ); + + for ( auto && elem : objs ) + { + ++num_elems; + + // create element + ContourElement ls_elem( mesh(), elem, xField, isoField ); + + sum_avg_edge_lengths += ls_elem.average_edge_length(); + } + + // communicate global sums + const int vec_length = 2; + std::vector local_sum( vec_length ); + std::vector global_sum( vec_length ); + local_sum[0] = sum_avg_edge_lengths; + local_sum[1] = 1.0*num_elems; + + stk::all_reduce_sum(mesh().parallel(), &local_sum[0], &global_sum[0], vec_length); + + const double h_avg = ( global_sum[1] != 0.0 ) ? global_sum[0]/global_sum[1] : 0.0; + + return h_avg; +} + +//-------------------------------------------------------------------------------- + +void +LevelSet::build_facets_locally(const stk::mesh::Selector & selector) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::build_facets_locally(void)"); /* %TRACE% */ + + // clear vector of facets + facets->clear(); + + const double h_avg = compute_average_edge_length(); + + const FieldRef xField = get_coordinates_field(); + const FieldRef isoField = get_isovar_field(); + + stk::mesh::Selector active_field_selector = selector & stk::mesh::selectField(isoField) & aux_meta().active_locally_owned_selector(); + std::vector< stk::mesh::Entity> objs; + stk::mesh::get_selected_entities( active_field_selector, mesh().buckets( stk::topology::ELEMENT_RANK ), objs ); + + for ( auto && elem : objs ) + { + // create element that is decomposed into subelements + ContourElement ls_elem( mesh(), elem, xField, isoField ); + ls_elem.compute_subelement_decomposition(h_avg); + + ls_elem.build_subelement_facets( *facets ); + } +} + +LevelSet & +LevelSet::build( + stk::mesh::MetaData & in_meta, + const std::string & ls_name, + stk::diag::Timer & parent_timer ) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::build(stk::mesh::MetaData & in_meta, const std::string & ls_name, stk::diag::Timer & parent_timer)"); /* %TRACE% */ + LevelSetManager & region_ls = LevelSetManager::get(in_meta); + + ThrowRequire(!region_ls.has_levelSet(ls_name)); + LevelSet * ls = new LevelSet(in_meta, ls_name, parent_timer); + region_ls.add(ls); + return *ls; +} + +//-------------------------------------------------------------------------------- +LevelSet::LevelSet( + stk::mesh::MetaData & in_meta, + const std::string & in_name, + stk::diag::Timer & parent_timer ) : + my_meta(in_meta), + my_aux_meta(AuxMetaData::get(in_meta)), + my_identifier(LevelSet::get_identifier(in_name)), + my_name(LevelSet::get_name(my_identifier)), + my_parent_timer(parent_timer), + my_timer("LevelSet", parent_timer), + spatial_dimension(in_meta.spatial_dimension()), + my_narrow_band_multiplier(0.0), + my_narrow_band_size(0.0), + my_max_feature_size(-1.0), + my_ic_offset(0.0), + my_ic_scale(1.0), + my_perform_initial_redistance(false), + my_keep_IC_surfaces(false), + my_threshold(0.0), + my_redistance_method(CLOSEST_POINT), + epsilon(1.0e-16), + trackIsoSurface(false), + my_facetFileIndex(1), + my_initial_neg_vol(0.0), + my_needs_reinitialize_every_step(false) +{ /* %TRACE[ON]% */ Trace trace__("krino::LevelSet::LevelSet(stk::mesh::MetaData & in_meta, const std::string & ls_name, stk::diag::Timer & parent_timer)"); /* %TRACE% */ + my_coordinates_field = get_current_coordinates(in_meta); + + // default names for distance and velocity + // line commands are available for overriding these names + const std::string distName = "D_" + name(); + set_distance_name(distName); +} + +LevelSet::~LevelSet() +{ +} + +//----------------------------------------------------------------------------------- +void +LevelSet::gather_nodal_field( + const stk::mesh::BulkData& stk_mesh, + stk::mesh::Entity obj, + const FieldRef & field, + double * gathered_field_data ) +{ /* %TRACE% */ /* %TRACE% */ + + int ncomp_field = field.length(); + + // Gather obj's nodal field into a single flat array + // of dimension (ncomp_field,num_nodes) + + int j = 0; + const unsigned num_nodes = stk_mesh.num_nodes(obj); + const stk::mesh::Entity* nodes = stk_mesh.begin_nodes(obj); + for (unsigned node_index=0; node_index(field, node); + for ( int i = 0; i < ncomp_field; ++i ) + { + gathered_field_data[j++] = var[i]; + } + } + + ThrowAssert( (unsigned)j == ncomp_field * stk_mesh.num_nodes(obj)); +} +//-------------------------------------------------------------------------------- +LevelSetManager & +LevelSetManager::get(stk::mesh::MetaData & meta) +{ + LevelSetManager * mgr = const_cast(meta.get_attribute()); + if (nullptr == mgr) + { + mgr = new LevelSetManager; + meta.declare_attribute_with_delete(mgr); + } + return *mgr; +} +//-------------------------------------------------------------------------------- +LevelSetManager & +LevelSetManager::get(const stk::mesh::MetaData & meta) +{ + LevelSetManager * mgr = const_cast(meta.get_attribute()); + ThrowRequireMsg(nullptr != mgr, "No LevelSetManager found for MetaData."); + return *mgr; +} +//-------------------------------------------------------------------------------- +bool LevelSetManager::has_levelSet(const std::string & ls_name) const +{ + for (auto&& ls : my_level_sets) + { + if (ls->name() == ls_name || ls->get_composite_name() == ls_name) + { + return true; + } + } + return false; +} +//-------------------------------------------------------------------------------- +std::string +print_sizes(const LevelSet & ls) +{ /* %TRACE[ON]% */ Trace trace__("krino::print_sizes(const LevelSet & levelSet)"); /* %TRACE% */ + + // + // find sizes of facets stored in vector of facet descriptions + // + + const auto & ls_surfaces = ls.get_facets().get_facets(); + unsigned local_facet_num = ls_surfaces.size(); + unsigned global_facet_num = 0; + stk::all_reduce_sum(ls.mesh().parallel(), &local_facet_num , &global_facet_num, 1); + + // iterate local facets and calc area + + double local_facets_totalArea = 0.0; // total surface area of interface from facets + double local_facets_maxArea = -1.0; // area of largest facet on interface + double local_facets_minArea = std::numeric_limits::max(); // area of smallest facet on interface + + // loop over facets + for ( auto&& surface : ls_surfaces ) + { + double area = surface->facet_area(); + local_facets_totalArea += area; + + local_facets_maxArea = std::min(area, local_facets_maxArea); + local_facets_minArea = std::max(area, local_facets_maxArea); + } + + double global_facets_totalArea = 0.0; + double global_facets_maxArea = 0.0; + double global_facets_minArea = 0.0; + + stk::all_reduce_min(ls.mesh().parallel(), &local_facets_minArea, &global_facets_minArea, 1); + stk::all_reduce_max(ls.mesh().parallel(), &local_facets_maxArea, &global_facets_maxArea, 1); + stk::all_reduce_sum(ls.mesh().parallel(), &local_facets_totalArea, &global_facets_totalArea, 1); + + std::ostringstream out; + + // facet info + out << "P" << ls.mesh().parallel_rank() << ": facets for level set '" << ls.name() << "' " << std::endl ; + out << "\t " << "Global sizes: { " << global_facet_num << " facets on }" << std::endl; + out << "\t " << "Local sizes: { " << local_facet_num << " facets on }" << std::endl; + out << "\t Global areas: { Min = " << global_facets_minArea << ", Max = " << global_facets_maxArea + << ", Total = " << global_facets_totalArea << " }" << std::endl; + return out.str(); +} +//-------------------------------------------------------------------------------- + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_LevelSet.hpp b/packages/krino/krino/krino_lib/Akri_LevelSet.hpp new file mode 100644 index 000000000000..609e13728396 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_LevelSet.hpp @@ -0,0 +1,339 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_LevelSet_h +#define Akri_LevelSet_h + +/** + + Create one of these for every interface you want to capture. + + */ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace stk { namespace mesh { class BulkData; } } +namespace stk { namespace mesh { class MetaData; } } +namespace sierra { namespace Sctl { class Event; } } +namespace krino { class AuxMetaData; } +namespace krino { class IC_Alg; } +namespace krino { class ParallelErrorMessage; } + +namespace krino { + +enum Redistance_Method +{ + CLOSEST_POINT=0, + FAST_MARCHING, + MAX_REDISTANCE_METHOD_TYPE +}; + +/// Return true if field-data exists for the specified meshobj and field. +bool all_nodes_have_field_data(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity entity, const stk::mesh::FieldBase& field); + +class LevelSet { +friend class LevelSet_Size; +public: + stk::mesh::MetaData & meta(); + const stk::mesh::MetaData & meta() const; + stk::mesh::BulkData & mesh(); + const stk::mesh::BulkData & mesh() const; + AuxMetaData & aux_meta(); + const AuxMetaData & aux_meta() const; + + const std::string & name() const { return my_name; } + LevelSet_Identifier get_identifier() const {return my_identifier; } + stk::diag::Timer & get_timer() const { return my_timer; } + stk::diag::Timer & get_parent_timer() const { return my_parent_timer; } + + // finalize setup + static void setup(stk::mesh::MetaData & meta); + static void post_commit_setup(stk::mesh::MetaData & meta); + virtual void setup(); + + static void set_current_coordinates(stk::mesh::MetaData & meta, const FieldRef ref); + static FieldRef get_current_coordinates(stk::mesh::MetaData & meta); + + void register_fields(); + + void advance_semilagrangian(const double deltaTime); + + static void gather_nodal_field( + const stk::mesh::BulkData& stk_mesh, + stk::mesh::Entity obj, + const FieldRef & field_ref, + double * field); + + double compute_average_edge_length() const; + + void build_facets_locally(const stk::mesh::Selector & selector); + + void compute_sizes( double & area, double & neg_vol, double & pos_vol, const double distance = 0.0 ); + double gradient_magnitude_error(); + void compute_continuous_gradient() const; + + void compute_distance( stk::mesh::Entity n, + const double & deltaTime ) const; + void estimate_error(); + + // hack to dump facet list to exoii databse. + void facets_exoii(); + void facets_exoii(Faceted_Surface & cs); + + bool elem_on_interface(stk::mesh::Entity e) const; + + void snap_to_mesh() const; + bool remove_wall_features() const; + bool simple_remove_wall_features() const; + bool elem_has_field_data(const FieldRef &myField, const stk::mesh::Entity &elem) const; + double calc_elem_len_normal(const stk::mesh::Entity &elem, const stk::mesh::Entity &side, const FieldRef &coordinates_field) const; + + + //-------------------------------------------------------------------------------- + // queries + //-------------------------------------------------------------------------------- + + const std::string & get_distance_name() const { return my_distance_name; } + void set_distance_name( const std::string & distance_name ) { my_distance_name = distance_name; } + + const FieldRef & get_distance_field() const { return my_distance_field; } + void set_distance_field( const FieldRef & ref ) { my_distance_field = ref; } + + const FieldRef & get_old_distance_field() const { return my_old_distance_field; } + void set_old_distance_field( const FieldRef & ref ) { my_old_distance_field = ref; } + + void set_extension_velocity( const Vector3d & extension_velocity ) { my_extension_velocity = extension_velocity; } + const Vector3d & get_extension_velocity() const { return my_extension_velocity; } + + const FieldRef & get_coordinates_field() const { return my_coordinates_field; } + + const std::string & get_isovar_name() const { return my_isovar_name; } + + const std::string & get_composite_name() const { return my_composite_name; } + void set_composite_name( const std::string & composite_name ) { + my_composite_name = composite_name; + std::transform(my_composite_name.begin(), my_composite_name.end(), my_composite_name.begin(), ::toupper); + } + + const FieldRef & get_isovar_field() const { return my_isovar_field; } + void set_isovar_field( const FieldRef & ref ) { my_isovar_field = ref; } + + double get_time_of_arrival_speed(stk::mesh::Entity elem, ParallelErrorMessage& err) const; + + void set_isovar(const std::string & isovar_name, const double isoval) { my_isovar_name = isovar_name; my_threshold = isoval; trackIsoSurface = true; } + const double & get_isoval() const { return my_threshold; } + + bool get_reinitialize_every_step() const { return my_needs_reinitialize_every_step; } + void set_reinitialize_every_step(const bool reinit) { set_keep_IC_surfaces(); my_needs_reinitialize_every_step = true; } + + Redistance_Method get_redistance_method() const { return my_redistance_method; } + void set_redistance_method( const Redistance_Method type ) { my_redistance_method = type; } + void set_time_of_arrival_element_speed_field_name( const std::string & time_of_arrival_speed_field_name) { my_time_of_arrival_element_speed_field_name = time_of_arrival_speed_field_name; } + void set_time_of_arrival_block_speed(const std::string & blockName, const double blockSpeed); + Faceted_Surface & get_facets() { return *facets; } + const Faceted_Surface & get_facets() const { return *facets; } + + static std::vector the_levelSet_names; + static LevelSet_Identifier get_identifier(const std::string & name); + static std::string & get_name(const LevelSet_Identifier identifier) { ThrowAssert(identifier.get() < the_levelSet_names.size()); return the_levelSet_names[identifier.get()]; } + + void narrow_band_multiplier( double multiplier ) { my_narrow_band_multiplier = multiplier; } + const double & narrow_band_size() const { return my_narrow_band_size; } + void narrow_band_size( double size ) { my_narrow_band_size = size; } // publicly deprecated, make private + + void max_feature_size( double size ) { my_max_feature_size= size; } + void use_simple_remove_feature( bool is_simple ) {my_use_simple_remove_feature = is_simple; } + + double max_feature_size() { return my_max_feature_size; } + bool use_simple_remove_feature() { return my_use_simple_remove_feature; } + + void set_surface_parts_vector(); + + void set_ic_offset (const double offset) { + my_ic_offset = offset; + } + void set_ic_scale (const double scale) { + my_ic_scale = scale; + } + void perform_initial_redistance(const bool flag) { + my_perform_initial_redistance = flag; + } + + void set_keep_IC_surfaces() { my_keep_IC_surfaces = true; } + bool get_keep_IC_surfaces() const { return my_keep_IC_surfaces; } + + static bool sign_change( double f1, double f2 ) { + return ( (f1 < 0.) ? (f2 >= 0.) : (f2 < 0.) ); // GOMA sign convention + //return ( (f1 > 0.) ? (f2 <= 0.) : (f2 > 0.) ); // Marching cubes sign convention + } + + static int sign( double f ) { + return ( (f < 0.) ? -1 : 1 ); // GOMA sign convention + //return ( (f > 0.) ? 1 : -1 ); // Marching cubes sign convention + } + + const std::vector & get_compute_surface_distance_parts() const { return my_compute_surface_distance_parts; } + std::vector & get_compute_surface_distance_parts() { return my_compute_surface_distance_parts; } + void set_surface_distance(std::vector surfaces, const double in_distance); + void compute_surface_distance(const double narrowBandSize=0.0, const double farFieldValue=0.0); + static void initialize(stk::mesh::MetaData & meta, const bool requires_additional_initialization); + void initialize(const double time = 0.0, const bool requires_additional_initialization = false); + void redistance(); + void redistance(const stk::mesh::Selector & selector); + void fast_marching_redistance(const stk::mesh::Selector & selector, const bool compute_time_of_arrival = false); + + void set_initial_volume(const double v) { my_initial_neg_vol = v; } + double constrained_redistance(const bool use_initial_vol = false); + + void compute_nodal_bbox( const stk::mesh::Selector & selector, + BoundingBox & node_bbox, + const Vector3d & displacement = Vector3d::ZERO ) const; + + double find_redistance_correction( const double start_area, + const double start_neg_vol, + const double start_pos_vol, + const int max_iterations = 100, + const double tol = 1.e-6 ); + + bool has_IC_surfaces(); + BoundingBox get_IC_surface_bounding_box(); + IC_Alg& get_IC_alg(); + + static LevelSet & build(stk::mesh::MetaData & in_meta, const std::string & ls_name, stk::diag::Timer & parent_timer); + + virtual ~LevelSet(); + +private: + LevelSet(stk::mesh::MetaData & in_meta, const std::string & in_name, stk::diag::Timer & parent_timer); + +private: + stk::mesh::MetaData & my_meta; + AuxMetaData & my_aux_meta; + const LevelSet_Identifier my_identifier; + const std::string my_name; + mutable stk::diag::Timer my_parent_timer; + mutable stk::diag::Timer my_timer; + +public: + const unsigned spatial_dimension; + +private: + + FieldRef my_coordinates_field; + FieldRef my_distance_field; + FieldRef my_old_distance_field; + FieldRef my_isovar_field; + FieldRef myTimeOfArrivalElementSpeedField; + + std::string my_distance_name; + std::string my_isovar_name; + + std::string my_composite_name; + + double my_narrow_band_multiplier; + double my_narrow_band_size; + + double my_max_feature_size; + bool my_use_simple_remove_feature = false; + + + double my_ic_offset; + double my_ic_scale; + bool my_perform_initial_redistance; + bool my_keep_IC_surfaces; + + double my_threshold; + Redistance_Method my_redistance_method; + std::string my_time_of_arrival_element_speed_field_name; + std::map myTimeOfArrivalBlockSpeedsByName; + std::vector myTimeOfArrivalBlockSpeeds; + + std::unique_ptr my_IC_alg; + + // vector of previous facets + std::unique_ptr facets_old; + + // vector of current facets + std::unique_ptr facets; + + Vector3d my_extension_velocity; + const double epsilon; + + bool trackIsoSurface; + + // used to increment file name for facet exoii database hack + int my_facetFileIndex; + + double my_initial_neg_vol; + + bool my_needs_reinitialize_every_step; + + std::vector my_compute_surface_distance_parts; + std::vector my_surface_parts; + + + void set_distance(const double & distance) const; + void increment_distance(const double increment, const bool enforce_sign = false) const; + void scale_distance(const double scale) const; + void negate_distance() const; + + void time_integrate(const double deltaTime); + + void prepare_to_compute_distance( const double & deltaTime, const stk::mesh::Selector & selector ); + + void compute_distance_semilagrangian(const double & deltaTime, const stk::mesh::Selector & selector ); + + double distance( const Vector3d & x, + const int previous_sign, + const bool enforce_sign ) const; + + bool compute_time_of_arrival() const { return !my_time_of_arrival_element_speed_field_name.empty() || !myTimeOfArrivalBlockSpeeds.empty(); } +}; + +class LevelSetManager { +public: + LevelSetManager() {} + LevelSetManager(LevelSetManager const&) = delete; + LevelSetManager& operator=(LevelSetManager const&) = delete; + + static LevelSetManager & get(stk::mesh::MetaData & meta); + static LevelSetManager & get(const stk::mesh::MetaData & meta); + + int numberLevelSets() const { return my_level_sets.size(); } + LevelSet & levelSet(const unsigned ordinal) const { ThrowAssert(ordinal < my_level_sets.size()); return *my_level_sets[ordinal]; } + bool has_levelSet(const std::string & ls_name) const; + void add(LevelSet * ls) { my_level_sets.emplace_back(ls); } + + std::vector< std::unique_ptr >::const_iterator begin() const { return my_level_sets.begin(); } + std::vector< std::unique_ptr >::const_iterator end() const { return my_level_sets.end(); } + + void set_current_coordinates(FieldRef current_coords) { my_current_coordinates = current_coords; } + FieldRef get_current_coordinates() const { return my_current_coordinates; } +private: + std::vector< std::unique_ptr > my_level_sets; + FieldRef my_current_coordinates; +}; + +std::string print_sizes(const LevelSet & ls); + +} // namespace krino + +#endif // Akri_LevelSet_h diff --git a/packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.cpp b/packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.cpp new file mode 100644 index 000000000000..9f2cfdda6005 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.cpp @@ -0,0 +1,931 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +std::unique_ptr create_element_cutter_with_error_handling(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const std::vector & elementParentEdges, + const std::vector & areParentEdgesAreOrientedSameAsElementEdges, + const Phase_Support & phaseSupport, + const std::function &)> & intersectingPlanesDiagonalPicker) +{ + if (krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "Building cutting planes for element global_id=" << mesh.identifier(element) << "\n"; + + stk::topology elementTopology = mesh.bucket(element).topology(); + const MasterElement & masterElement = MasterElementDeterminer::getMasterElement(elementTopology); + const bool oneLSPerPhase = phaseSupport.has_one_levelset_per_phase(); + + std::unique_ptr cutter; + + try + { + cutter = create_element_cutter(oneLSPerPhase, masterElement, elementParentEdges, areParentEdgesAreOrientedSameAsElementEdges, intersectingPlanesDiagonalPicker); + } + catch(const std::exception & err) + { + krinolog << "Error constructing cutting surfaces for Mesh_Element " << mesh.identifier(element) << stk::diag::push << stk::diag::dendl; + for(unsigned i=0; i < elementParentEdges.size(); ++i) + { + krinolog << "Edge " << i; + if(elementParentEdges[i]) { + krinolog << " node ids = "; + for(auto && node : elementParentEdges[i]->get_nodes()) krinolog << mesh.identifier(node) << " "; + krinolog << stk::diag::push << stk::diag::dendl; + krinolog << *elementParentEdges[i] << stk::diag::pop << stk::diag::dendl; + } + else + { + krinolog << " No parent edge." << stk::diag::dendl; + } + } + krinolog << stk::diag::pop << stk::diag::dendl; + krinolog << err.what() << stk::diag::dendl; + throw err; + } + + return cutter; +} + +LevelSetElementCutter::LevelSetElementCutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const ParentEdgeMap & parentEdges, + const Phase_Support & phaseSupport, + const std::function &)> & intersectingPlanesDiagonalPicker) +{ + fill_element_parent_edges(mesh, element, parentEdges, myParentEdges, myParentEdgesAreOrientedSameAsElementEdges); + myElementInterfaceCutter = create_element_cutter_with_error_handling(mesh, element, myParentEdges, myParentEdgesAreOrientedSameAsElementEdges, phaseSupport, intersectingPlanesDiagonalPicker); +} + +std::string LevelSetElementCutter::visualize(const stk::mesh::BulkData & mesh) const +{ + std::ostringstream os; + + std::vector interiorIntersections; + myElementInterfaceCutter->fill_interior_intersections(interiorIntersections); + + os << "Interior intersections " << interiorIntersections.size() << ": \n"; + for (auto && intersection : interiorIntersections) + { + os << "Interior intersection at " << intersection.parametricCoords << " with domains { "; + for (int domain : intersection.sortedDomains) os<< domain << " "; + os << "}\n"; + } + + os << "Parent edges: \n"; + for (auto && edge : myParentEdges) + { + os << "Edge with nodes " << mesh.identifier(edge->get_parent_nodes().first) << " and " << mesh.identifier(edge->get_parent_nodes().second) << ":\n"; + if (edge) os << *edge; + } + + os << myElementInterfaceCutter->visualize() << "\n"; + + return os.str(); +} + +std::unique_ptr LevelSetInterfaceGeometry::build_element_cutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const std::function &)> & intersectingPlanesDiagonalPicker) const +{ + std::unique_ptr cutter; + cutter.reset( new LevelSetElementCutter(mesh, element, myParentEdges, myPhaseSupport, intersectingPlanesDiagonalPicker) ); + return cutter; +} + +PhaseTag LevelSetInterfaceGeometry::get_starting_phase(const ElementCutter * cutter) const +{ + const LevelSetElementCutter * LSCutter = dynamic_cast(cutter); + ThrowRequire(LSCutter); + return LSCutter->get_starting_phase(myCdfemSupport, myPhaseSupport); +} + +PhaseTag LevelSetElementCutter::get_starting_phase(const CDFEM_Support & cdfemSupport, const Phase_Support & phaseSupport) const +{ + PhaseTag phase; + + // For elements with no interfaces, this will be the uncrossed phase of the mesh element. + // For elements with interface, this is the starting phase that will be inherited by the + // subelements and then incrementally updated as we process the interfaces. + if (cdfemSupport.num_ls_fields() > 1 && Phase_Support::has_one_levelset_per_phase()) + { + int LSphase = myElementInterfaceCutter->get_starting_phase_for_cutting_surfaces(); + if (LSphase == -1) + { + const auto & phasesPresent = get_phases_present_on_edges(myParentEdges); + ThrowRequire(!phasesPresent.empty() && (myElementInterfaceCutter->get_num_cutting_surfaces() > 0 || 1 == phasesPresent.size())); + LSphase = *phasesPresent.begin(); + } + phase.add(cdfemSupport.ls_field(LSphase).identifier, -1); + } + else + { + const int num_ls = cdfemSupport.num_ls_fields(); + for(int ls_index=0; ls_index < num_ls; ++ls_index) + { + const InterfaceID interface(ls_index,ls_index); + int sign = 0; + bool shouldSetPhase = false; + for(auto && edge : myParentEdges) + { + if (edge) + { + shouldSetPhase = true; + if (edge->have_crossing(InterfaceID(ls_index,ls_index))) + { + shouldSetPhase = false; + break; + } + else + sign = edge->get_crossing_sign(interface); + } + } + if (shouldSetPhase) + phase.add(cdfemSupport.ls_field(ls_index).identifier, sign); + } + } + + return phase; +} + +static std::vector find_next_phase_candidates(const std::vector & interfaces, + const std::vector & pathSoFar) +{ + const int currentPhase = pathSoFar.back(); + std::vector nextPhaseCandidates; + for (auto && interface : interfaces) + { + if (interface.first_ls() == currentPhase) + { + if (std::find(pathSoFar.begin(), pathSoFar.end(), interface.second_ls()) == pathSoFar.end()) + nextPhaseCandidates.push_back(interface.second_ls()); + } + else if (interface.second_ls() == currentPhase) + { + if (std::find(pathSoFar.begin(), pathSoFar.end(), interface.first_ls()) == pathSoFar.end()) + nextPhaseCandidates.push_back(interface.first_ls()); + } + } + return nextPhaseCandidates; +} + +static std::vector shortest_path_to_end(const std::vector & pathSoFar, + const std::vector & interfaces, + const std::set & endPhases) +{ + if (endPhases.count(pathSoFar.back()) > 0) + return pathSoFar; + + const std::vector nextPhaseCandidates = find_next_phase_candidates(interfaces, pathSoFar); + if (nextPhaseCandidates.empty()) + { + return {}; + } + + std::vector shortestPath; + size_t shortestPathSize = std::numeric_limits::max(); + for (int nextPhase : nextPhaseCandidates) + { + std::vector path = pathSoFar; + path.push_back(nextPhase); + const auto fullPath = shortest_path_to_end(path, interfaces, endPhases); + if (!fullPath.empty() && fullPath.size() < shortestPathSize) + { + shortestPath = fullPath; + shortestPathSize = fullPath.size(); + } + } + return shortestPath; +} + +std::vector shortest_path_from_begin_to_end(const std::vector & interfaces, + const int beginPhase, + const std::set & endPhases) +{ + std::vector startPath; + startPath.push_back(beginPhase); + return shortest_path_to_end(startPath, interfaces, endPhases); +} + +static bool captures_interface(const std::vector * sortedDomains, const InterfaceID & interface) +{ + if (sortedDomains == nullptr) + return false; + if (interface.first_ls() == interface.second_ls()) + return first_sorted_vector_of_domains_contains_all_domains_in_second_vector(*sortedDomains, {interface.first_ls()}); + return first_sorted_vector_of_domains_contains_all_domains_in_second_vector(*sortedDomains, {interface.first_ls(),interface.second_ls()}); +} + +static bool interface_has_uncaptured_edge_intersection(const LevelSetElementCutter & cutter, + const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains, + const InterfaceID & interface) +{ + ThrowRequire(elemNodesCoords.size() == 4 || elemNodesCoords.size() == 3); + const stk::topology topology = (elemNodesCoords.size() == 4)? stk::topology::TETRAHEDRON_4 : stk::topology::TRIANGLE_3_2D; + + const unsigned numEdges = topology.num_edges(); + for(unsigned i=0; i < numEdges; ++i) + { + const unsigned * edgeNodeOrdinals = get_edge_node_ordinals(topology, i); + const int n0 = edgeNodeOrdinals[0]; + const int n1 = edgeNodeOrdinals[1]; + if (!captures_interface(elemNodesSnappedDomains[n0], interface) && !captures_interface(elemNodesSnappedDomains[n1], interface)) + { + const Segment3d edge(elemNodesCoords[n0], elemNodesCoords[n1]); + if (cutter.have_crossing(interface, edge)) + return true; + } + } + return false; +} + +static std::vector +get_sorted_cutting_interfaces_with_uncaptured_intersections(const LevelSetElementCutter & cutter, + const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains) +{ + std::set interfacesWithUncapturedCrossings; + for (auto && interface : cutter.get_sorted_cutting_interfaces()) + if (interface_has_uncaptured_edge_intersection(cutter, elemNodesCoords, elemNodesSnappedDomains, interface)) + interfacesWithUncapturedCrossings.insert(interface); + + cutter.add_interfaces_with_uncaptured_intersection_within_element(elemNodesCoords, elemNodesSnappedDomains, interfacesWithUncapturedCrossings); + + std::vector interfaces(interfacesWithUncapturedCrossings.begin(), interfacesWithUncapturedCrossings.end()); + return interfaces; +} + +static int get_interface_index(const std::vector & sortedInterfaces, const InterfaceID interface) +{ + const auto iter = std::lower_bound(sortedInterfaces.begin(), sortedInterfaces.end(), interface); + return std::distance(sortedInterfaces.begin(), iter); +} + +static Vector3d get_centroid(const std::vector & elemNodesCoords) +{ + Vector3d centroid = Vector3d::ZERO; + for(auto && nodeCoords : elemNodesCoords) + { + centroid += nodeCoords; + } + centroid *= 1./elemNodesCoords.size(); + return centroid; +} + +std::vector +LevelSetElementCutter::get_interface_signs_based_on_crossings(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains) const +{ + const auto allInterfaces = get_sorted_cutting_interfaces(); + std::vector interfaceSigns(allInterfaces.size(), 0); + + const auto intersectingInterfaces = get_sorted_cutting_interfaces_with_uncaptured_intersections(*this, elemNodesCoords, elemNodesSnappedDomains); + const Vector3d centroid = get_centroid(elemNodesCoords); + + const bool oneLSPerPhase = Phase_Support::has_one_levelset_per_phase(); + if (oneLSPerPhase) + { + std::set subPhases; + const int ownerStartPhase = get_starting_phase_for_cutting_surfaces(); + + for (auto && interface : allInterfaces) + { + if (std::binary_search(intersectingInterfaces.begin(), intersectingInterfaces.end(), interface)) + { + subPhases.insert(interface.first_ls()); + subPhases.insert(interface.second_ls()); + } + else + { + interfaceSigns[get_interface_index(allInterfaces, interface)] = -2; + } + } + if (subPhases.empty()) + { + subPhases.insert(myElementInterfaceCutter->get_ls_per_interface_phase_at_location(centroid)); + } + + if (subPhases.count(ownerStartPhase) == 0) + { + std::vector fixPath = shortest_path_from_begin_to_end(allInterfaces, ownerStartPhase, subPhases); + ThrowRequireMsg(!fixPath.empty(), "Cannot fix starting phase."); + for (unsigned i=1; i fixPath[i-1]) ? 1 : -1; + interfaceSigns[get_interface_index(allInterfaces, interface)] = sign; + } + } + } + else + { + for (auto && interface : allInterfaces) + if (!std::binary_search(intersectingInterfaces.begin(), intersectingInterfaces.end(), interface)) + interfaceSigns[get_interface_index(allInterfaces, interface)] = sign_at_position(interface, centroid); + } + + return interfaceSigns; +} + +void LevelSetElementCutter::update_edge_crossings(const unsigned iEdge, const std::vector> & nodesIsovar) +{ + ThrowRequire(iEdge < myParentEdges.size()); + CDFEM_Parent_Edge * parentEdge = const_cast(myParentEdges[iEdge]); + ThrowRequire(parentEdge); + if (myParentEdgesAreOrientedSameAsElementEdges[iEdge]) + { + parentEdge->find_crossings(nodesIsovar); + } + else + { + const std::vector> orientedIsovar = {nodesIsovar[1], nodesIsovar[0]}; + parentEdge->find_crossings(orientedIsovar); + } +} + +static std::function &)> +temporary_build_always_true_diagonal_picker() +{ + auto diagonalPicker = + [](const std::array & faceNodes) + { + return true; + }; + return diagonalPicker; +} + +typedef std::function ParentEdgeFilter; + +static ParentEdgeFilter keep_owned_edges_filter(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & parentElementSelector) +{ + const auto filter = [&mesh, &parentElementSelector](const CDFEM_Parent_Edge & edge) + { + const std::pair edgeNodes = edge.get_parent_nodes(); + std::vector edgeElems; + stk::mesh::get_entities_through_relations(mesh, {edgeNodes.first, edgeNodes.second}, stk::topology::ELEMENT_RANK, edgeElems); + { + bool foundOwnedElement = false; + for (auto && edgeElem : edgeElems) + if (parentElementSelector(mesh.bucket(edgeElem)) && mesh.parallel_owner_rank(edgeElem) == mesh.parallel_rank()) + foundOwnedElement = true; + ThrowRequire(foundOwnedElement); + } + const int parallelRank = mesh.parallel_rank(); // Assumes local proc owns at least one selected element of edge + for (auto && edgeElem : edgeElems) + if (mesh.parallel_owner_rank(edgeElem) < parallelRank && parentElementSelector(mesh.bucket(edgeElem))) + return false; + return true; + }; + return filter; +} + +static ParentEdgeFilter keep_all_edges_filter() +{ + const auto filter = [](const CDFEM_Parent_Edge & edge) + { + return true; + }; + return filter; +} + +static void append_intersection_points_from_filtered_parent_edges(std::vector & intersectionPoints, + const ParentEdgeMap & parentEdges, + const IntersectionPointFilter & intersectionPointFilter, + const ParentEdgeFilter edgeFilter) +{ + const bool intersectionPointIsOwned = true; + std::vector intersectionPointSortedDomains; + for (auto && mapEntry : parentEdges) + { + const CDFEM_Parent_Edge & edge = mapEntry.second; + const auto & edgeCrossings = edge.get_crossings(); + if (!edgeCrossings.empty() && edgeFilter(edge)) + { + const std::pair edgeNodes = edge.get_parent_nodes(); + for (auto & crossing : edgeCrossings) + { + const InterfaceID & interface = crossing.first; + const double location = crossing.second; + interface.fill_sorted_domains(intersectionPointSortedDomains); + std::vector intersectionPointNodes{edgeNodes.first, edgeNodes.second}; + if (intersectionPointFilter(intersectionPointNodes, intersectionPointSortedDomains)) + intersectionPoints.emplace_back(intersectionPointIsOwned, intersectionPointNodes, std::vector{1.-location, location}, intersectionPointSortedDomains); + } + } + } +} + +static void append_intersection_points_from_owned_parent_edges(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & parentElementSelector, + std::vector & intersectionPoints, + const ParentEdgeMap & parentEdges, + const IntersectionPointFilter & intersectionPointFilter) +{ + append_intersection_points_from_filtered_parent_edges(intersectionPoints, parentEdges, intersectionPointFilter, keep_owned_edges_filter(mesh, parentElementSelector)); +} + +static void append_intersection_points_from_all_parent_edges(std::vector & intersectionPoints, + const ParentEdgeMap & parentEdges, + const IntersectionPointFilter & intersectionPointFilter) +{ + append_intersection_points_from_filtered_parent_edges(intersectionPoints, parentEdges, intersectionPointFilter, keep_all_edges_filter()); +} + +LevelSetInterfaceGeometry::LevelSetInterfaceGeometry(const stk::mesh::MetaData & meta) +: LevelSetInterfaceGeometry(AuxMetaData::get(meta).active_part(), CDFEM_Support::get(meta), Phase_Support::get(meta)) {} + +void LevelSetInterfaceGeometry::prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + myParentsToChildMapper.build_map(mesh, myActivePart, myCdfemSupport, myPhaseSupport); + const auto should_build_linearized_edge = build_no_linearized_edges_function(); + myParentEdges = build_parent_edges(mesh, myParentsToChildMapper, should_build_linearized_edge, myActivePart, myCdfemSupport, myPhaseSupport); +} + +void LevelSetInterfaceGeometry::prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const std::vector & elementsToIntersect, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + myParentsToChildMapper.build_map(mesh, myActivePart, myCdfemSupport, myPhaseSupport); + const auto should_build_linearized_edge = build_no_linearized_edges_function(); + myParentEdges = build_parent_edges_using_elements(mesh, myParentsToChildMapper, should_build_linearized_edge, elementsToIntersect, myActivePart, myCdfemSupport, myPhaseSupport); +} + +std::vector LevelSetInterfaceGeometry::get_edge_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + std::vector intersectionPoints; + prepare_to_process_elements(mesh, nodesToCapturedDomains); + const IntersectionPointFilter intersectionPointFilter = keep_all_intersection_points_filter(); + append_intersection_points_from_all_parent_edges(intersectionPoints, myParentEdges, intersectionPointFilter); + return intersectionPoints; +} + +void LevelSetInterfaceGeometry::append_element_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + const std::vector & elementsToIntersect, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) const +{ + const stk::mesh::Selector parentElementSelector = get_parent_element_selector(myActivePart, myCdfemSupport, myPhaseSupport); + prepare_to_process_elements(mesh, elementsToIntersect, nodesToCapturedDomains); + append_intersection_points_from_owned_parent_edges(mesh, parentElementSelector, intersectionPoints, myParentEdges, intersectionPointFilter); + append_intersection_points_from_within_elements_and_owned_faces(mesh, parentElementSelector, elementsToIntersect, *this, intersectionPointFilter, intersectionPoints); +} + +static int get_domain_for_uncut_element(const stk::mesh::BulkData & mesh, + const stk::mesh::Entity element, + const std::vector & elementParentEdges, + const bool oneLSPerPhase) +{ + if (oneLSPerPhase) + { + const auto edgePhases = get_phases_present_on_edges_and_interior(elementParentEdges); + if (edgePhases.size() == 1) + return *edgePhases.begin(); + } + return -1; +} + +void LevelSetInterfaceGeometry::store_phase_for_uncut_elements(const stk::mesh::BulkData & mesh) const +{ + std::vector elementsToIntersect; + stk::mesh::get_entities( mesh, stk::topology::ELEMENT_RANK, elementsToIntersect, false); + + myParentsToChildMapper.build_map(mesh, myActivePart, myCdfemSupport, myPhaseSupport); + const auto linearize_all_edges = build_all_linearized_edges_function(); + ParentEdgeMap parentEdges = build_parent_edges_using_elements(mesh, myParentsToChildMapper, linearize_all_edges, elementsToIntersect, myActivePart, myCdfemSupport, myPhaseSupport); + + const bool oneLSPerPhase = myCdfemSupport.num_ls_fields() > 1 && Phase_Support::has_one_levelset_per_phase(); + std::vector elementParentEdges; + std::vector areParentEdgesOrientedSameAsElementEdges; + + const std::vector parentElements = get_owned_parent_elements(mesh, myActivePart, myCdfemSupport, myPhaseSupport); + for (auto element : parentElements) + { + fill_element_parent_edges(mesh, element, parentEdges, elementParentEdges, areParentEdgesOrientedSameAsElementEdges); + const int elementDomain = get_domain_for_uncut_element(mesh, + element, + elementParentEdges, + oneLSPerPhase); + if (elementDomain >= 0) + myUncutElementPhases[element] = elementDomain; + } +} + +static NodeToCapturedDomainsMap store_and_communicate_new_snap_node_domains(const stk::mesh::BulkData & mesh, const std::vector & intersectionPoints, const std::vector & snapInfos) +{ + std::vector snapNodes; + NodeToCapturedDomainsMap newSnapnodesToCapturedDomains; + for (auto && snapInfo : snapInfos) + { + if (snapInfo.get_owner() == mesh.parallel_rank()) + { + const size_t intersectionPointIndex = snapInfo.get_intersection_point_index(); + stk::mesh::Entity snapNode = mesh.get_entity(stk::topology::NODE_RANK, snapInfo.get_node_global_id()); + snapNodes.push_back(snapNode); + const IntersectionPoint & intersectionPoint = intersectionPoints[intersectionPointIndex]; + + newSnapnodesToCapturedDomains[snapNode] = intersectionPoint.get_sorted_domains(); + } + } + + communicate_node_captured_domains_for_given_nodes(mesh, snapNodes, newSnapnodesToCapturedDomains); + + return newSnapnodesToCapturedDomains; +} + +static std::vector get_node_parametric_coords_after_snapping(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + stk::mesh::Entity snapNode, + const Vector3d snapNodeLocation) +{ + stk::topology elementTopology = mesh.bucket(element).topology(); + const MasterElement & masterElement = MasterElementDeterminer::getMasterElement(elementTopology); + const double * elemNodeParamCoords = masterElement.nodal_parametric_coordinates(); + const int dim = mesh.mesh_meta_data().spatial_dimension(); + const StkMeshEntities elementNodes{mesh.begin_nodes(element), mesh.end_nodes(element)}; + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + + std::vector nodeLocations; + fill_element_node_coordinates(mesh, element, coordsField, nodeLocations); + + std::vector nodesCoords; + for (unsigned n=0; n= baseShapeFcn) + return 1.; + return baseShapeFcn/(baseShapeFcn-shapeFcn); +} + +static Vector3d find_point_within_deformed_and_undeformed_tet(const std::vector & deformedElementParamCoords, + const int lnn) +{ + stk::topology topology = stk::topology::TETRAHEDRON_4; + std::array permutations{0, 1, 2, 4}; + std::array permuteNodes; + topology.permutation_node_ordinals(permutations[lnn], permuteNodes.data()); + ThrowAssert(permuteNodes[0] == lnn); + const Vector3d & pt = deformedElementParamCoords[lnn]; + const Vector3d oppositePt = 1./3.*(deformedElementParamCoords[permuteNodes[1]] + deformedElementParamCoords[permuteNodes[2]] + deformedElementParamCoords[permuteNodes[3]]); + double fraction = 1.0; + fraction = std::min(fraction, truncate_to_maintain_positive_shape_function(pt[0], oppositePt[0])); + fraction = std::min(fraction, truncate_to_maintain_positive_shape_function(pt[1], oppositePt[1])); + fraction = std::min(fraction, truncate_to_maintain_positive_shape_function(pt[2], oppositePt[2])); + fraction = std::min(fraction, truncate_to_maintain_positive_shape_function(1.-pt[0]-pt[1]-pt[2], 1.-oppositePt[0]-oppositePt[1]-oppositePt[2])); + const double centroidWt = 0.25*fraction; + return centroidWt*pt + (1.-centroidWt)*oppositePt; +} + +static Vector3d find_point_within_deformed_and_undeformed_tri(const std::vector & deformedElementParamCoords, + const int lnn) +{ + stk::topology topology = stk::topology::TRIANGLE_3_2D; + std::array permutations{0, 2, 1}; + std::array permuteNodes; + topology.permutation_node_ordinals(permutations[lnn], permuteNodes.data()); + ThrowAssert(permuteNodes[0] == lnn); + const Vector3d & pt = deformedElementParamCoords[lnn]; + const Vector3d oppositePt = 0.5*(deformedElementParamCoords[permuteNodes[1]] + deformedElementParamCoords[permuteNodes[2]]); + double fraction = 1.0; + fraction = std::min(fraction, truncate_to_maintain_positive_shape_function(pt[0], oppositePt[0])); + fraction = std::min(fraction, truncate_to_maintain_positive_shape_function(pt[1], oppositePt[1])); + fraction = std::min(fraction, truncate_to_maintain_positive_shape_function(1.-pt[0]-pt[1], 1.-oppositePt[0]-oppositePt[1])); + const double centroidWt = 1./3.*fraction; + return centroidWt*pt + (1.-centroidWt)*oppositePt; +} + +static int get_node_of_element(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + stk::mesh::Entity node) +{ + const StkMeshEntities elementNodes{mesh.begin_nodes(element), mesh.end_nodes(element)}; + for (unsigned n=0; n & deformedElementParamCoords) +{ + const int lnn = get_node_of_element(mesh, element, snapNode); + + stk::topology elementTopology = mesh.bucket(element).topology(); + if (elementTopology.base() == stk::topology::TETRAHEDRON_4) + return find_point_within_deformed_and_undeformed_tet(deformedElementParamCoords, lnn); + ThrowRequire(elementTopology.base() == stk::topology::TRIANGLE_3_2D); + return find_point_within_deformed_and_undeformed_tri(deformedElementParamCoords, lnn); +} + +static std::vector*> get_node_snap_domains(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + stk::mesh::Entity snapNode, + const std::vector & snapNodeDomains, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + const StkMeshEntities elementNodes{mesh.begin_nodes(element), mesh.end_nodes(element)}; + std::vector*> nodeDomains; + nodeDomains.reserve(elementNodes.size()); + for (auto node : StkMeshEntities{mesh.begin_nodes(element), mesh.end_nodes(element)}) + { + if (node == snapNode) + { + nodeDomains.push_back(&snapNodeDomains); + } + else + { + const auto iter = nodesToCapturedDomains.find(node); + if (iter == nodesToCapturedDomains.end()) + nodeDomains.push_back(nullptr); + else + nodeDomains.push_back(&(iter->second)); + } + } + + return nodeDomains; +} + +static bool domain_is_common_to_all_entities(const int domain, const std::vector*> & entitiesDomains) +{ + for (auto && domains : entitiesDomains) + if (!std::binary_search(domains->begin(), domains->end(), domain)) + return false; + return true; +} + +static std::set get_common_domains(std::vector*> entitiesDomains) +{ + std::set commonDomains; + + if (entitiesDomains.empty()) + return commonDomains; + + for (auto & entityDomains : entitiesDomains) + if (!entityDomains) + return commonDomains; + + for (int domain : *entitiesDomains[0]) + if (domain_is_common_to_all_entities(domain, entitiesDomains)) + commonDomains.insert(domain); + + return commonDomains; +} + +static bool will_have_uncaptured_edge_intersection_after_snapping(const Element_Cutter & cutter, stk::topology elementTopology, const std::vector nodeCoords, + const std::vector*> & nodeDomains) +{ + std::vector interfacesWithCuttingSurface; + cutter.fill_interfaces_with_cutting_surface(interfacesWithCuttingSurface); + + const unsigned numEdges = elementTopology.num_edges(); + for(unsigned i=0; i < numEdges; ++i) + { + const unsigned * edgeNodeOrdinals = get_edge_node_ordinals(elementTopology(), i); + const std::vector* node0Domains = nodeDomains[edgeNodeOrdinals[0]]; + const std::vector* node1Domains = nodeDomains[edgeNodeOrdinals[1]]; + + for (auto && interface : interfacesWithCuttingSurface) + { + if (!captures_interface(node0Domains, interface) && !captures_interface(node1Domains, interface)) + { + const Segment3d edge(nodeCoords[edgeNodeOrdinals[0]], nodeCoords[edgeNodeOrdinals[1]]); + if (cutter.have_crossing(interface, edge)) + return true; + } + } + } + return false; +} + +static int get_domain_of_element_if_it_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper parentsToChildMapper, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + stk::mesh::Entity element, + stk::mesh::Entity snapNode, + const Vector3d & snapNodeLocation, + const std::vector*> & elementNodeDomains) +{ + const auto diagonalPicker = temporary_build_always_true_diagonal_picker(); + const bool oneLSPerPhase = true; + + ParentEdgeMap parentEdges = build_parent_edges_using_elements(mesh, parentsToChildMapper, build_all_linearized_edges_function(), {element}, activePart, cdfemSupport, phaseSupport); + if (parentEdges.empty()) + return -1; // not parent element + + std::vector elementParentEdges; + std::vector areParentEdgesOrientedSameAsElementEdges; + fill_element_parent_edges(mesh, element, parentEdges, elementParentEdges, areParentEdgesOrientedSameAsElementEdges); + + const auto edgePhases = get_phases_present_on_edges_and_interior(elementParentEdges); + if (edgePhases.size() == 1) + return *edgePhases.begin(); + + stk::topology elementTopology = mesh.bucket(element).topology(); + const MasterElement & masterElement = MasterElementDeterminer::getMasterElement(elementTopology); + std::unique_ptr elementCutter = create_element_cutter(oneLSPerPhase, masterElement, elementParentEdges, areParentEdgesOrientedSameAsElementEdges, diagonalPicker); + + const std::vector nodeParamCoordsAfterSnapping = get_node_parametric_coords_after_snapping(mesh, element, snapNode, snapNodeLocation); + if (!will_have_uncaptured_edge_intersection_after_snapping(*elementCutter, elementTopology, nodeParamCoordsAfterSnapping, elementNodeDomains)) + { + const Vector3d evaluationPt = find_point_within_deformed_and_undeformed_element(mesh, element, snapNode, nodeParamCoordsAfterSnapping); + return elementCutter->get_ls_per_interface_phase_at_location(evaluationPt); + } + + return -1; +} + +static int get_sign_of_uncrossed_edges(const InterfaceID interface, const std::vector & elementParentEdges) +{ + int uncrossedSign = 0; + for (auto && edge : elementParentEdges) + { + if (edge->have_crossing(interface)) + { + return 0; + } + else + { + const int edgeSign = edge->get_crossing_sign(interface); + if (edgeSign == -uncrossedSign) + return 0; + uncrossedSign = edgeSign; + } + } + return uncrossedSign; +} + +static int get_sign_of_element_if_it_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper parentsToChildMapper, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + stk::mesh::Entity element, + stk::mesh::Entity snapNode, + const Vector3d & snapNodeLocation, + const InterfaceID & interface) +{ + const auto diagonalPicker = temporary_build_always_true_diagonal_picker(); + const bool oneLSPerPhase = false; + + ParentEdgeMap parentEdges = build_parent_edges_using_elements(mesh, parentsToChildMapper, build_all_linearized_edges_function(), {element}, activePart, cdfemSupport, phaseSupport); + if (parentEdges.empty()) + return -1; // not parent element + + std::vector elementParentEdges; + std::vector areParentEdgesOrientedSameAsElementEdges; + fill_element_parent_edges(mesh, element, parentEdges, elementParentEdges, areParentEdgesOrientedSameAsElementEdges); + + const int uncrossedSign = get_sign_of_uncrossed_edges(interface, elementParentEdges); + if (uncrossedSign != 0) + return uncrossedSign; + + stk::topology elementTopology = mesh.bucket(element).topology(); + const MasterElement & masterElement = MasterElementDeterminer::getMasterElement(elementTopology); + std::unique_ptr elementCutter = create_element_cutter(oneLSPerPhase, masterElement, elementParentEdges, areParentEdgesOrientedSameAsElementEdges, diagonalPicker); + + const std::vector nodeParamCoordsAfterSnapping = get_node_parametric_coords_after_snapping(mesh, element, snapNode, snapNodeLocation); + const Vector3d evaluationPt = find_point_within_deformed_and_undeformed_element(mesh, element, snapNode, nodeParamCoordsAfterSnapping); + return elementCutter->sign_at_position(interface, evaluationPt); +} + +static void set_domains_for_element_if_it_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper parentsToChildMapper, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + stk::mesh::Entity element, + stk::mesh::Entity snapNode, + const std::vector & snapNodeDomains, + const Vector3d & snapNodeLocation, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + ElementToDomainMap & elementsToDomain ) +{ + auto iter = elementsToDomain.lower_bound(element); + if (iter == elementsToDomain.end() || iter->first != element) + { + const std::vector*> nodeDomains = get_node_snap_domains(mesh, element, snapNode, snapNodeDomains, nodesToCapturedDomains); + + const std::set commonDomains = get_common_domains(nodeDomains); + + int elementDomain = -1; + if (commonDomains.size() == 1) + { + elementDomain = *commonDomains.begin(); + } + else if (commonDomains.size() > 1) + { + elementDomain = get_domain_of_element_if_it_will_be_uncut_after_snapping(mesh, parentsToChildMapper, activePart, cdfemSupport, phaseSupport, element, snapNode, snapNodeLocation, nodeDomains); + } + + if (elementDomain >= 0) + elementsToDomain.emplace_hint(iter, element, elementDomain); + } +} + +static void set_sign_for_element_if_it_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const ParentsToChildMapper parentsToChildMapper, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport, + stk::mesh::Entity element, + stk::mesh::Entity snapNode, + const std::vector & snapNodeDomains, + const Vector3d & snapNodeLocation, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + ElementToDomainMap & elementsToDomain ) +{ + auto iter = elementsToDomain.lower_bound(element); + if (iter == elementsToDomain.end() || iter->first != element) + { + const std::vector*> nodeDomains = get_node_snap_domains(mesh, element, snapNode, snapNodeDomains, nodesToCapturedDomains); + + const std::set commonDomains = get_common_domains(nodeDomains); + + int elementSign = 0; + if (commonDomains.size() == 1) + { + const int lsIndex = *commonDomains.begin(); + const InterfaceID interface(lsIndex,lsIndex); + elementSign = get_sign_of_element_if_it_will_be_uncut_after_snapping(mesh, parentsToChildMapper, activePart, cdfemSupport, phaseSupport, element, snapNode, snapNodeLocation, interface); + } + + if (elementSign != 0) + elementsToDomain.emplace_hint(iter, element, elementSign); + } +} + +void LevelSetInterfaceGeometry::store_phase_for_elements_that_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const std::vector & intersectionPoints, + const std::vector & snapInfos, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const +{ + const bool oneLSPerPhase = myCdfemSupport.num_ls_fields() > 1 && Phase_Support::has_one_levelset_per_phase(); + if (!oneLSPerPhase && myCdfemSupport.num_ls_fields() > 1) + return; //FIXME: Fix for more than one ls per interface + + const NodeToCapturedDomainsMap newSnapnodesToCapturedDomains = store_and_communicate_new_snap_node_domains(mesh, intersectionPoints, snapInfos); + + for (auto && snapInfo : snapInfos) + { + stk::mesh::Entity snapNode = mesh.get_entity(stk::topology::NODE_RANK, snapInfo.get_node_global_id()); + const stk::math::Vector3d snapLocation = snapInfo.get_snap_location(); + const auto & newSnapNodeDomains = newSnapnodesToCapturedDomains.at(snapNode); + for (auto elem : StkMeshEntities{mesh.begin_elements(snapNode), mesh.end_elements(snapNode)}) + { + if (mesh.bucket(elem).owned() && mesh.bucket(elem).member(myActivePart)) + { + if (oneLSPerPhase) + set_domains_for_element_if_it_will_be_uncut_after_snapping(mesh, myParentsToChildMapper, myActivePart, myCdfemSupport, myPhaseSupport, elem, snapNode, newSnapNodeDomains, snapLocation, nodesToCapturedDomains, myUncutElementPhases); + else + set_sign_for_element_if_it_will_be_uncut_after_snapping(mesh, myParentsToChildMapper, myActivePart, myCdfemSupport, myPhaseSupport, elem, snapNode, newSnapNodeDomains, snapLocation, nodesToCapturedDomains, myUncutElementPhases); + } + } + } +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.hpp b/packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.hpp new file mode 100644 index 000000000000..393c97c3e622 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_LevelSetInterfaceGeometry.hpp @@ -0,0 +1,122 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_LEVELSETINTERFACEGEOMETRY_H_ +#define KRINO_INCLUDE_AKRI_LEVELSETINTERFACEGEOMETRY_H_ +#include +#include +#include +#include +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" + +namespace krino { + +class CDFEM_Support; +class Phase_Support; + +class LevelSetElementCutter : public ElementCutter +{ +public: + LevelSetElementCutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const ParentEdgeMap & parentEdges, + const Phase_Support & phaseSupport, + const std::function &)> & intersectingPlanesDiagonalPicker); + virtual ~LevelSetElementCutter() {} + + virtual bool might_have_interior_or_face_intersections() const override + { return myElementInterfaceCutter->get_num_cutting_surfaces() > 1; } + virtual void fill_interior_intersections(const ElementIntersectionPointFilter & intersectionPointFilter, std::vector & intersections) const override + { myElementInterfaceCutter->fill_interior_intersections(intersectionPointFilter, intersections); } + virtual std::vector get_sorted_cutting_interfaces() const override + { std::vector interfaces; myElementInterfaceCutter->fill_interfaces_with_cutting_surface(interfaces); return interfaces; } + virtual std::vector get_interface_signs_based_on_crossings(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains) const override; + virtual void fill_tetrahedron_face_interior_intersections(const std::array & faceNodes, + const InterfaceID & interface1, + const InterfaceID & interface2, + const ElementIntersectionPointFilter & intersectionPointFilter, + std::vector & intersections) const override + { myElementInterfaceCutter->fill_tetrahedron_face_interior_intersections(faceNodes, interface1, interface2, intersectionPointFilter, intersections); } + virtual std::string visualize(const stk::mesh::BulkData & mesh) const override; + virtual bool have_crossing(const InterfaceID interface, const Segment3d & edge) const override + { return myElementInterfaceCutter->have_crossing(interface, edge); } + virtual double interface_crossing_position(const InterfaceID interface, const Segment3d & edge) const override + { return myElementInterfaceCutter->interface_crossing_position(interface, edge); } + virtual int sign_at_position(const InterfaceID interface, const Vector3d & paramCoords) const override + { return myElementInterfaceCutter->sign_at_position(interface, paramCoords); } + virtual int get_starting_phase_for_cutting_surfaces() const override + { return myElementInterfaceCutter->get_starting_phase_for_cutting_surfaces(); } + + PhaseTag get_starting_phase(const CDFEM_Support & cdfemSupport, const Phase_Support & phaseSupport) const; + void update_edge_crossings(const unsigned iEdge, const std::vector> & nodesIsovar); + void add_interfaces_with_uncaptured_intersection_within_element(const std::vector & elemNodesCoords, + const std::vector *> & elemNodesSnappedDomains, + std::set & interfacesWithUncapturedCrossings) const + { return myElementInterfaceCutter->add_interfaces_with_uncaptured_intersection_within_element(elemNodesCoords, elemNodesSnappedDomains, interfacesWithUncapturedCrossings); } + +private: + std::vector myParentEdges; + std::vector myParentEdgesAreOrientedSameAsElementEdges; + std::unique_ptr myElementInterfaceCutter; +}; + +class LevelSetInterfaceGeometry : public InterfaceGeometry { + +public: + LevelSetInterfaceGeometry(const stk::mesh::MetaData & meta); + LevelSetInterfaceGeometry(const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +: myActivePart(activePart), + myCdfemSupport(cdfemSupport), + myPhaseSupport(phaseSupport) {} + virtual ~LevelSetInterfaceGeometry() {} + + virtual void prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const override; + virtual void prepare_to_process_elements(const stk::mesh::BulkData & mesh, + const std::vector & elementsToIntersect, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const override; + + virtual std::vector get_edge_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const override; + virtual void append_element_intersection_points(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + const std::vector & elementsToIntersect, + const IntersectionPointFilter & intersectionPointFilter, + std::vector & intersectionPoints) const override; + + // FIXME: Temporary methods + virtual void store_phase_for_uncut_elements(const stk::mesh::BulkData & mesh) const override; + virtual void store_phase_for_elements_that_will_be_uncut_after_snapping(const stk::mesh::BulkData & mesh, + const std::vector & intersectionPoints, + const std::vector & independentSnapInfos, + const NodeToCapturedDomainsMap & nodesToCapturedDomains) const override; + + virtual const ElementToDomainMap & get_phase_for_uncut_elements() const override { return myUncutElementPhases; } + + virtual std::unique_ptr build_element_cutter(const stk::mesh::BulkData & mesh, + stk::mesh::Entity element, + const std::function &)> & intersectingPlanesDiagonalPicker) const override; + + virtual PhaseTag get_starting_phase(const ElementCutter * cutter) const override; + +private: + const stk::mesh::Part & myActivePart; + const CDFEM_Support & myCdfemSupport; + const Phase_Support & myPhaseSupport; + mutable ParentEdgeMap myParentEdges; + mutable ParentsToChildMapper myParentsToChildMapper; + mutable ElementToDomainMap myUncutElementPhases; +}; + +} + + +#endif /* KRINO_INCLUDE_AKRI_LEVELSETINTERFACEGEOMETRY_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_LevelSet_Identifier.hpp b/packages/krino/krino/krino_lib/Akri_LevelSet_Identifier.hpp new file mode 100644 index 000000000000..4de543ea1156 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_LevelSet_Identifier.hpp @@ -0,0 +1,37 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_LevelSet_Identifier_h +#define Akri_LevelSet_Identifier_h + +#include + +namespace krino { + +class LevelSet_Identifier { +public: + explicit LevelSet_Identifier(const unsigned id) : my_id(id) {} + + bool operator < ( const LevelSet_Identifier & RHS ) const { return my_id < RHS.my_id; } + bool operator == ( const LevelSet_Identifier & RHS ) const { return my_id == RHS.my_id; } + bool operator != ( const LevelSet_Identifier & RHS ) const { return my_id != RHS.my_id; } + + unsigned get() const { return my_id; } + + friend std::ostream& operator<<(std::ostream & os, const LevelSet_Identifier & id) + { + os << id.get(); + return os; + } +private: + unsigned my_id; +}; + +} // namespace krino + +#endif // Akri_LevelSet_Identifier_h diff --git a/packages/krino/krino/krino_lib/Akri_LowerEnvelope.cpp b/packages/krino/krino/krino_lib/Akri_LowerEnvelope.cpp new file mode 100644 index 000000000000..0f660042073a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_LowerEnvelope.cpp @@ -0,0 +1,317 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include + +namespace krino{ + +LS_Segment::LS_Segment(const double xL, const double xR, int id_) +: x_left(xL), x_right(xR), LS_index(id_) +{ +} + +int +get_min(const std::vector & phi) +{ + double min_value = 0.0; + int min_index = -1; + for (unsigned i=0; i +find_crossing_position_and_sign(const InterfaceID key, const std::vector & pos, const std::vector> & phi) +{ + const int num_nodes = pos.size(); + std::vector key_isovar(num_nodes); + for ( int node = 0; node < num_nodes; ++node ) + { + key_isovar[node] = phi[node][key.first_ls()]-phi[node][key.second_ls()]; + } + + if ( LevelSet::sign_change(key_isovar[0], key_isovar[num_nodes-1]) ) + { + for ( int s = 0; s < num_nodes-1; ++s ) + { + const double ls0 = key_isovar[s]; + const double ls1 = key_isovar[s+1]; + if ( LevelSet::sign_change(ls0, ls1) ) + { + const double interval_position = ls0 / ( ls0 - ls1 ); + const double abs_position = (1.-interval_position)*pos[s] + interval_position*pos[s+1]; + return std::make_pair(abs_position, LevelSet::sign(ls1)); + } + } + } + + const int node0_sign = LevelSet::sign(key_isovar[0]); + const int node1_sign = LevelSet::sign(key_isovar[num_nodes-1]); + ThrowRequire(node0_sign == node1_sign); + return std::make_pair(-1., node0_sign); +} + +bool +find_lower_envelope_crossing_point( const std::array,2> & phi, double & x) +{ + const int id0 = get_min(phi[0]); + const int id1 = get_min(phi[1]); + + if ( id0 != id1 ) + { + const double ls0 = (phi[0][id0]-phi[0][id1]); + const double ls1 = (phi[1][id0]-phi[1][id1]); + + x = ls0 / ( ls0 - ls1 ); + return true; + } + + return false; +} + +bool +find_lower_envelope_crossing_point(const std::array,3> & phi, std::array & point) +{ + const int id0 = get_min(phi[0]); + const int id1 = get_min(phi[1]); + const int id2 = get_min(phi[2]); + + if (id0 != id1 && id1 != id2 && id0 != id2) + { + const double d10 = (phi[0][id1]-phi[0][id0]); + const double d11 = (phi[1][id1]-phi[1][id0]); + const double d12 = (phi[2][id1]-phi[2][id0]); + const double d20 = (phi[0][id2]-phi[0][id0]); + const double d21 = (phi[1][id2]-phi[1][id0]); + const double d22 = (phi[2][id2]-phi[2][id0]); + + const double a00 = d11-d10; + const double a01 = d12-d10; + const double a10 = d21-d20; + const double a11 = d22-d20; + + const double b0 = -d10; + const double b1 = -d20; + + const double det = a00*a11-a01*a10; + if (det == 0.) return false; + + const double x = (b0*a11-b1*a01)/det; + const double y = (-b0*a10+b1*a00)/det; + if (x < 0. || y < 0. || 1.-x-y < 0.) return false; + + point = {{x,y}}; + return true; + } + + return false; +} + +bool +find_lower_envelope_crossing_point(const std::array,4> & phi, std::array & point) +{ + const int id0 = get_min(phi[0]); + const int id1 = get_min(phi[1]); + const int id2 = get_min(phi[2]); + const int id3 = get_min(phi[3]); + + if (id0 != id1 && id0 != id2 && id0 != id3 && id1 != id2 && id1 != id3 && id2 != id3) + { + const double d10 = (phi[0][id1]-phi[0][id0]); + const double d11 = (phi[1][id1]-phi[1][id0]); + const double d12 = (phi[2][id1]-phi[2][id0]); + const double d13 = (phi[3][id1]-phi[3][id0]); + const double d20 = (phi[0][id2]-phi[0][id0]); + const double d21 = (phi[1][id2]-phi[1][id0]); + const double d22 = (phi[2][id2]-phi[2][id0]); + const double d23 = (phi[3][id2]-phi[3][id0]); + const double d30 = (phi[0][id3]-phi[0][id0]); + const double d31 = (phi[1][id3]-phi[1][id0]); + const double d32 = (phi[2][id3]-phi[2][id0]); + const double d33 = (phi[3][id3]-phi[3][id0]); + + const double a00 = d11-d10; + const double a01 = d12-d10; + const double a02 = d13-d10; + const double a10 = d21-d20; + const double a11 = d22-d20; + const double a12 = d23-d20; + const double a20 = d31-d30; + const double a21 = d32-d30; + const double a22 = d33-d30; + const double b0 = -d10; + const double b1 = -d20; + const double b2 = -d30; + const double det = a00*(a22*a11-a21*a12)-a10*(a22*a01-a21*a02)+a20*(a12*a01-a11*a02); + const double x =( b0*(a22*a11-a21*a12)-b1*(a22*a01-a21*a02)+b2*(a12*a01-a11*a02))/det; + const double y =(-b0*(a22*a10-a20*a12)+b1*(a22*a00-a20*a02)-b2*(a12*a00-a10*a02))/det; + const double z =( b0*(a21*a10-a20*a11)-b1*(a21*a00-a20*a01)+b2*(a11*a00-a10*a01))/det; + + point = {{x,y,z}}; + return true; + } + + return false; +} + +void +SegmentLowerEnvelope::adjust_piecewise_linear_positions(Segment_Vector & segments, const std::vector & pos, const std::vector> & phi) +{ + if (pos.size() > 2) + { + // Even for piecewise edges, the structure of the phases must be no more complicated than that + // given by the linear version of the edge. So adjust the linear locations and then remove + // the degenerate, inverted segements. + + // Use piecewise approximation to find actual transition locations + for(Segment_Vector::iterator it = segments.begin(); it != segments.end()-1; ++it) + { + LS_Segment & cur = *it; + LS_Segment & next = *(it+1); + if (cur.ls_index() == next.ls_index()) continue; + InterfaceID iface(cur.ls_index(), next.ls_index()); + + const double crossing_pos = (find_crossing_position_and_sign(iface, pos, phi)).first; + ThrowRequire(crossing_pos >= 0.); + cur = LS_Segment(cur.left_endpoint(), crossing_pos, cur.ls_index()); + next = LS_Segment(crossing_pos, next.right_endpoint(), next.ls_index()); + } + + // Now remove inverted segments + if (segments.size() > 2) + { + for(Segment_Vector::iterator it = segments.begin()+1; it != segments.end()-1; ++it) + { + LS_Segment & prev = *(it-1); + LS_Segment & cur = *it; + LS_Segment & next = *(it+1); + + if (cur.right_endpoint() < cur.left_endpoint()) + { + InterfaceID iface(prev.ls_index(), next.ls_index()); + + const double crossing_pos = (find_crossing_position_and_sign(iface, pos, phi)).first; + ThrowRequire(crossing_pos >= 0.); + prev = LS_Segment(prev.left_endpoint(), cur.right_endpoint(), prev.ls_index()); + cur = LS_Segment(cur.right_endpoint(), crossing_pos, prev.ls_index()); + next = LS_Segment(crossing_pos, next.right_endpoint(), next.ls_index()); + } + } + } + } +} + +void +SegmentLowerEnvelope::collapse_identical_segments(Segment_Vector & segments) +{ + if (segments.size() > 1) + { + Segment_Vector uncollapsed; + uncollapsed.swap(segments); + segments.push_back(uncollapsed.front()); + for(Segment_Vector::const_iterator it = uncollapsed.begin()+1; it != uncollapsed.end(); ++it) + { + const LS_Segment & prev = *(it-1); + const LS_Segment & curr = *it; + if (prev.ls_index() == curr.ls_index()) + { + segments.back() = LS_Segment(segments.back().left_endpoint(), curr.right_endpoint(), prev.ls_index()); + } + else + { + segments.push_back(curr); + } + } + } +} + +void +SegmentLowerEnvelope::add_segment(Segment_Vector & segments, + const double x0, const std::vector & phi0, const int min0, + const double x1, const std::vector & phi1, const int min1) +{ + if (min0 == min1) + { + const LS_Segment segment(x0, x1, min0); + segments.push_back(segment); + return; + } + else + { + InterfaceID key(min0, min1); + const double ls0 = (phi0[key.first_ls()]-phi0[key.second_ls()]); // ls >= 0 for phi2 >= phi1 + const double ls1 = (phi1[key.first_ls()]-phi1[key.second_ls()]); + ThrowRequire(ls0*ls1 <= 0.); + const double cut = ls0 / (ls0 - ls1); + + { + const double x_cut = (1.-cut)*x0 + cut*x1; + ThrowAssert(phi0.size() == phi1.size()); + std::vector phiAtCut(phi0.size()); + for (unsigned i=0; i 1.-tol; + + if (isInfinitesmalCutAt0) + { + if (segments.empty()) + segments.emplace_back(0., 0., min0); + if (minCutMatchesEndPoint) + segments.emplace_back(x0, x1, min1); + else + add_segment(segments, x0, phiAtCut, minAtCut, x1, phi1, min1); + } + else if (isInfinitesmalCutAt1) + { + if (minCutMatchesEndPoint) + segments.emplace_back(x0, x1, min0); + else + add_segment(segments, x0, phi0, min0, x1, phiAtCut, minAtCut); + if (x1 == 1.) + { + if (segments.back().left_endpoint() == 1.) + segments.pop_back(); + segments.emplace_back(1., 1., min1); + } + } + else + { + if (minCutMatchesEndPoint) + segments.emplace_back(x0, x_cut, min0); + else + add_segment(segments, x0, phi0, min0, x_cut, phiAtCut, minAtCut); + + if (minCutMatchesEndPoint) + segments.emplace_back(x_cut, x1, min1); + else + add_segment(segments, x_cut, phiAtCut, minAtCut, x1, phi1, min1); + } + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_LowerEnvelope.hpp b/packages/krino/krino/krino_lib/Akri_LowerEnvelope.hpp new file mode 100644 index 000000000000..88cd8f6e6210 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_LowerEnvelope.hpp @@ -0,0 +1,120 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_LowerEnvelope_h +#define Akri_LowerEnvelope_h + +#include +#include + +#include + +namespace krino { + +class LS_Segment +{ +public: + LS_Segment(const double xL, const double xR, int id); + + double length() const { return x_right - x_left; } + int ls_index() const { return LS_index; } + double left_endpoint() const { return x_left; } + double right_endpoint() const { return x_right; } + + friend bool operator==(const LS_Segment & lhs, const LS_Segment & rhs); + friend std::ostream & operator << (std::ostream &os, const LS_Segment & segment); + +private: + double x_left; + double x_right; + int LS_index; +}; +typedef std::vector Segment_Vector; + +inline std::ostream & operator << (std::ostream &os, const LS_Segment & segment) +{ + os << "segment LS id = " << segment.LS_index + << " left point = " << segment.x_left + << " right point = " << segment.x_right; + return os; +} + +inline std::ostream & operator << (std::ostream &os, const Segment_Vector & segmentVec) +{ + os << "Segments: "; + for (auto && segment : segmentVec) + os << "{" << segment << "} "; + return os; +} + +std::pair +find_crossing_position_and_sign(const InterfaceID key, const std::vector & pos, const std::vector> & phi); + +bool find_lower_envelope_crossing_point(const std::array,2> & phi, double & x); + +bool find_lower_envelope_crossing_point(const std::array,3> & phi, std::array & point); + +bool find_lower_envelope_crossing_point(const std::array,4> & phi, std::array & point); + +int get_min(const std::vector & phi); + +class SegmentLowerEnvelope +{ +public: + // Must be larger than machine epsilon, but puts limit on smallest effective snap tolerance + static constexpr double MinSize() {return 1.e-12;}; + + // linear (2 points) + static Segment_Vector find_lower_envelope(const std::vector & phi0, + const std::vector & phi1) { SegmentLowerEnvelope env(0., phi0, 1., phi1); return env.get_segments(); } + static Segment_Vector find_lower_envelope(const double x0, const std::vector & phi0, + const double x1, const std::vector & phi1) { SegmentLowerEnvelope env(x0, phi0, x1, phi1); return env.get_segments(); } + // piecewise linear (>2 points) + static Segment_Vector find_lower_envelope(const std::vector & pos, const std::vector> & phi) + { SegmentLowerEnvelope env(pos,phi); return env.get_segments(); } + + SegmentLowerEnvelope(const std::vector & phi0, const std::vector & phi1) : SegmentLowerEnvelope(0., phi0, 1., phi1) {} + SegmentLowerEnvelope(const double x0, const std::vector & phi0, + const double x1, const std::vector & phi1) + { + add_segment(mySegments, x0, phi0, x1, phi1); + collapse_identical_segments(mySegments); + } + SegmentLowerEnvelope(const std::vector & pos, const std::vector> & phi) + { + ThrowRequire(pos.size() == phi.size() && pos.size() > 1); + add_segment(mySegments, pos.front(), phi.front(), pos.back(), phi.back()); + collapse_identical_segments(mySegments); + adjust_piecewise_linear_positions(mySegments, pos, phi); + } + const Segment_Vector & get_segments() const { return mySegments; } + +private: + static void add_segment(Segment_Vector & segments, + const double x0, const std::vector & phi0, + const double x1, const std::vector & phi1) + { + const int min0 = get_min(phi0); + const int min1 = get_min(phi1); + return add_segment(segments, x0, phi0, min0, x1, phi1, min1); + } + + static void add_segment(Segment_Vector & segments, + const double x0, const std::vector & phi0, const int min0, + const double x1, const std::vector & phi1, const int min1); + + static void collapse_identical_segments(Segment_Vector & segments); + + static void adjust_piecewise_linear_positions(Segment_Vector & segments, const std::vector & pos, const std::vector> & phi); + + Segment_Vector mySegments; +}; + +} // namespace krino + +#endif // Akri_LowerEnvelope_h diff --git a/packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.cpp b/packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.cpp new file mode 100644 index 000000000000..dad407903149 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.cpp @@ -0,0 +1,105 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +#include +#include +#include +#include + +namespace krino { + +const MasterElement& +MasterElementDeterminer::getMasterElement(stk::mesh::Bucket & bucket, FieldRef field) +{ + stk::topology field_topology = get_field_topology(bucket, field); + return MasterElementDeterminer::getMasterElement(field_topology); +} + +stk::topology +MasterElementDeterminer::get_field_topology(const stk::mesh::Bucket & b, const FieldRef field) +{ + // As an optimization, assume there is only 1 field topology active for a given mesh topology and field + typedef std::map, stk::topology> FieldAndMeshTopoToFieldTopoMap; + static FieldAndMeshTopoToFieldTopoMap field_topo_map; + + const stk::topology mesh_topology = b.topology(); + const unsigned field_ordinal = field.field().mesh_meta_data_ordinal(); + FieldAndMeshTopoToFieldTopoMap::iterator it = field_topo_map.find(std::make_pair(field_ordinal, mesh_topology)); + if (it != field_topo_map.end()) + { + return it->second; + } + + stk::mesh::MetaData & meta = field.field().mesh_meta_data(); + stk::topology field_topology = AuxMetaData::get(meta).get_nodal_field_topology(field, b); + + field_topo_map.insert(FieldAndMeshTopoToFieldTopoMap::value_type(std::make_pair(field_ordinal, mesh_topology), field_topology)); + return field_topology; +} + +const MasterElement & +MasterElementDeterminer::getMasterElement(stk::topology t) +{ + static std::vector> all_master_elems(stk::topology::BEGIN_TOPOLOGY + stk::topology::NUM_TOPOLOGIES); + std::unique_ptr & master_elem = all_master_elems[t()]; + if (nullptr == master_elem.get()) + { + std::unique_ptr basis; + switch(t()) + { + case stk::topology::LINE_2: + basis = std::make_unique(); + break; + case stk::topology::LINE_3: + basis = std::make_unique(); + break; + case stk::topology::TRI_3: + case stk::topology::TRI_3_2D: + basis = std::make_unique(); + break; + case stk::topology::TRI_6: + case stk::topology::TRI_6_2D: + basis = std::make_unique(); + break; + case stk::topology::QUAD_4: + case stk::topology::QUAD_4_2D: + basis = std::make_unique(); + break; + case stk::topology::QUAD_9: + case stk::topology::QUAD_9_2D: + basis = std::make_unique(); + break; + case stk::topology::TET_4: + basis = std::make_unique(); + break; + case stk::topology::TET_10: + basis = std::make_unique(); + break; + case stk::topology::HEX_8: + basis = std::make_unique(); + break; + case stk::topology::HEX_27: + basis = std::make_unique(); + break; + case stk::topology::WEDGE_6: + basis = std::make_unique(); + break; + default: + ThrowRuntimeError("Element topology not found in MasterElementDeterminer::getMasterElement: " << t.name()); + break; + } + master_elem = std::make_unique(t, std::move(basis)); + } + return *master_elem; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.hpp b/packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.hpp new file mode 100644 index 000000000000..af84aaea85f2 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MasterElementDeterminer.hpp @@ -0,0 +1,30 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MasterElementDeterminer_h +#define Akri_MasterElementDeterminer_h + +#include + +namespace krino { class FieldRef; } +namespace stk { namespace mesh { class Bucket; } } +namespace stk { class topology; } + +namespace krino { + +class MasterElementDeterminer +{ +public: + static const MasterElement& getMasterElement(stk::mesh::Bucket & bucket, FieldRef field); + static const MasterElement& getMasterElement(stk::topology topology); + static stk::topology get_field_topology(const stk::mesh::Bucket & b, const FieldRef field); +}; + +} // end namespace krino + +#endif // Akri_MasterElementDeterminer_h diff --git a/packages/krino/krino/krino_lib/Akri_MathUtil.cpp b/packages/krino/krino/krino_lib/Akri_MathUtil.cpp new file mode 100644 index 000000000000..f2a7d53a2b6f --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MathUtil.cpp @@ -0,0 +1,233 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +namespace krino { + +Vector3d +get_parametric_coordinates_of_point(const std::vector & nodeCoords, const Vector3d & pt) +{ + if (nodeCoords.size() == 3) + { + const Vector3d relativeCoords1 = nodeCoords[1] - nodeCoords[0]; + const Vector3d relativeCoords2 = nodeCoords[2] - nodeCoords[0]; + const Vector3d relativeCoords = pt - nodeCoords[0]; + + const double a00 = relativeCoords1[0]; + const double a01 = relativeCoords2[0]; + const double a10 = relativeCoords1[1]; + const double a11 = relativeCoords2[1]; + const double b0 = relativeCoords[0]; + const double b1 = relativeCoords[1]; + const double det = a00*a11-a01*a10; + const double x = (b0*a11-b1*a01)/det; + const double y = (-b0*a10+b1*a00)/det; + return Vector3d(x,y,0.); + } + else + { + const Vector3d relativeCoords1 = nodeCoords[1] - nodeCoords[0]; + const Vector3d relativeCoords2 = nodeCoords[2] - nodeCoords[0]; + const Vector3d relativeCoords3 = nodeCoords[3] - nodeCoords[0]; + const Vector3d relativeCoords = pt - nodeCoords[0]; + const double a00 = relativeCoords1[0]; + const double a01 = relativeCoords2[0]; + const double a02 = relativeCoords3[0]; + const double a10 = relativeCoords1[1]; + const double a11 = relativeCoords2[1]; + const double a12 = relativeCoords3[1]; + const double a20 = relativeCoords1[2]; + const double a21 = relativeCoords2[2]; + const double a22 = relativeCoords3[2]; + const double b0 = relativeCoords[0]; + const double b1 = relativeCoords[1]; + const double b2 = relativeCoords[2]; + const double det = a00*(a22*a11-a21*a12)-a10*(a22*a01-a21*a02)+a20*(a12*a01-a11*a02); + const double x =( b0*(a22*a11-a21*a12)-b1*(a22*a01-a21*a02)+b2*(a12*a01-a11*a02))/det; + const double y =(-b0*(a22*a10-a20*a12)+b1*(a22*a00-a20*a02)-b2*(a12*a00-a10*a02))/det; + const double z =( b0*(a21*a10-a20*a11)-b1*(a21*a00-a20*a01)+b2*(a11*a00-a10*a01))/det; + return Vector3d(x,y,z); + } +} + +std::pair find_root( const std::function & f, + const double xa, + const double xb, + const double fa, + const double fb, + const unsigned maxIters, + const double xTol) +{ + boost::uintmax_t iterCount = maxIters; + auto tol_function = [&xTol](const double a, const double b) { return std::abs(b-a) <= xTol; }; + auto result = boost::math::tools::toms748_solve(f, xa, xb, fa, fb, tol_function, iterCount); + const bool success = iterCount < maxIters; + return {success, 0.5*(result.first+result.second)}; +} + +std::pair find_bracketed_root_newton_raphson( const std::function(const double)> & f, + double x, + double fx, + double dfx, + double xa, + double xb, + double fa, + double fb, + const unsigned maxIters, + const double fTol) +{ + unsigned iter = 0; + while (iter++ < maxIters) + { + if (dfx == 0.) + { + x = 0.5*(xa+xb); // zero slope so use bisection + } + else + { + x -= fx / dfx; + if (x < xa || x > xb) + x = 0.5*(xa+xb); // Newton-Raphson step went out of bounds so use bisection + } + + std::tie(fx, dfx) = f(x); + + if (std::abs(fx) <= fTol) + return {true, x}; + + if (fx*fb < 0.) + { + xa = x; + fa = fx; + } + else + { + xb = x; + fb = fx; + } + + const double xTol = 4*std::numeric_limits::epsilon()*(fabs(xa)+fabs(xb)); + if (xb-xa < xTol) + return {false, x}; + } + return {true, x}; +} + +void attempt_to_bracket_root_newton_raphson( const std::function(const double)> & f, + const double guess, + double & x, + double & fx, + double & dfx, + double & xa, + double & xb, + double & fa, + double & fb, + bool & solnIsConvergedAtX, + bool & solnIsBracketed, + const unsigned maxIters, + const double fTol) +{ + x = guess; + std::tie(fx,dfx) = f(x); + + if (std::abs(fx) <= fTol) + { + solnIsConvergedAtX = true; + solnIsBracketed = false; + return; + } + + xa = x; + xb = x; + fa = fx; + fb = fx; + + unsigned iter = 0; + while (iter++ < maxIters) + { + if (dfx == 0.) + { + // FAILED: zero slope + solnIsConvergedAtX = false; + solnIsBracketed = false; + return; + } + + xa = x; + fa = fx; + + x -= fx / dfx; + std::tie(fx,dfx) = f(x); + + if (std::abs(fx) <= fTol) + { + solnIsConvergedAtX = true; + solnIsBracketed = false; + return; + } + + if (fa*fx > 0. && std::abs(fx) > std::abs(fa)) + { + // Jacobian is poor, try line search + const double tau = 0.5; + const int maxLineSearchIters = 5; + unsigned lineSearchIters = 0; + while (lineSearchIters++ < maxLineSearchIters && fa*fx > 0. && std::abs(fx) > std::abs(fa)) + { + x = xa + tau*(x-xa); + std::tie(fx,dfx) = f(x); + } + } + + if (fa*fx < 0.) + { + solnIsConvergedAtX = false; + solnIsBracketed = true; + xb = x; + fb = fx; + if (xa > xb) + { + std::swap(xa,xb); + std::swap(fa,fb); + } + return; + } + } + + //FAILED: did not bracket root + solnIsConvergedAtX = false; + solnIsBracketed = false; +} + +std::pair find_root_newton_raphson( const std::function(const double)> & f, + const double guess, + const unsigned maxIters, + const double fTol) +{ + double x, fx, dfx, xa, xb, fa, fb; + bool solnIsConvergedAtX, solnIsBracketed; + attempt_to_bracket_root_newton_raphson(f, guess, x, fx, dfx, xa, xb, fa, fb, solnIsConvergedAtX, solnIsBracketed, maxIters, fTol); + + if (solnIsConvergedAtX) + return {true, x}; + + if (!solnIsBracketed) + return {false, guess}; + + return find_bracketed_root_newton_raphson(f, x, fx, dfx, xa, xb, fa, fb, maxIters, fTol); +} + +} + + diff --git a/packages/krino/krino/krino_lib/Akri_MathUtil.hpp b/packages/krino/krino/krino_lib/Akri_MathUtil.hpp new file mode 100644 index 000000000000..6741d5bd9eac --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MathUtil.hpp @@ -0,0 +1,35 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_MATHUTIL_H_ +#define KRINO_INCLUDE_AKRI_MATHUTIL_H_ +#include +#include +#include + +namespace krino { + +Vector3d get_parametric_coordinates_of_point(const std::vector & nodeCoords, const Vector3d & pt); + +std::pair find_root( const std::function & f, + const double xa, + const double xb, + const double fa, + const double fb, + const unsigned maxIters = 100, + const double tol = 1.e-4); + +std::pair find_root_newton_raphson( const std::function(const double)> & f, + const double guess, + const unsigned maxIters = 100, + const double fTol = 1.e-4); + +} + + +#endif /* KRINO_INCLUDE_AKRI_MATHUTIL_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_MeshClone.cpp b/packages/krino/krino/krino_lib/Akri_MeshClone.cpp new file mode 100644 index 000000000000..49d9abaea644 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshClone.cpp @@ -0,0 +1,629 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace krino{ + +bool +MeshClone::stash_or_restore_mesh(stk::mesh::BulkData & mesh, const unsigned step_count) +{ /* %TRACE[ON]% */ Trace trace__("krino::MeshClone::stash_or_restore_mesh(tftk::mesh::Mesh &mesh)"); /* %TRACE% */ + + const auto empty_function = []() {}; + return stash_or_restore_mesh(mesh, step_count, empty_function, empty_function); +} + +bool +MeshClone::stash_or_restore_mesh(stk::mesh::BulkData & mesh, const unsigned step_count, + const std::function & notify_of_pre_mesh_modification, + const std::function & notify_of_post_mesh_modification) +{ /* %TRACE[ON]% */ Trace trace__("krino::MeshClone::stash_or_restore_mesh(tftk::mesh::Mesh &mesh)"); /* %TRACE% */ + bool restored_mesh = false; + MeshClone * clone = const_cast(mesh.mesh_meta_data().get_attribute()); + if (!clone) + { + clone = new MeshClone(mesh, sierra::Diag::sierraTimer(), step_count); + mesh.mesh_meta_data().declare_attribute_with_delete(clone); + } + else + { + if (step_count > clone->my_step_count) + { + if (!clone->mesh_is_up_to_date()) + { + clone->update(step_count); + } + } + else if (!clone->mesh_is_up_to_date()) + { + notify_of_pre_mesh_modification(); + clone->restore(step_count); + notify_of_post_mesh_modification(); + restored_mesh = true; + } + } + return restored_mesh; +} + +bool +MeshClone::exists(const stk::mesh::BulkData & mesh) +{ /* %TRACE[ON]% */ Trace trace__("krino::MeshClone::exists(stk::mesh::BulkData & mesh)"); /* %TRACE% */ + MeshClone * clone = const_cast(mesh.mesh_meta_data().get_attribute()); + return (clone != nullptr); +} + +MeshClone & +MeshClone::get(const stk::mesh::BulkData & mesh) +{ /* %TRACE[ON]% */ Trace trace__("krino::MeshClone::update_cloned_fields(stk::mesh::BulkData & mesh)"); /* %TRACE% */ + MeshClone * clone = const_cast(mesh.mesh_meta_data().get_attribute()); + ThrowRequireMsg(clone != nullptr, "Could not find MeshClone."); + return *clone; +} + +MeshClone::MeshClone( stk::mesh::BulkData & orig_mesh, stk::diag::Timer parent_timer, const unsigned step_count ) +: my_orig_mesh(&orig_mesh), + my_timer("Clone mesh", parent_timer), + my_step_count(step_count), + my_synchronized_count(orig_mesh.synchronized_count()) +{ + stk::diag::TimeBlock timer__(my_timer); + const stk::mesh::MetaData & in_meta = my_orig_mesh->mesh_meta_data(); + my_meta = std::make_unique(); + clone_meta_data_parts_and_fields(in_meta, *my_meta); + + my_mesh = std::make_unique(*my_meta, + my_orig_mesh->parallel(), + stk::mesh::BulkData::NO_AUTO_AURA +#ifdef SIERRA_MIGRATION + ,my_orig_mesh->add_fmwk_data() +#endif + ); + + my_meta->commit(); + + my_mesh->modification_begin(); + clone_bulk_data_entities(*my_orig_mesh, *my_mesh, false); + my_mesh->modification_end(); + + copy_field_data(*my_orig_mesh, *my_mesh); +} + +void MeshClone::update(const unsigned step_count) +{ /* %TRACE[ON]% */ Trace trace__("krino::MeshClone::update(const unsigned step_count)"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timer); + + const stk::mesh::BulkData & in_mesh = *my_orig_mesh; + stk::mesh::BulkData & out_mesh = *my_mesh; + + clone_mesh(in_mesh, out_mesh, false); + + my_step_count = step_count; + mark_mesh_as_up_to_date(); +} + +void MeshClone::restore(const unsigned step_count) +{ /* %TRACE[ON]% */ Trace trace__("krino::MeshClone::restore(const unsigned step_count)"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timer); + krinolog << "Restoring mesh from clone." << stk::diag::dendl; + + const stk::mesh::BulkData & in_mesh = *my_mesh; + stk::mesh::BulkData & out_mesh = *my_orig_mesh; + + clone_mesh(in_mesh, out_mesh, false); + + ThrowRequire(step_count == my_step_count); + mark_mesh_as_up_to_date(); +} + +void MeshClone::clone_mesh(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh, const bool full_overwrite) +{ /* %TRACE[ON]% */ Trace trace__("krino::MeshClone::clone_mesh(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh, const bool full_overwrite)"); /* %TRACE% */ + if (full_overwrite) + { + // Ugly, but legal and effective. + stk::mesh::MetaData & out_meta = out_mesh.mesh_meta_data(); + out_mesh.~BulkData(); + + const stk::mesh::BulkData::AutomaticAuraOption aura_option = + in_mesh.is_automatic_aura_on() ? + stk::mesh::BulkData::AUTO_AURA : + stk::mesh::BulkData::NO_AUTO_AURA; + + new (&out_mesh) stk::mesh::BulkData(out_meta, + in_mesh.parallel(), + aura_option +#ifdef SIERRA_MIGRATION + ,in_mesh.add_fmwk_data() +#endif + ); + + out_mesh.modification_begin(); + clone_bulk_data_entities(in_mesh, out_mesh, false); + out_mesh.modification_end(); + } + else + { + // I don't think you can start with clone_parallel because you can't know where something goes unless you know about it locally. + // Therefore I don't think that creation can go before deletion because you can't know if it already exists. + // Best to just delete everything wrong and then rebuild. + ThrowRequireMsg(out_mesh.modification_begin(), "MeshClone::restore must not be called within a modification cycle."); + delete_extraneous_entities(in_mesh, out_mesh); + // Occasionally, there is an issue with deleting and recreating the same entity within the same modification cycle, + // so we need to end here and begin again for the creation. + out_mesh.modification_end(); + + //Delete graph because the following mod cycle creates elements and faces in the same mode cycle. + out_mesh.delete_face_adjacent_element_graph(); + + out_mesh.modification_begin(); + clone_bulk_data_entities(in_mesh, out_mesh, true); + out_mesh.modification_end(); + } + + copy_field_data(in_mesh, out_mesh); +} + + +void +MeshClone::clone_meta_data_parts_and_fields(const stk::mesh::MetaData & in_meta, stk::mesh::MetaData & out_meta) +{ + /* %TRACE[ON]% */ Trace trace__("void MeshClone::clone_meta_data(stk::mesh::MetaData & in_meta)"); /* %TRACE% */ + + // This is pretty nasty. We want the part.mesh_meta_data_ordinal to be the same for the in_meta and out_meta. + // To accomplish this, we must be careful about the order of the part creation. + // Specifically, we must clone the parts that were created before in_meta.initialize() were called, then + // call out_meta.initialize(), then clone the rest. + + const stk::mesh::PartVector & in_parts = in_meta.get_parts(); + + unsigned ipart = 0; + bool more_to_do = ipart < in_parts.size(); + while (more_to_do) + { + stk::mesh::Part * in_part = in_parts[ipart++]; + if (stk::mesh::is_topology_root_part(*in_part)) + { + more_to_do = false; + } + else + { + more_to_do = ipart < in_parts.size(); + ThrowRequire(in_part->primary_entity_rank() == stk::topology::INVALID_RANK); + stk::mesh::Part & out_part = out_meta.declare_part(in_part->name()); + ThrowRequire(out_part.mesh_meta_data_ordinal() == in_part->mesh_meta_data_ordinal()); + } + } + + out_meta.initialize(in_meta.spatial_dimension(), in_meta.entity_rank_names()); + + for ( auto&& in_part : in_parts ) + { + stk::mesh::Part & out_part = + (in_part->primary_entity_rank() == stk::topology::INVALID_RANK) ? + out_meta.declare_part(in_part->name()) : + out_meta.declare_part(in_part->name(), in_part->primary_entity_rank(), in_part->force_no_induce()); + ThrowRequire(out_part.mesh_meta_data_ordinal() == in_part->mesh_meta_data_ordinal()); + if (stk::io::is_part_io_part(*in_part)) + { + stk::io::put_io_part_attribute(out_part); + } + } + + for ( auto&& in_part : in_parts ) + { + stk::mesh::Part & out_part = out_meta.get_part(in_part->mesh_meta_data_ordinal()); + const stk::mesh::PartVector & in_subsets = in_part->subsets(); + for (auto && in_subset : in_subsets) + { + stk::mesh::Part & out_subset = out_meta.get_part(in_subset->mesh_meta_data_ordinal()); + out_meta.declare_part_subset(out_part, out_subset); + } + } + + const stk::mesh::FieldVector & in_fields = in_meta.get_fields(); + for ( auto&& in_field : in_fields ) + { + if (in_field->state() == stk::mesh::StateNone) + { + stk::topology::rank_t entity_rank = static_cast(in_field->entity_rank()); + stk::mesh::FieldBase * out_field = out_meta.declare_field_base(in_field->name(), entity_rank, in_field->data_traits(), in_field->field_array_rank(), in_field->dimension_tags(), in_field->number_of_states()); + + for ( auto&& in_restriction : in_field->restrictions() ) + { + const stk::mesh::Selector out_selector = MeshClone::translate_selector(in_restriction.selector(), out_meta); + + out_meta.declare_field_restriction( *out_field, out_selector, in_restriction.num_scalars_per_entity(), in_restriction.dimension() ); + } + } + } +} + +void +MeshClone::delete_extraneous_entities(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh) +{ + /* %TRACE[ON]% */ Trace trace__("void MeshClone::delete_extraneous_entities(stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh)"); /* %TRACE% */ + + std::vector entities; + std::vector relatives; + std::vector relative_ordinals; + std::vector in_mesh_comm_procs; + std::vector out_mesh_comm_procs; + + const stk::mesh::EntityRank highest_entity_rank = static_cast(in_mesh.mesh_meta_data().entity_rank_count()-1); + for (stk::mesh::EntityRank entity_rank = highest_entity_rank; entity_rank >= stk::topology::NODE_RANK; --entity_rank) + { + stk::mesh::get_entities( out_mesh, entity_rank, entities ); + + for (auto&& out_entity : entities) + { + // Delete all entities for which any of the following are true: + // 1. There is no corresponding entity in in_mesh + // 2. The parallel owner changed. + // 3. The comm procs or comm shared procs differ. (Ugh. This is to work around strange corner case issues.) + // 4. The entity has invalid nodes (due to the nodes violating 1 or 2). + + stk::mesh::Entity in_entity = get_entity_on_other_mesh(out_mesh, out_entity, in_mesh); + bool delete_entity = !in_mesh.is_valid(in_entity); + + if (!delete_entity) + { + delete_entity = + in_mesh.parallel_owner_rank(in_entity) != out_mesh.parallel_owner_rank(out_entity); + } + + if (!delete_entity) + { + in_mesh.comm_procs(in_mesh.entity_key(in_entity), in_mesh_comm_procs); + out_mesh.comm_procs(out_mesh.entity_key(out_entity), out_mesh_comm_procs); + delete_entity = in_mesh_comm_procs != out_mesh_comm_procs; + } + + if (!delete_entity) + { + in_mesh.comm_shared_procs(in_entity, in_mesh_comm_procs); + out_mesh.comm_shared_procs(out_entity, out_mesh_comm_procs); + delete_entity = in_mesh_comm_procs != out_mesh_comm_procs; + } + + if (delete_entity) + { + ThrowRequireMsg(disconnect_and_destroy_entity(out_mesh, out_entity), "Could not destroy entity " << out_mesh.entity_key(out_entity) << debug_entity(out_mesh, out_entity)); + } + } + } + + for (stk::mesh::EntityRank entity_rank = stk::topology::ELEMENT_RANK; entity_rank > stk::topology::NODE_RANK; --entity_rank) + { + stk::mesh::get_entities( out_mesh, entity_rank, entities ); + + for (auto&& out_entity : entities) + { + const unsigned num_nodes = out_mesh.bucket(out_entity).topology().num_nodes(); + if (out_mesh.count_valid_connectivity(out_entity, stk::topology::NODE_RANK) != num_nodes) + { + ThrowRequireMsg(disconnect_and_destroy_entity(out_mesh, out_entity), "Could not destroy entity " << out_mesh.entity_key(out_entity)); + } + } + } +} + +void +MeshClone::delete_all_entities(stk::mesh::BulkData & mesh) +{ + /* %TRACE[ON]% */ Trace trace__("void MeshClone::delete_extraneous_entities(stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh)"); /* %TRACE% */ + + std::vector entities; + + const stk::mesh::EntityRank highest_entity_rank = static_cast(mesh.mesh_meta_data().entity_rank_count()-1); + for (stk::mesh::EntityRank entity_rank = highest_entity_rank; entity_rank >= stk::topology::NODE_RANK; --entity_rank) + { + stk::mesh::get_entities( mesh, entity_rank, entities ); + + for (auto && entity : entities) + { + ThrowRequireMsg(mesh.destroy_entity(entity), "Could not destroy entity " << mesh.entity_key(entity)); + } + } +} + +void +MeshClone::clone_bulk_data_entities(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh, const bool search_for_existing) +{ + /* %TRACE[ON]% */ Trace trace__("void MeshClone::clone_bulk_data(stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh)"); /* %TRACE% */ + + const bool debug = false; + + const stk::mesh::MetaData & in_meta = in_mesh.mesh_meta_data(); + const stk::mesh::MetaData & out_meta = out_mesh.mesh_meta_data(); + + std::vector sharing; + + const stk::mesh::EntityRank highest_entity_rank = static_cast(in_meta.entity_rank_count()-1); + for (stk::mesh::EntityRank entity_rank = stk::topology::NODE_RANK; entity_rank <= highest_entity_rank; ++entity_rank) + { + stk::mesh::Selector not_ghost = in_meta.locally_owned_part() | in_meta.globally_shared_part(); + const stk::mesh::BucketVector & buckets = in_mesh.get_buckets(entity_rank, not_ghost); + + for ( auto&& bucket_ptr : buckets ) + { + const stk::mesh::Bucket & b = *bucket_ptr; + + stk::mesh::PartVector in_bucket_parts, out_bucket_parts; + get_bucket_parts(b,in_bucket_parts); + + translate_parts(in_bucket_parts, out_meta, out_bucket_parts); + + const bool bucket_is_locally_owned = b.member(in_meta.locally_owned_part()); + + + const int length = b.size(); + for (int i = 0; i < length; ++i) + { + stk::mesh::Entity in_entity = b[i]; + + stk::mesh::Entity out_entity; + if (search_for_existing) + { + out_entity = out_mesh.get_entity( entity_rank, in_mesh.identifier(in_entity) ); + if (out_mesh.is_valid(out_entity) && + out_mesh.bucket(out_entity).member(out_meta.locally_owned_part())) + { + // make sure parts are right + stk::mesh::PartVector current_parts; + get_bucket_parts(out_mesh.bucket(out_entity), current_parts); + out_mesh.change_entity_parts(out_entity, out_bucket_parts, current_parts); + } + } + + // Unfortunately, there is a subtle bug that may be present in the input mesh that can cause a face to be present on + // this processor even when there are no locally owned elements using that face. We will skip these faces. + if (!bucket_is_locally_owned && entity_rank == in_meta.side_rank()) + { + bool found_locally_owned_elem = false; + const unsigned num_elems = in_mesh.num_elements(in_entity); + const stk::mesh::Entity* elems = in_mesh.begin_elements(in_entity); + for (unsigned ielem=0; ielem 1) + { + in_mesh.comm_shared_procs(in_entity,sharing); + for ( size_t k = 0 ; k < sharing.size() ; ++k ) + { + out_mesh.add_node_sharing(out_entity, sharing[k]); + } + } + + for (stk::mesh::EntityRank relative_rank = stk::topology::NODE_RANK; relative_rank < entity_rank; ++relative_rank) + { + const unsigned num_relatives = in_mesh.num_connectivity(in_entity, relative_rank); + const stk::mesh::Entity* in_relatives = in_mesh.begin(in_entity, relative_rank); + const stk::mesh::ConnectivityOrdinal * relative_ordinals = in_mesh.begin_ordinals(in_entity, relative_rank); + const stk::mesh::Permutation * relative_permutations = in_mesh.begin_permutations(in_entity, relative_rank); + + for (unsigned relative_index=0; relative_index( stk::mesh::field_data( out_field, b ) ); + + for (unsigned ib=0; ib( stk::mesh::field_data( in_field, in_entity ) ); + for ( unsigned i = 0; i < out_length; i++ ) out_data[i] = in_data[i]; + } + } +} + +void +MeshClone::copy_field_data(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh) +{ + /* %TRACE[ON]% */ Trace trace__("void MeshClone::copy_field_data(stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh)"); /* %TRACE% */ + + const stk::mesh::MetaData & in_meta = in_mesh.mesh_meta_data(); + stk::mesh::MetaData & out_meta = out_mesh.mesh_meta_data(); + + const stk::mesh::FieldVector & in_fields = in_meta.get_fields(); + const stk::mesh::FieldVector & out_fields = out_meta.get_fields(); + ThrowAssert(in_fields.size() == out_fields.size()); + + const bool out_mesh_aura_from_communication = out_mesh.is_automatic_aura_on() && !in_mesh.is_automatic_aura_on(); + + for ( unsigned field_index=0; field_index < in_fields.size(); ++field_index ) + { + const stk::mesh::FieldBase & in_field = *in_fields[field_index]; + const stk::mesh::FieldBase & out_field = *out_fields[field_index]; + copy_field_data(in_mesh, out_mesh, in_field, out_field, out_mesh_aura_from_communication); + } + + if (out_mesh_aura_from_communication) + { + const std::vector ghostings = out_mesh.ghostings(); + const std::vector const_fields(out_fields.begin(), out_fields.end()); + stk::mesh::communicate_field_data(*ghostings[stk::mesh::BulkData::AURA], const_fields); + } +} + +void +MeshClone::translate_parts(const stk::mesh::PartVector & in_parts, const stk::mesh::MetaData & out_meta, stk::mesh::PartVector & out_parts) +{ + out_parts.clear(); + for ( auto&& in_part : in_parts ) + { + stk::mesh::Part & out_part = out_meta.get_part(in_part->mesh_meta_data_ordinal()); + stk::mesh::insert(out_parts, out_part); + } +} + +stk::mesh::Selector +MeshClone::translate_selector(const stk::mesh::Selector & in_selector, const stk::mesh::MetaData & out_meta) +{ + if (in_selector == stk::mesh::Selector()) + { + return in_selector; + } + ThrowRequireMsg(in_selector.is_all_unions(), "Cannot translate selector " << in_selector); + stk::mesh::PartVector in_parts, out_parts; + in_selector.get_parts(in_parts); + translate_parts(in_parts, out_meta, out_parts); + return stk::mesh::selectUnion(out_parts); +} + +stk::mesh::Part * +MeshClone::translate_part(const stk::mesh::Part & in_part, const stk::mesh::MetaData & out_meta) +{ + return &out_meta.get_part(in_part.mesh_meta_data_ordinal()); +} + +void +MeshClone::get_bucket_parts(const stk::mesh::Bucket & bucket, stk::mesh::PartVector & parts) +{ + parts.clear(); + + stk::mesh::PartVector const& bucket_parts = bucket.supersets(); + for ( stk::mesh::PartVector::const_iterator ip = bucket_parts.begin(); ip != bucket_parts.end(); ++ip ) + { + stk::mesh::Part & bucket_part = **ip; + if (bucket_part.primary_entity_rank() != stk::topology::INVALID_RANK && bucket_part.primary_entity_rank() != bucket.entity_rank()) + { + continue; + } + if (stk::mesh::is_auto_declared_part(bucket_part) && !stk::mesh::is_topology_root_part(bucket_part)) + { + continue; + } + + parts.push_back(&bucket_part); + } + std::sort( parts.begin(), parts.end(), stk::mesh::PartLess() ); +} + +stk::mesh::Entity +MeshClone::get_entity_on_other_mesh(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const stk::mesh::BulkData & other_mesh) +{ + stk::mesh::EntityRank entity_rank = mesh.entity_rank(entity); + stk::mesh::Entity other_entity = other_mesh.get_entity( entity_rank, mesh.identifier(entity) ); + + if (other_mesh.is_valid(other_entity) && entity_rank != stk::topology::NODE_RANK) + { + // check if nodes are the same + const unsigned num_entity_nodes = mesh.num_nodes(entity); + const unsigned num_other_entity_nodes = other_mesh.num_nodes(other_entity); + if (num_entity_nodes != num_other_entity_nodes) + { + return stk::mesh::Entity(); + } + const stk::mesh::Entity* entity_nodes = mesh.begin_nodes(entity); + const stk::mesh::Entity* other_entity_nodes = other_mesh.begin_nodes(other_entity); + for (unsigned n=0; n +#include +#include +#include + +namespace krino { + +class MeshClone { +public: + + MeshClone( stk::mesh::BulkData & orig_mesh, stk::diag::Timer parent_timer, const unsigned step_count = 0 ); + + static bool exists(const stk::mesh::BulkData & mesh); + static MeshClone & get(const stk::mesh::BulkData & mesh); + static bool stash_or_restore_mesh(stk::mesh::BulkData & mesh, const unsigned step_count); + static bool stash_or_restore_mesh(stk::mesh::BulkData & mesh, + const unsigned step_count, + const std::function & notify_of_pre_mesh_modification, + const std::function & notify_of_post_mesh_modification); + bool mesh_is_up_to_date() const {return my_orig_mesh->synchronized_count() == my_synchronized_count;} + void mark_mesh_as_up_to_date() {my_synchronized_count = my_orig_mesh->synchronized_count();} + void mark_mesh_as_out_of_date() {--my_synchronized_count;} + +private: + void update(const unsigned step_count = 0); + void restore(const unsigned step_count = 0); + + static void clone_meta_data_parts_and_fields(const stk::mesh::MetaData & in_meta, stk::mesh::MetaData & out_meta); + static void translate_parts(const stk::mesh::PartVector & in_parts, const stk::mesh::MetaData & out_meta, stk::mesh::PartVector & out_parts); + static stk::mesh::Part * translate_part(const stk::mesh::Part & in_part, const stk::mesh::MetaData & out_meta); + static stk::mesh::Selector translate_selector(const stk::mesh::Selector & in_selector, const stk::mesh::MetaData & out_meta); + static void get_bucket_parts(const stk::mesh::Bucket & bucket, stk::mesh::PartVector & parts); + + static void clone_mesh(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh, const bool full_overwrite=false); + static void clone_bulk_data(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh); + static void copy_field_data(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh, const stk::mesh::FieldBase & in_field, const stk::mesh::FieldBase & out_field, const bool out_mesh_aura_from_communication); + static void copy_field_data(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh); + + static void delete_extraneous_entities(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh); + static void delete_all_entities(stk::mesh::BulkData & mesh); + static void clone_bulk_data_entities(const stk::mesh::BulkData & in_mesh, stk::mesh::BulkData & out_mesh, const bool search_for_existing); + + static stk::mesh::Entity get_entity_on_other_mesh(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const stk::mesh::BulkData & other_mesh); + + stk::mesh::BulkData* my_orig_mesh; + std::unique_ptr my_meta; + std::unique_ptr my_mesh; + + mutable stk::diag::Timer my_timer; + unsigned my_step_count; + size_t my_synchronized_count; +}; + +} // namespace krino + +#endif // Akri_MeshClone_h diff --git a/packages/krino/krino/krino_lib/Akri_MeshDiagnostics.cpp b/packages/krino/krino/krino_lib/Akri_MeshDiagnostics.cpp new file mode 100644 index 000000000000..e030340d2567 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshDiagnostics.cpp @@ -0,0 +1,70 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace krino{ + +void print_volume_or_surface_area(const stk::mesh::BulkData& mesh, const stk::mesh::EntityRank entity_rank, const stk::mesh::Selector & active_selector, const stk::mesh::PartVector & parts) +{ + double overall_sum = 0.; + + const std::string label = (entity_rank == stk::topology::ELEMENT_RANK) ? "Volume" : "Surface Area"; + + krinolog << stk::diag::dendl; + + for (auto && part : parts) + { + const stk::mesh::Selector selector = mesh.mesh_meta_data().locally_owned_part() & active_selector & *part; + const double local_part_sum = compute_volume_or_surface_area(mesh, entity_rank, selector); + double global_part_sum = 0.; + + stk::all_reduce_sum(mesh.parallel(), &local_part_sum, &global_part_sum, 1); + overall_sum += global_part_sum; + + krinolog << label << " for part " << part->name() << " = " << global_part_sum << stk::diag::dendl; + } + + krinolog << "Sum of " << label << " for all parts (which may overlap) = " << overall_sum << stk::diag::dendl; +} + +double +compute_volume_or_surface_area(const stk::mesh::BulkData& mesh, const stk::mesh::EntityRank entity_rank, const stk::mesh::Selector & selector) +{ + const FieldRef coords_field(mesh.mesh_meta_data().coordinate_field()); + const unsigned dim = mesh.mesh_meta_data().spatial_dimension(); + + std::vector coords; + std::vector intg_weights; + + double volume_or_area = 0.; + for ( auto && bucket : mesh.get_buckets( entity_rank, selector ) ) + { + const krino::MasterElement& master_elem = MasterElementDeterminer::getMasterElement(bucket->topology()); + + for ( auto && entity : *bucket ) + { + ElementObj::gather_nodal_field(mesh, entity, coords_field, coords, dim); + ElementObj::integration_weights( intg_weights, dim, coords, master_elem ); + + for ( double intg_wt : intg_weights ) volume_or_area += intg_wt; + } + } + return volume_or_area; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_MeshDiagnostics.hpp b/packages/krino/krino/krino_lib/Akri_MeshDiagnostics.hpp new file mode 100644 index 000000000000..668e904d417f --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshDiagnostics.hpp @@ -0,0 +1,23 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MeshDiagnostics_h +#define Akri_MeshDiagnostics_h + +#include +#include +#include + +namespace krino { + +void print_volume_or_surface_area(const stk::mesh::BulkData& mesh, const stk::mesh::EntityRank entity_rank, const stk::mesh::Selector & active_selector, const stk::mesh::PartVector & parts); +double compute_volume_or_surface_area(const stk::mesh::BulkData& mesh, const stk::mesh::EntityRank entity_rank, const stk::mesh::Selector & selector); + +} // namespace krino + +#endif // Akri_MeshDiagnostics_h diff --git a/packages/krino/krino/krino_lib/Akri_MeshHelpers.cpp b/packages/krino/krino/krino_lib/Akri_MeshHelpers.cpp new file mode 100644 index 000000000000..f701face6949 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshHelpers.cpp @@ -0,0 +1,2560 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include // Needed for all_reduce_max +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino{ + +template +class ContainerResizer +{ +public: + ContainerResizer(CONTAINER & container) : myContainer(container) {} + void resize(size_t size) { myContainer.resize(size); } +private: + CONTAINER & myContainer; +}; + +template +class ContainerResizer> +{ +public: + ContainerResizer(std::array & container) : myContainer(container) {} + void resize(size_t size) { ThrowRequire(SIZE == size); } +private: + std::array & myContainer; +}; + +template +void resize_container(CONTAINER & container, size_t size) +{ + ContainerResizer resizer(container); + resizer.resize(size); +} + +void fill_procs_owning_or_sharing_or_ghosting_node(const stk::mesh::BulkData& bulkData, stk::mesh::Entity node, std::vector & procsOwningSharingOrGhostingNode) +{ + ThrowAssert(bulkData.parallel_owner_rank(node)==bulkData.parallel_rank()); + bulkData.comm_procs(bulkData.entity_key(node), procsOwningSharingOrGhostingNode); + procsOwningSharingOrGhostingNode.push_back(bulkData.parallel_rank()); +} + +//-------------------------------------------------------------------------------- + +template +void fill_element_node_coordinates(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coordsField, CONTAINER & elementNodeCoords) +{ + StkMeshEntities elementNodes{mesh.begin_nodes(element), mesh.end_nodes(element)}; + resize_container(elementNodeCoords, elementNodes.size()); + for (size_t n=0; n(coordsField, elementNodes[n]); + for ( int d = 0; d < DIM; ++d ) + elementNodeCoords[n][d] = coords[d]; + } +} + +void fill_element_node_coordinates(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coordsField, const int dim, std::vector & elementNodeCoords) +{ + ThrowAssert(dim == 3 || dim == 2); + if (dim == 2) + fill_element_node_coordinates<2>(mesh, element, coordsField, elementNodeCoords); + else + fill_element_node_coordinates<3>(mesh, element, coordsField, elementNodeCoords); +} + +void fill_element_node_coordinates(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coordsField, std::vector & elementNodeCoords) +{ + const int dim = mesh.mesh_meta_data().spatial_dimension(); + elementNodeCoords.clear(); + for (auto node : StkMeshEntities{mesh.begin_nodes(element), mesh.end_nodes(element)}) + elementNodeCoords.emplace_back(field_data(coordsField, node), dim); +} + +static std::array gather_tet_coordinates(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coordsField) +{ + ThrowAssert(mesh.bucket(element).topology() == stk::topology::TETRAHEDRON_4); + std::array elementNodeCoords; + fill_element_node_coordinates<3>(mesh, element, coordsField, elementNodeCoords); + return elementNodeCoords; +} + +static std::array gather_tri_coordinates(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coordsField) +{ + ThrowAssert(mesh.bucket(element).topology() == stk::topology::TRIANGLE_3_2D); + std::array elementNodeCoords; + fill_element_node_coordinates<2>(mesh, element, coordsField, elementNodeCoords); + return elementNodeCoords; +} + +static double compute_tri_volume(const std::array & elementNodeCoords) +{ + return 0.5*(Cross(elementNodeCoords[1]-elementNodeCoords[0], elementNodeCoords[2]-elementNodeCoords[0]).length()); +} + +static double compute_tet_volume(const std::array & elementNodeCoords) +{ + return Dot(elementNodeCoords[3]-elementNodeCoords[0],Cross(elementNodeCoords[1]-elementNodeCoords[0], elementNodeCoords[2]-elementNodeCoords[0]))/6.0; +} + +static double compute_tri_or_tet_volume(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coordsField) +{ + stk::topology elemTopology = mesh.bucket(element).topology(); + + if (elemTopology == stk::topology::TETRAHEDRON_4) + { + const auto elementNodeCoords = gather_tet_coordinates(mesh, element, coordsField); + return compute_tet_volume(elementNodeCoords); + } + + ThrowRequireMsg(elemTopology == stk::topology::TRIANGLE_3_2D, "Topology " << elemTopology << " not supported in compute_tri_or_tet_volume."); + const auto elementNodeCoords = gather_tri_coordinates(mesh, element, coordsField); + return compute_tri_volume(elementNodeCoords); +} + +static void update_min_max_values(const double currentValue, double & minValue, double & maxValue) +{ + minValue = std::min(minValue, currentValue); + maxValue = std::max(maxValue, currentValue); +} + +template +void update_min_max_edge_lengths_squared(const CONTAINER & elementNodeCoords, double & minEdgeLengthSqr, double & maxEdgeLengthSqr) +{ + for ( size_t inode = 0; inode < elementNodeCoords.size(); ++inode ) + for ( size_t jnode = inode+1; jnode < elementNodeCoords.size(); ++jnode ) + update_min_max_values((elementNodeCoords[inode] - elementNodeCoords[jnode]).length_squared(), minEdgeLengthSqr, maxEdgeLengthSqr); +} + +template +void update_max_edge_lengths_squared(const CONTAINER & elementNodeCoords, double & maxEdgeLengthSqr) +{ + // This is a little strange for non-simplex elements since it goes from each node to every other node + for ( size_t inode = 0; inode < elementNodeCoords.size(); ++inode ) + for ( size_t jnode = inode+1; jnode < elementNodeCoords.size(); ++jnode ) + maxEdgeLengthSqr = std::max(maxEdgeLengthSqr, (elementNodeCoords[inode] - elementNodeCoords[jnode]).length_squared()); +} + +//-------------------------------------------------------------------------------- + +double +compute_maximum_element_size(stk::mesh::BulkData& mesh) +{ + const unsigned ndim = mesh.mesh_meta_data().spatial_dimension(); + double max_sqr_edge_length = 0.0; + + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + + stk::mesh::Selector locally_owned_selector = mesh.mesh_meta_data().locally_owned_part(); + + const stk::mesh::BucketVector & buckets = mesh.get_buckets( stk::topology::ELEMENT_RANK, locally_owned_selector ); + std::vector elementNodeCoords; + + for ( auto && bucket : buckets ) + { + for ( auto && elem : *bucket ) + { + fill_element_node_coordinates(mesh, elem, coordsField, ndim, elementNodeCoords); + update_max_edge_lengths_squared(elementNodeCoords, max_sqr_edge_length); + } + } + + const double local_max = max_sqr_edge_length; + stk::all_reduce_max(mesh.parallel(), &local_max, &max_sqr_edge_length, 1); + + return std::sqrt(max_sqr_edge_length); +} + +//-------------------------------------------------------------------------------- + +void compute_element_quality(const stk::mesh::BulkData & mesh, double & minEdgeLength, double & maxEdgeLength, double & minVolume, double & maxVolume) +{ + double minEdgeLengthSqr = std::numeric_limits::max(); + double maxEdgeLengthSqr = -std::numeric_limits::max(); + minVolume = std::numeric_limits::max(); + maxVolume = std::numeric_limits::lowest(); + + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + + stk::mesh::Selector locally_owned_selector = mesh.mesh_meta_data().locally_owned_part(); + + const stk::mesh::BucketVector & buckets = mesh.get_buckets( stk::topology::ELEMENT_RANK, locally_owned_selector ); + + for ( auto && bucket : buckets ) + { + stk::topology elem_topology = bucket->topology(); + const unsigned num_nodes = elem_topology.num_nodes(); + std::vector elem_node_coords(num_nodes); + + for ( auto && elem : *bucket ) + { + if (elem_topology == stk::topology::TETRAHEDRON_4) + { + const auto elementNodeCoords = gather_tet_coordinates(mesh, elem, coordsField); + update_min_max_values(compute_tet_volume(elementNodeCoords), minVolume, maxVolume); + update_min_max_edge_lengths_squared(elementNodeCoords, minEdgeLengthSqr, maxEdgeLengthSqr); + } + else if (elem_topology == stk::topology::TRIANGLE_3_2D) + { + const auto elementNodeCoords = gather_tri_coordinates(mesh, elem, coordsField); + update_min_max_values(compute_tri_volume(elementNodeCoords), minVolume, maxVolume); + update_min_max_edge_lengths_squared(elementNodeCoords, minEdgeLengthSqr, maxEdgeLengthSqr); + } + else + { + ThrowRuntimeError("Topology " << elem_topology << " not supported in compute_element_quality."); + } + } + } + + const double localMinEdgeLength = std::sqrt(minEdgeLengthSqr); + stk::all_reduce_min(mesh.parallel(), &localMinEdgeLength, &minEdgeLength, 1); + const double localMaxEdgeLength = std::sqrt(maxEdgeLengthSqr); + stk::all_reduce_max(mesh.parallel(), &localMaxEdgeLength, &maxEdgeLength, 1); + const double localMinVolume = minVolume; + stk::all_reduce_min(mesh.parallel(), &localMinVolume, &minVolume, 1); + const double localMaxVolume = maxVolume; + stk::all_reduce_max(mesh.parallel(), &localMaxVolume, &maxVolume, 1); +} + +static std::vector get_owned_nodes_with_nodal_volume_below_threshold(const stk::mesh::BulkData & mesh, const stk::mesh::Selector & blockSelector, const double threshold) +{ + ThrowRequireMsg(mesh.is_automatic_aura_on() || mesh.parallel_size() == 1, "Method requires automatic aura."); + + // This would be more efficient if a nodal field was used because it could compute the element volume only once. + std::vector ownedNodesWithNodalVolBelowThreshold; + + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + std::vector elementNodeCoords; + + stk::mesh::Selector ownedBlockSelector = mesh.mesh_meta_data().locally_owned_part() & blockSelector; + + const stk::mesh::BucketVector & buckets = mesh.get_buckets( stk::topology::NODE_RANK, ownedBlockSelector ); + + for ( auto && bucket : buckets ) + { + for ( auto && node : *bucket ) + { + double nodalVolume = 0.; + for (auto && element : StkMeshEntities{mesh.begin_elements(node), mesh.end_elements(node)}) + { + if (blockSelector(mesh.bucket(element))) + nodalVolume += compute_tri_or_tet_volume(mesh, element, coordsField); + + if (nodalVolume >= threshold) + break; + } + if (nodalVolume < threshold) + ownedNodesWithNodalVolBelowThreshold.push_back(node); + } + } + return ownedNodesWithNodalVolBelowThreshold; +} + +static std::vector get_nodes_with_no_attached_elements(const stk::mesh::BulkData & mesh) +{ + ThrowRequireMsg(mesh.is_automatic_aura_on() || mesh.parallel_size() == 1, "Method requires automatic aura."); + + std::vector nodesWithNoAttachedElements; + + for ( auto && bucket : mesh.buckets(stk::topology::NODE_RANK) ) + for ( auto && node : *bucket ) + if (mesh.num_elements(node) == 0) + nodesWithNoAttachedElements.push_back(node); + + return nodesWithNoAttachedElements; +} + +static +void pack_entities_for_sharing_procs(const stk::mesh::BulkData & mesh, + const std::vector & entities, + stk::CommSparse &commSparse) +{ + std::vector sharingProcs; + stk::pack_and_communicate(commSparse,[&]() + { + for (auto entity : entities) + { + if (mesh.bucket(entity).shared()) + { + mesh.comm_shared_procs(entity, sharingProcs); + for (int procId : sharingProcs) + commSparse.send_buffer(procId).pack(mesh.entity_key(entity)); + } + } + }); +} + +static +void unpack_shared_entities(const stk::mesh::BulkData & mesh, + std::vector & sharedEntities, + stk::CommSparse &commSparse) +{ + stk::unpack_communications(commSparse, [&](int procId) + { + stk::CommBuffer & buffer = commSparse.recv_buffer(procId); + + while ( buffer.remaining() ) + { + stk::mesh::EntityKey entityKey; + commSparse.recv_buffer(procId).unpack(entityKey); + stk::mesh::Entity entity = mesh.get_entity(entityKey); + ThrowAssert(mesh.is_valid(entity)); + sharedEntities.push_back(entity); + } + }); +} + +static +void append_shared_entities_to_owned_ones(const stk::mesh::BulkData & mesh, + std::vector & entities) +{ + stk::CommSparse commSparse(mesh.parallel()); + pack_entities_for_sharing_procs(mesh, entities, commSparse); + + std::vector sharedEntities; + unpack_shared_entities(mesh, sharedEntities, commSparse); + entities.insert(entities.end(), sharedEntities.begin(), sharedEntities.end()); +} + +//-------------------------------------------------------------------------------- + +void delete_node_and_all_entities_using_it(stk::mesh::BulkData & mesh, const stk::mesh::Entity node) +{ + std::vector relatives; + + const stk::mesh::EntityRank highestEntityRank = static_cast(mesh.mesh_meta_data().entity_rank_count()-1); + for (stk::mesh::EntityRank irank = highestEntityRank; irank != stk::topology::NODE_RANK; --irank) + { + relatives.assign(mesh.begin(node, irank), mesh.end(node, irank)); + for (auto && relative : relatives) + ThrowRequire(mesh.destroy_entity(relative)); + } + ThrowRequire(mesh.destroy_entity(node)); +} + +static void delete_nodes_and_all_entities_using_them(stk::mesh::BulkData & mesh, const std::vector & nodesToDelete) +{ + mesh.modification_begin(); + for (auto && node : nodesToDelete) + delete_node_and_all_entities_using_it(mesh, node); + mesh.modification_end(); +} + +static size_t delete_nodes_with_nodal_volume_below_threshold_and_all_entities_using_them(stk::mesh::BulkData & mesh, const stk::mesh::Selector & blockSelector, const double threshold) +{ + std::vector nodesToDelete = get_owned_nodes_with_nodal_volume_below_threshold(mesh, blockSelector, threshold); + const size_t globalNumNodesToDelete = stk::get_global_sum(mesh.parallel(), nodesToDelete.size()); + + if (globalNumNodesToDelete > 0) + { + append_shared_entities_to_owned_ones(mesh, nodesToDelete); + delete_nodes_and_all_entities_using_them(mesh, nodesToDelete); + } + return globalNumNodesToDelete; +} + +static size_t delete_nodes_with_no_attached_elements(stk::mesh::BulkData & mesh) +{ + const std::vector nodesToDelete = get_nodes_with_no_attached_elements(mesh); + const size_t globalNumNodesToDelete = stk::get_global_sum(mesh.parallel(), nodesToDelete.size()); + + if (globalNumNodesToDelete > 0) + delete_nodes_and_all_entities_using_them(mesh, nodesToDelete); + + return globalNumNodesToDelete; +} + +void delete_all_entities_using_nodes_with_nodal_volume_below_threshold(stk::mesh::BulkData & mesh, const stk::mesh::Selector & blockSelector, const double threshold) +{ + const int maxIterations = 10; + int iteration = 0; + while (++iteration <= maxIterations) + { + const size_t numNodesDeletedWithSmallVolume = delete_nodes_with_nodal_volume_below_threshold_and_all_entities_using_them(mesh, blockSelector, threshold); + if (numNodesDeletedWithSmallVolume > 0) + { + sierra::Env::outputP0() << "Iteration " << iteration << ":" << std::endl; + sierra::Env::outputP0() << " Deleted " << numNodesDeletedWithSmallVolume << " node(s) with a nodal volume less than " << threshold << " (and all attached entities)." << std::endl; + const size_t numNodesDeletedWithNoElements = delete_nodes_with_no_attached_elements(mesh); + sierra::Env::outputP0() << " Deleted " << numNodesDeletedWithNoElements << " node(s) that have no attached elements." << std::endl; + } + else + break; + } + if (iteration < maxIterations) + sierra::Env::outputP0() << "Successfully deleted all nodes with a nodal volume less than " << threshold << "." << std::endl; + else + sierra::Env::outputP0() << "Terminating after performing max iterations. There still may be nodes with a nodal volume less than " << threshold << "." << std::endl; +} + +//-------------------------------------------------------------------------------- + +struct ActiveChildNodeRequest +{ + std::vector m_parents; + std::vector m_sharing_procs; + std::vector > m_id_proc_pairs_from_all_procs; + bool m_id_procs_pairs_have_been_sorted; + stk::mesh::Entity *m_node_entity; + + ActiveChildNodeRequest(const std::vector & parents, stk::mesh::Entity *entity_place_holder=NULL) + : m_parents(parents), m_sharing_procs(), m_id_proc_pairs_from_all_procs(), + m_id_procs_pairs_have_been_sorted(false), m_node_entity(entity_place_holder) + { + std::sort(m_parents.begin(), m_parents.end()); + } + + void add_proc_id_pair(int proc_id, stk::mesh::EntityId id) + { + m_id_proc_pairs_from_all_procs.push_back(std::make_pair(proc_id, id)); + } + + void calculate_sharing_procs(stk::mesh::BulkData& mesh) + { + ThrowRequire(!m_parents.empty()); + + stk::mesh::EntityKey key0(stk::topology::NODE_RANK, m_parents[0]); + mesh.comm_shared_procs(key0, m_sharing_procs); + std::sort(m_sharing_procs.begin(), m_sharing_procs.end()); + + std::vector sharingProcs; + for (unsigned i=1; i working_set; + m_sharing_procs.swap(working_set); + std::set_intersection(working_set.begin(),working_set.end(),sharingProcs.begin(),sharingProcs.end(),std::back_inserter(m_sharing_procs)); + } + } + + size_t num_sharing_procs() const + { + return m_sharing_procs.size(); + } + + int sharing_proc(int index) const + { + return m_sharing_procs[index]; + } + + stk::mesh::EntityId suggested_node_id() const + { + ThrowRequireMsg(!m_id_procs_pairs_have_been_sorted, "Invalid use of child node calculation. Contact sierra-help"); + return m_id_proc_pairs_from_all_procs[0].second; + } + + void sort_id_proc_pairs() + { + m_id_procs_pairs_have_been_sorted = true; + std::sort(m_id_proc_pairs_from_all_procs.begin(), m_id_proc_pairs_from_all_procs.end()); + } + + stk::mesh::EntityId get_id_for_child() const + { + ThrowRequireMsg(m_id_procs_pairs_have_been_sorted, "Invalid use of child node calculation. Contact sierra-help"); + return m_id_proc_pairs_from_all_procs[0].second; + } + + void set_node_entity_for_request(stk::mesh::BulkData& mesh, const stk::mesh::PartVector & node_parts) + { + this->sort_id_proc_pairs(); + stk::mesh::EntityId id_for_child = get_id_for_child(); + *m_node_entity = mesh.declare_node(id_for_child, node_parts); + for (size_t i=0;i & child_node_requests, const stk::mesh::PartVector & node_parts, bool assert_32bit_ids, bool make_64bit_ids) +{ + std::vector communicate_request(child_node_requests.size(), false); + + unsigned num_nodes_requested = child_node_requests.size(); + std::vector available_node_ids; + + EntityIdPool::generate_new_ids(mesh, stk::topology::NODE_RANK, num_nodes_requested, available_node_ids, assert_32bit_ids, make_64bit_ids); + + while ( true ) + { + int more_work_to_be_done = false; + + std::vector active_child_node_requests; + + for (unsigned it_req=0; it_req & request_parents = request.parents; + + bool request_is_ready = true; + for (auto && request_parent : request_parents) + { + if (!mesh.is_valid(*request_parent)) + { + request_is_ready = false; + } + } + + if (request_is_ready) + { + stk::mesh::Entity *request_child = request.child; + + communicate_request[it_req] = true; + more_work_to_be_done = true; + + std::vector parent_ids(request_parents.size()); + for (size_t parent_index=0; parent_index & request_parents = active_child_node_requests[request_index].m_parents; + const stk::mesh::EntityId this_procs_suggested_id = active_child_node_requests[request_index].suggested_node_id(); + const size_t num_parents = request_parents.size(); + comm_spec.send_buffer(other_proc).pack(num_parents); + for (size_t parent_index=0; parent_index request_parents(num_parents); + for (size_t parent_index=0; parent_index::iterator iter = std::lower_bound(active_child_node_requests.begin(), active_child_node_requests.end(), from_other_proc); + + if ( iter != active_child_node_requests.end() && *iter == from_other_proc) + { + iter->add_proc_id_pair(i, suggested_node_id); + } + } + } + } + + for (size_t request_index=0;request_index::iterator iter = std::find(communicate_request.begin(), communicate_request.end(), false); + ThrowRequireMsg(iter == communicate_request.end(), "Invalid child node request. Contact sierra-help."); +} + +//-------------------------------------------------------------------------------- + +void +batch_create_sides(stk::mesh::BulkData & mesh, const std::vector & side_requests) +{ + const stk::mesh::EntityRank side_rank = mesh.mesh_meta_data().side_rank(); + + if (!mesh.has_face_adjacent_element_graph()) + { + mesh.initialize_face_adjacent_element_graph(); + } + + mesh.modification_begin(); + for (auto && side_request : side_requests) + { + stk::mesh::Entity element = side_request.element; + const unsigned element_side_ord = side_request.element_side_ordinal; + + stk::mesh::Entity existing_element_side = find_entity_by_ordinal(mesh, element, side_rank, element_side_ord); + if (mesh.is_valid(existing_element_side)) + { + continue; + } + + mesh.declare_element_side(element, element_side_ord, side_request.side_parts); + } + mesh.modification_end(); + + ThrowAssert(check_face_and_edge_ownership(mesh)); + ThrowAssert(check_face_and_edge_relations(mesh)); +} + +//-------------------------------------------------------------------------------- + +void +make_side_ids_consistent_with_stk_convention(stk::mesh::BulkData & mesh) +{ + const stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + const stk::mesh::EntityRank side_rank = meta.side_rank(); + stk::mesh::Selector not_ghost_selector(meta.locally_owned_part() | meta.globally_shared_part()); + + std::vector sides; + stk::mesh::get_selected_entities( not_ghost_selector, mesh.buckets(side_rank), sides ); + + std::vector side_requests; + + mesh.modification_begin(); + for (auto&& side : sides) + { + const unsigned num_side_elems = mesh.num_elements(side); + const stk::mesh::Entity* side_elems = mesh.begin_elements(side); + const stk::mesh::ConnectivityOrdinal* side_elem_ordinals = mesh.begin_element_ordinals(side); + ThrowRequire(num_side_elems > 0); + stk::mesh::EntityId newId = 0; + for (unsigned i=0; i * const coordsField) +{ + stk::topology elem_topology = mesh.bucket(element).topology(); + if (elem_topology == stk::topology::TETRAHEDRON_4) + { + const std::array nodes = gather_tet_coordinates(mesh, element, coordsField); + const double vol = compute_tet_volume(nodes); + compute_tri_or_tet_volume(mesh, element, *coordsField); + const double edge_rms = std::sqrt( + ((nodes[1]-nodes[0]).length_squared() + + (nodes[2]-nodes[0]).length_squared() + + (nodes[3]-nodes[0]).length_squared() + + (nodes[3]-nodes[1]).length_squared() + + (nodes[3]-nodes[2]).length_squared() + + (nodes[2]-nodes[1]).length_squared())/6.); + return vol/(edge_rms*edge_rms*edge_rms); + } + ThrowRuntimeError("Topology " << elem_topology << " not supported in compute_element_volume_to_edge_ratio."); +} + +//-------------------------------------------------------------------------------- + +static void +debug_entity(std::ostream & output, const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const bool includeFields) +{ + if (!mesh.is_valid(entity)) + { + output << "Invalid entity: " << mesh.entity_key(entity) << std::endl; + return; + } + output << mesh.entity_key(entity) << ", parallel owner = " << mesh.parallel_owner_rank(entity) << " {" << std::endl; + output << " Connectivity:" << std::endl; + const stk::mesh::EntityRank end_rank = static_cast(mesh.mesh_meta_data().entity_rank_count()); + for (stk::mesh::EntityRank r = stk::topology::BEGIN_RANK; r < end_rank; ++r) { + unsigned num_rels = mesh.num_connectivity(entity, r); + stk::mesh::Entity const *rel_entities = mesh.begin(entity, r); + stk::mesh::ConnectivityOrdinal const *rel_ordinals = mesh.begin_ordinals(entity, r); + stk::mesh::Permutation const *rel_permutations = mesh.begin_permutations(entity, r); + for (unsigned i = 0; i < num_rels; ++i) { + output << " " << mesh.entity_key(rel_entities[i]) + << " @" << rel_ordinals[i]; + if (rel_permutations) output << ":" << (int)rel_permutations[i]; + output << std::endl; + } + } + output << " Parts: "; + const stk::mesh::PartVector & parts = mesh.bucket(entity).supersets(); + for(stk::mesh::PartVector::const_iterator part_iter = parts.begin(); part_iter != parts.end(); ++part_iter) + { + const stk::mesh::Part * const part = *part_iter; + output << part->name() << " "; + } + output << std::endl; + + if (includeFields) + { + const stk::mesh::FieldVector & all_fields = mesh.mesh_meta_data().get_fields(); + for ( stk::mesh::FieldVector::const_iterator it = all_fields.begin(); it != all_fields.end() ; ++it ) + { + const FieldRef field = (const FieldRef)(**it); + + if(field.entity_rank()!=mesh.entity_rank(entity)) continue; + + const unsigned field_length = field.length(); + + field.field().sync_to_host(); + if (field.type_is()) + { + const double * data = field_data(field, entity); + if (NULL != data) + { + if (1 == field_length) + { + output << " Field: field_name=" << field.name() << ", field_state=" << field.state() << ", value=" << *data << std::endl; + } + else + { + for (unsigned i = 0; i < field_length; ++i) + { + output << " Field: field_name=" << field.name() << ", field_state=" << field.state() << ", value[" << i << "]=" << data[i] << std::endl; + } + } + } + } + else if (field.type_is()) + { + const int * data = field_data(field, entity); + if (NULL != data) + { + if (1 == field_length) + { + output << " Field: field_name=" << field.name() << ", field_state=" << field.state() << ", value=" << *data << std::endl; + } + else + { + for (unsigned i = 0; i < field_length; ++i) + { + output << " Field: field_name=" << field.name() << ", field_state=" << field.state() << ", value[" << i << "]=" << data[i] << std::endl; + } + } + } + } + } + output << std::endl; + } +} + +std::string +debug_entity(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const bool includeFields) +{ + std::ostringstream out; + debug_entity(out, mesh, entity, includeFields); + return out.str(); +} + +std::string +debug_entity(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity) +{ + return debug_entity(mesh, entity, false); +} + +//-------------------------------------------------------------------------------- + +std::vector +get_side_permutation(stk::topology topology, stk::mesh::Permutation node_permutation) +{ + const unsigned perm = node_permutation; + switch (topology) + { + case stk::topology::TRIANGLE_3_2D: + case stk::topology::TRIANGLE_6_2D: + switch (perm) + { + case 0: return {0, 1, 2}; + case 1: return {2, 0, 1}; + case 2: return {1, 2, 0}; + default: ThrowRuntimeError("find_side_permutation error, invalid triangle permutation."); + } + break; + case stk::topology::TETRAHEDRON_4: + case stk::topology::TETRAHEDRON_10: + switch (perm) + { + case 0: return {0, 1, 2, 3}; + case 1: return {1, 2, 0, 3}; + case 2: return {2, 0, 1, 3}; + case 3: return {2, 1, 3, 0}; + case 4: return {1, 3, 2, 0}; + case 5: return {3, 2, 1, 0}; + case 6: return {3, 1, 0, 2}; + case 7: return {1, 0, 3, 2}; + case 8: return {0, 3, 1, 2}; + case 9: return {0, 2, 3, 1}; + case 10: return {2, 3, 0, 1}; + case 11: return {3, 0, 2, 1}; + default: ThrowRuntimeError("find_side_permutation error, invalid tetrahedron permutation."); + } + break; + default: ThrowRuntimeError("find_side_permutation error, unsupported topology."); + } +} + +//-------------------------------------------------------------------------------- + +const stk::mesh::Part & +find_element_part(const stk::mesh::BulkData& mesh, stk::mesh::Entity elem) +{ + ThrowAssert(mesh.entity_rank(elem) == stk::topology::ELEMENT_RANK); + const stk::mesh::Part * elem_io_part = nullptr; + + const stk::mesh::PartVector & elem_parts = mesh.bucket(elem).supersets(); + for(stk::mesh::PartVector::const_iterator part_iter = elem_parts.begin(); part_iter != elem_parts.end(); ++part_iter) + { + const stk::mesh::Part * const part = *part_iter; + if (part->primary_entity_rank() == stk::topology::ELEMENT_RANK && part->subsets().empty() && part->topology() != stk::topology::INVALID_TOPOLOGY) + { + // there should only be one element rank part without subsets with topology on the element + ThrowRequireMsg(nullptr == elem_io_part, "For element " << mesh.identifier(elem) << ", more than one element rank part was found: " << elem_io_part->name() << " " << part->name()); + elem_io_part = part; + } + } + ThrowRequire(NULL != elem_io_part); + + return *elem_io_part; +} + +//-------------------------------------------------------------------------------- + +void +disconnect_entity(stk::mesh::BulkData & mesh, stk::mesh::Entity entity) +{ + stk::mesh::EntityRank entity_rank = mesh.entity_rank(entity); + std::vector relatives; + std::vector relative_ordinals; + + const stk::mesh::EntityRank highest_entity_rank = static_cast(mesh.mesh_meta_data().entity_rank_count()-1); + for (stk::mesh::EntityRank irank = highest_entity_rank; irank != entity_rank; --irank) + { + // Previously this attempted to delete forward or backward and still the list got corrupted, + // so just copy into vector and delete from there. + relatives.assign(mesh.begin(entity, irank),mesh.end(entity, irank)); + relative_ordinals.assign(mesh.begin_ordinals(entity, irank), mesh.end_ordinals(entity, irank)); + + for (size_t irel = 0; irel < relatives.size(); ++irel) + { + mesh.destroy_relation( relatives[irel], entity, relative_ordinals[irel]); + } + } +} + +//-------------------------------------------------------------------------------- + +bool +disconnect_and_destroy_entity(stk::mesh::BulkData & mesh, stk::mesh::Entity entity) +{ + disconnect_entity(mesh, entity); + return mesh.destroy_entity(entity); +} + +//-------------------------------------------------------------------------------- + +bool +check_induced_parts(const stk::mesh::BulkData & mesh) +{ /* %TRACE[ON]% */ Trace trace__("krino::debug_induced_parts()"); /* %TRACE% */ + + // This method requires aura to work correctly. + if (!mesh.is_automatic_aura_on() && mesh.parallel_size() > 1) + { + // Skip check if we don't have aura + return true; + } + + bool success = true; + + const stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + stk::mesh::Selector not_ghost_selector(meta.locally_owned_part() | meta.globally_shared_part()); + + std::vector< stk::mesh::Entity> entities; + + for (stk::mesh::EntityRank entity_rank = stk::topology::NODE_RANK; entity_rank <= stk::topology::ELEMENT_RANK; ++entity_rank) + { + stk::mesh::get_selected_entities( not_ghost_selector, mesh.buckets(entity_rank), entities ); + + for (unsigned i=0; iprimary_entity_rank() == entity_rank) + { + bool have_relative_missing_part = false; + const unsigned num_relatives = mesh.num_connectivity(entity, relative_rank); + const stk::mesh::Entity* relatives = mesh.begin(entity, relative_rank); + for (unsigned it_rel=0; it_relname() << " is found on " << mesh.entity_key(entity) << " but is missing from relatives: "; + for (unsigned it_rel=0; it_relprimary_entity_rank() == relative_rank) + { + bool found_relative_with_part = false; + const unsigned num_relatives = mesh.num_connectivity(entity, relative_rank); + const stk::mesh::Entity* relatives = mesh.begin(entity, relative_rank); + for (unsigned it_rel=0; it_relname() << " is found on " << mesh.entity_key(entity) << " but not on any of its relatives: "; + for (unsigned it_rel=0; it_rel & remote_entity_node_ids) +{ + stk::mesh::Entity entity = mesh.get_entity(remote_entity_key); + if (!mesh.is_valid(entity)) + { + krinolog << "Shared entity error, local entity does not exist, remote entity: " << remote_entity_key << stk::diag::dendl; + return false; + } + + std::vector entity_nodes(mesh.begin_nodes(entity), mesh.end_nodes(entity)); + if (entity_nodes.size() != remote_entity_node_ids.size()) + { + krinolog << "Shared entity error, number_of nodes don't match, number of remote nodes = " << remote_entity_node_ids.size() << stk::diag::dendl; + krinolog << "Local entity: " << debug_entity(mesh, entity) << stk::diag::dendl; + return false; + } + + bool nodes_match = true; + for (size_t node_index=0;node_index & entities) +{ + bool success = true; + + std::vector sharing_procs; + stk::CommSparse comm_spec(mesh.parallel()); + + for (int phase=0;phase<2;++phase) + { + for (std::vector::iterator it_entity = entities.begin(); it_entity != entities.end(); ++it_entity) + { + stk::mesh::Entity entity = *it_entity; + std::vector entity_nodes(mesh.begin_nodes(entity), mesh.end_nodes(entity)); + stk::mesh::EntityKey entity_key = mesh.entity_key(entity); + ThrowRequire(mesh.bucket(entity).shared()); + + mesh.shared_procs_intersection(entity_nodes, sharing_procs); + for (size_t proc_index=0;proc_index entity_node_ids(num_nodes); + for (size_t node_index=0; node_index sharing_procs; + stk::CommSparse comm_spec(mesh.parallel()); + + bool success = true; + stk::mesh::Selector shared_selector = meta.globally_shared_part(); + std::vector entities; + + for (stk::mesh::EntityRank entity_rank = stk::topology::EDGE_RANK; entity_rank < stk::topology::ELEMENT_RANK; ++entity_rank) + { + stk::mesh::get_selected_entities( shared_selector, mesh.buckets( entity_rank ), entities ); + + if (!check_shared_entity_nodes(mesh, entities)) success = false; + } + return success; +} + +//-------------------------------------------------------------------------------- + +bool +check_face_and_edge_relations(const stk::mesh::BulkData & mesh) +{ + const stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + + bool success = true; + stk::mesh::Selector not_ghost_selector = meta.locally_owned_part() | meta.globally_shared_part(); + + for (stk::mesh::EntityRank entity_rank = stk::topology::EDGE_RANK; entity_rank <= stk::topology::FACE_RANK; ++entity_rank) + { + const stk::mesh::BucketVector & buckets = mesh.get_buckets( entity_rank, not_ghost_selector ); + + stk::mesh::BucketVector::const_iterator ib = buckets.begin(); + stk::mesh::BucketVector::const_iterator ib_end = buckets.end(); + + for ( ; ib != ib_end; ++ib ) + { + const stk::mesh::Bucket & b = **ib; + const size_t length = b.size(); + for (size_t it_entity = 0; it_entity < length; ++it_entity) + { + stk::mesh::Entity entity = b[it_entity]; + stk::topology entity_topology = mesh.bucket(entity).topology(); + std::vector entity_nodes(mesh.begin_nodes(entity), mesh.end_nodes(entity)); + std::vector entity_elems; + stk::mesh::get_entities_through_relations(mesh, entity_nodes, stk::topology::ELEMENT_RANK, entity_elems); + + if (entity_elems.empty()) + { + krinolog << "Relation error, entity not attached to any elements: " << stk::diag::dendl; + krinolog << "Entity: " << debug_entity(mesh, entity) << stk::diag::dendl; + success = false; + } + + bool have_coincident_shell = false; + std::pair shell_relationship(stk::mesh::INVALID_CONNECTIVITY_ORDINAL, stk::mesh::INVALID_PERMUTATION); + for (auto&& elem : entity_elems) + { + stk::topology elem_topology = mesh.bucket(elem).topology(); + if (elem_topology.is_shell() && elem_topology.num_nodes() == entity_topology.num_nodes()) + { + have_coincident_shell = true; + shell_relationship = determine_shell_side_ordinal_and_permutation(mesh, elem, entity); + break; + } + } + + for (auto&& elem : entity_elems) + { + stk::topology elem_topology = mesh.bucket(elem).topology(); + const bool is_coincident_shell = (elem_topology.is_shell() && elem_topology.num_nodes() == entity_topology.num_nodes()); + bool should_be_attached = true; + if (have_coincident_shell && !is_coincident_shell) + { + // Volume elements should only be attached to inward pointing faces when the surface has a shell. + std::pair relationship = determine_ordinal_and_permutation(mesh, elem, entity); + const bool elem_polarity = entity_topology.is_positive_polarity(relationship.second); + const bool shell_polarity = entity_topology.is_positive_polarity(shell_relationship.second); + should_be_attached = elem_polarity != shell_polarity; + } + + const unsigned num_elem_entities = mesh.num_connectivity(elem, entity_rank); + const stk::mesh::Entity* elem_entities = mesh.begin(elem, entity_rank); + const stk::mesh::ConnectivityOrdinal * elem_ordinals = mesh.begin_ordinals(elem, entity_rank); + const stk::mesh::Permutation * elem_permutations = mesh.begin_permutations(elem, entity_rank); + bool already_attached = false; + for (unsigned it_s=0; it_s relationship = + is_coincident_shell ? + shell_relationship : + determine_ordinal_and_permutation(mesh, elem, entity); + if (relationship.first != elem_ordinals[it_s]) + { + krinolog << "Relation error, ordinal is incorrect: " << relationship.first << "!=" << elem_ordinals[it_s] << stk::diag::dendl; + krinolog << "Entity: " << debug_entity(mesh, entity) << stk::diag::dendl; + krinolog << "Element: " << debug_entity(mesh, elem) << stk::diag::dendl; + success = false; + } + if (relationship.second != elem_permutations[it_s]) + { + krinolog << "Relation error, permutation is incorrect: " << relationship.second << "!=" << elem_permutations[it_s] << stk::diag::dendl; + krinolog << "Entity: " << debug_entity(mesh, entity) << stk::diag::dendl; + krinolog << "Element: " << debug_entity(mesh, elem) << stk::diag::dendl; + success = false; + } + } + } + } + if (!already_attached && should_be_attached) + { + + krinolog << "Relation error, entity is not attached to element: " << stk::diag::dendl; + krinolog << "Entity: " << debug_entity(mesh, entity) << stk::diag::dendl; + krinolog << "Element: " << debug_entity(mesh, elem) << stk::diag::dendl; + std::pair relationship = determine_ordinal_and_permutation(mesh, elem, entity); + for (unsigned it_s=0; it_s entities; + stk::mesh::get_entities( mesh, meta.side_rank(), entities ); + + mesh.modification_begin(); + for (auto&& entity : entities) + { + attach_entity_to_elements(mesh, entity); + } + mesh.modification_end(); +} + +void +attach_entity_to_elements(stk::mesh::BulkData & mesh, stk::mesh::Entity entity) +{ + //Sorry! Passing these scratch vectors into stk's declare_relation function is + //a performance improvement (fewer allocations). But stk will try to clean up + //this ugliness soon. (i.e., find a better way to get the performance.) + stk::mesh::OrdinalVector scratch1, scratch2, scratch3; + + stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + stk::topology entity_topology = mesh.bucket(entity).topology(); + stk::mesh::EntityRank entity_rank = entity_topology.rank(); + std::vector entity_nodes(mesh.begin_nodes(entity), mesh.end_nodes(entity)); + std::vector entity_elems; + stk::mesh::get_entities_through_relations(mesh, entity_nodes, stk::topology::ELEMENT_RANK, entity_elems); + + bool have_coincident_shell = false; + std::pair shell_relationship(stk::mesh::INVALID_CONNECTIVITY_ORDINAL, stk::mesh::INVALID_PERMUTATION); + for (auto&& elem : entity_elems) + { + stk::topology elem_topology = mesh.bucket(elem).topology(); + if (elem_topology.is_shell() && elem_topology.num_nodes() == entity_topology.num_nodes()) + { + have_coincident_shell = true; + shell_relationship = determine_shell_side_ordinal_and_permutation(mesh, elem, entity); + break; + } + } + + for (auto&& elem : entity_elems) + { + if (!mesh.bucket(elem).member(meta.locally_owned_part())) + { + continue; + } + bool already_attached = false; + const unsigned num_elem_entities = mesh.num_connectivity(elem, entity_rank); + const stk::mesh::Entity* elem_entities = mesh.begin(elem, entity_rank); + for (unsigned it_s=0; it_s relationship(stk::mesh::INVALID_CONNECTIVITY_ORDINAL, stk::mesh::INVALID_PERMUTATION); + if (!have_coincident_shell) + { + relationship = determine_ordinal_and_permutation(mesh, elem, entity); + } + else + { + stk::topology elem_topology = mesh.bucket(elem).topology(); + if (elem_topology.is_shell() && elem_topology.num_nodes() == entity_topology.num_nodes()) + { + ThrowAssertMsg(shell_relationship.second == determine_permutation(mesh, elem, entity, shell_relationship.first), "All shells should have same permutation for side."); + relationship = shell_relationship; + } + else + { + relationship = determine_ordinal_and_permutation(mesh, elem, entity); + const bool elem_polarity = entity_topology.is_positive_polarity(relationship.second); + const bool shell_polarity = entity_topology.is_positive_polarity(shell_relationship.second); + if (elem_polarity == shell_polarity) + { + // Side does not touch volume element; + continue; + } + } + } + + mesh.declare_relation( elem, entity, relationship.first, relationship.second, scratch1, scratch2, scratch3 ); + const bool successfully_attached = (find_entity_by_ordinal(mesh, elem, entity_rank, relationship.first) == entity); + if (!successfully_attached) + { + krinolog << "Could not attach " << debug_entity(mesh,entity) << " to element " << debug_entity(mesh,elem) << stk::diag::dendl; + krinolog << "Existing attached entities:" << stk::diag::dendl; + for (unsigned it_s=0; it_s & entities, + stk::CommSparse &commSparse) +{ + entities.clear(); + stk::unpack_communications(commSparse, [&](int procId) + { + stk::CommBuffer & buffer = commSparse.recv_buffer(procId); + + while ( buffer.remaining() ) + { + stk::mesh::EntityKey entityKey; + commSparse.recv_buffer(procId).unpack(entityKey); + entities.insert(mesh.get_entity(entityKey)); + } + }); +} + +void +update_node_activation(stk::mesh::BulkData & mesh, stk::mesh::Part & active_part) +{ + stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + + stk::mesh::PartVector active_part_vec(1, &active_part); + stk::mesh::PartVector inactive_part_vec; + + std::vector entities; + stk::mesh::Selector locally_owned(meta.locally_owned_part()); + stk::mesh::get_selected_entities( locally_owned, mesh.buckets( stk::topology::NODE_RANK ), entities ); + + for (std::vector::iterator i_node = entities.begin(); i_node != entities.end(); ++i_node) + { + stk::mesh::Entity node = *i_node; + + const unsigned num_node_elems = mesh.num_elements(node); + const stk::mesh::Entity* node_elems = mesh.begin_elements(node); + bool have_active_elems = false; + for (unsigned node_elem_index=0; node_elem_index add_parts; + std::vector remove_parts; + std::vector entities; + + stk::mesh::Selector inactive_locally_owned = mesh.mesh_meta_data().locally_owned_part() & !active_part; + + for (stk::mesh::EntityRank entity_rank = stk::topology::NODE_RANK; entity_rank <= stk::topology::ELEMENT_RANK; ++entity_rank) + { + const stk::mesh::BucketVector & buckets = mesh.get_buckets(entity_rank, inactive_locally_owned); + for (auto&& bucket_ptr : buckets) + { + entities.insert(entities.end(), bucket_ptr->begin(), bucket_ptr->end()); + } + } + add_parts.assign(entities.size(), {&active_part}); + remove_parts.resize(entities.size()); + + mesh.batch_change_entity_parts(entities, add_parts, remove_parts); +} + +//-------------------------------------------------------------------------------- + +void destroy_custom_ghostings(stk::mesh::BulkData & mesh) +{ + const std::vector & ghostings = mesh.ghostings(); + for(unsigned i = stk::mesh::BulkData::AURA+1; i < ghostings.size(); ++i) + { + mesh.destroy_ghosting(*ghostings[i]); + } +} + +//-------------------------------------------------------------------------------- + +void +delete_mesh_entities(stk::mesh::BulkData & mesh, std::vector & child_elems) +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::delete_old_mesh_entities(void)"); /* %TRACE% */ + + stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + + stk::mesh::Selector not_ghost_selector = meta.locally_owned_part() | meta.globally_shared_part(); + stk::mesh::Selector universal_selector = meta.universal_part(); + + std::vector child_sides; + std::vector child_edges; + std::vector child_nodes; + + for (unsigned i=0; i * const coords_field = reinterpret_cast*>(mesh.mesh_meta_data().coordinate_field()); + ThrowRequireMsg(nullptr != coords_field, "Coordinates must be defined."); + + double * child_coords = stk::mesh::field_data(*coords_field, child); + double * parent0_coords = stk::mesh::field_data(*coords_field, parent0); + double * parent1_coords = stk::mesh::field_data(*coords_field, parent1); + + const unsigned ndim = mesh.mesh_meta_data().spatial_dimension(); + unsigned best_dim = 0; + double best_extent = std::abs(parent1_coords[0] - parent0_coords[0]); + for (unsigned dim = 1; dim < ndim; ++dim) + { + const double extent = std::abs(parent1_coords[dim] - parent0_coords[dim]); + if (extent > best_extent) + { + best_dim = dim; + best_extent = extent; + } + } + return std::abs(child_coords[best_dim] - parent0_coords[best_dim])/best_extent; +} + +//-------------------------------------------------------------------------------- + +void +store_edge_node_parent_ids(const stk::mesh::BulkData & mesh, + const FieldRef & parent_id_field, + stk::mesh::Entity edge_node_entity, + stk::mesh::EntityId parent0_id, + stk::mesh::EntityId parent1_id) +{ + if (parent_id_field.type_is()) + { + auto * stored_parent_ids = field_data(parent_id_field, edge_node_entity); + ThrowAssertMsg(stored_parent_ids, "Node " << mesh.identifier(edge_node_entity) + << " does not have the parent_ids field suggesting it is a mesh node."); + stored_parent_ids[0] = parent0_id; + stored_parent_ids[1] = parent1_id; + } + else if (parent_id_field.type_is()) + { + auto * stored_parent_ids = field_data(parent_id_field, edge_node_entity); + ThrowAssertMsg(stored_parent_ids, "Node " << mesh.identifier(edge_node_entity) + << " does not have the parent_ids field suggesting it is a mesh node."); + stored_parent_ids[0] = parent0_id; + stored_parent_ids[1] = parent1_id; + } + else + { + ThrowRequireMsg(false, "Unsupported field type for parent_node_ids_field"); + } +} + +//-------------------------------------------------------------------------------- + +std::array +get_edge_node_parent_ids(const stk::mesh::BulkData & mesh, + const FieldRef & parent_id_field, + const stk::mesh::Entity edge_node_entity) +{ + std::array parent_ids; + + if (parent_id_field.type_is()) + { + const auto * stored_parent_ids = field_data(parent_id_field, edge_node_entity); + ThrowAssertMsg(stored_parent_ids, "No SubElementNode found for node " << mesh.identifier(edge_node_entity) + << ", but it does not have the parent_ids field suggesting it is a mesh node."); + parent_ids[0] = stored_parent_ids[0]; + parent_ids[1] = stored_parent_ids[1]; + } + else if (parent_id_field.type_is()) + { + const auto * stored_parent_ids = field_data(parent_id_field, edge_node_entity); + ThrowAssertMsg(stored_parent_ids, "No SubElementNode found for node " << mesh.identifier(edge_node_entity) + << ", but it does not have the parent_ids field suggesting it is a mesh node."); + parent_ids[0] = stored_parent_ids[0]; + parent_ids[1] = stored_parent_ids[1]; + } + else + { + ThrowRequireMsg(false, "Unsupported field type for parent_node_ids_field"); + } + return parent_ids; +} + +//-------------------------------------------------------------------------------- + +void get_parent_nodes_from_child(const stk::mesh::BulkData & mesh, + stk::mesh::Entity child, const FieldRef & parent_id_field, + std::set & parent_nodes) +{ + if (has_field_data(parent_id_field, child)) + { + auto parent_ids = get_edge_node_parent_ids(mesh, parent_id_field, child); + const stk::mesh::Entity parent0 = mesh.get_entity(stk::topology::NODE_RANK, parent_ids[0]); + const stk::mesh::Entity parent1 = mesh.get_entity(stk::topology::NODE_RANK, parent_ids[1]); + ThrowAssert(mesh.is_valid(parent0) && mesh.is_valid(parent1)); + get_parent_nodes_from_child(mesh, parent0, parent_id_field, parent_nodes); + get_parent_nodes_from_child(mesh, parent1, parent_id_field, parent_nodes); + } + else + { + parent_nodes.insert(child); + } +} + +//-------------------------------------------------------------------------------- + +void debug_print_selector_parts(const stk::mesh::Selector & selector) +{ + stk::mesh::PartVector parts; + selector.get_parts(parts); + krinolog << "Selector contains parts: " << stk::diag::push << stk::diag::dendl; + for(stk::mesh::PartVector::const_iterator it = parts.begin(); it != parts.end(); ++it) + { + krinolog << (*it)->name() << stk::diag::dendl; + } + krinolog << stk::diag::pop << stk::diag::dendl; +} + +//-------------------------------------------------------------------------------- + +stk::mesh::PartVector filter_non_io_parts(const stk::mesh::PartVector & all_parts) +{ + stk::mesh::PartVector io_parts; + + for(stk::mesh::PartVector::const_iterator it = all_parts.begin(); it != all_parts.end(); ++it) + { + if( stk::io::is_part_io_part(**it) ) + { + io_parts.push_back(*it); + } + } + + return io_parts; +} + +void +activate_selected_sides_touching_active_elements(stk::mesh::BulkData & mesh, const stk::mesh::Selector & side_selector, stk::mesh::Part & active_part) +{ + // This method requires AURA + ThrowRequire(mesh.is_automatic_aura_on()); + + mesh.modification_begin(); + stk::mesh::PartVector active_part_vec(1, &active_part); + stk::mesh::PartVector inactive_part_vec; + stk::mesh::Selector select_locally_owned = side_selector & mesh.mesh_meta_data().locally_owned_part(); + + std::vector sides; + stk::mesh::get_selected_entities( select_locally_owned, mesh.buckets( mesh.mesh_meta_data().side_rank() ), sides ); + for (auto && side : sides) + { + bool have_active_elem = false; + const stk::mesh::Entity* side_elems = mesh.begin_elements(side); + const unsigned num_side_elems = mesh.num_elements(side); + for (unsigned ielem=0; ielem & coincident_elems) +{ + stk::topology elem_topology = mesh.bucket(elem).topology(); + const stk::mesh::Entity* elem_nodes = mesh.begin_nodes(elem); + std::vector elem_side_nodes; + std::vector elem_nbrs; + std::vector nbr_side_nodes; + + coincident_elems.clear(); + const unsigned num_sides = elem_topology.num_sides(); + for (unsigned iside=0; iside sorted_elem_side_nodes = elem_side_nodes; + std::sort(sorted_elem_side_nodes.begin(), sorted_elem_side_nodes.end(), stk::mesh::EntityLess(mesh)); + const unsigned unique_len = std::distance(sorted_elem_side_nodes.begin(), std::unique( sorted_elem_side_nodes.begin(), sorted_elem_side_nodes.end() )); + const bool is_degenerate_side = unique_len < mesh.mesh_meta_data().spatial_dimension(); + if (is_degenerate_side) continue; + + stk::mesh::get_entities_through_relations(mesh, elem_side_nodes, stk::topology::ELEMENT_RANK, elem_nbrs); + ThrowRequire(!elem_nbrs.empty()); + + for (auto && nbr : elem_nbrs) + { + if (nbr == elem) continue; + + stk::topology nbr_topology = mesh.bucket(nbr).topology(); + const stk::mesh::Entity* nbr_nodes = mesh.begin_nodes(nbr); + const unsigned num_nbr_sides = nbr_topology.num_sides(); + for (unsigned inbr_side=0; inbr_side 1) + { + // Skip check if we don't have aura + return true; + } + + bool found_mismatched_side = false; + + std::vector element_nodes; + std::vector element_side_nodes; + std::vector side_elements; + std::vector active_side_elements; + + const stk::mesh::BucketVector & buckets = mesh.get_buckets( stk::topology::ELEMENT_RANK, active_part & mesh.mesh_meta_data().locally_owned_part() ); + + for ( auto && bucket : buckets ) + { + stk::topology element_topology = bucket->topology(); + if (element_topology.is_shell()) continue; + const unsigned num_sides = element_topology.num_sides(); + + for ( auto && element : *bucket ) + { + element_nodes.assign(mesh.begin_nodes(element), mesh.end_nodes(element)); + for (unsigned iside=0; iside elements; + std::vector< stk::mesh::Entity> coincident_elements; + std::vector< stk::mesh::Entity> element_nodes; + + ParallelErrorMessage err(mesh.parallel()); + + stk::mesh::get_entities( mesh, stk::topology::ELEMENT_RANK, elements ); + for (auto && element : elements) + { + element_nodes.assign(mesh.begin_nodes(element), mesh.end_nodes(element)); + stk::mesh::get_entities_through_relations(mesh, element_nodes, stk::topology::ELEMENT_RANK, coincident_elements); + ThrowRequire(!coincident_elements.empty()); + stk::topology element_topology = mesh.bucket(element).topology(); + + bool coincident_element_error = false; + for (auto && coincident : coincident_elements) + { + if (coincident == element || element_topology != mesh.bucket(coincident).topology()) continue; + if (!element_topology.is_shell()) + { + err << "Non-shell elements " << mesh.entity_key(element) << " and " + << mesh.entity_key(coincident) << " are fully coincident (overlapping).\n"; + coincident_element_error = true; + } + else + { + stk::EquivalentPermutation result = element_topology.is_equivalent(mesh.begin_nodes(coincident), element_nodes.data()); + ThrowRequire(result.is_equivalent); + if (result.permutation_number != stk::mesh::DEFAULT_PERMUTATION) + { + err << "Elements " << mesh.entity_key(element) << " and " << mesh.entity_key(coincident) + << " are fully coincident shell elements but have different node order.\n"; + coincident_element_error = true; + } + } + } + + // Detect partially coincident, non-shell elements that are both active + if (!coincident_element_error && mesh.bucket(element).member(active_part) && !element_topology.is_shell() && element_topology.base() != stk::topology::BEAM_2) + { + get_partially_and_fully_coincident_elements(mesh, element, coincident_elements); + + for (auto && coincident : coincident_elements) + { + if (mesh.bucket(coincident).member(active_part) && !mesh.bucket(coincident).topology().is_shell() && + mesh.bucket(coincident).topology().base() != stk::topology::BEAM_2) + { + err << "Non-shell elements " << mesh.entity_key(element) << " with topology " + << element_topology.name() << " and " << mesh.entity_key(coincident) + << " with topology " << mesh.bucket(coincident).topology().name() + << " are partially coincident (overlapping) and active.\n"; + coincident_element_error = true; + } + } + } + } + auto err_msg = err.gather_message(); + krinolog << err_msg.second; + return !err_msg.first; +} + +bool +fix_coincident_element_ownership(stk::mesh::BulkData & mesh) +{ + // This method exploits aura to choose which processor the faces and edges should be owned by + if (!mesh.is_automatic_aura_on()) + { + // Make no changes, hope for the best. + return false; + } + + stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + stk::mesh::Selector locally_owned_selector(meta.locally_owned_part()); + + std::vector elems; + std::vector coincident_elems; + + std::vector entities_to_move; + + stk::mesh::get_selected_entities( locally_owned_selector, mesh.buckets(stk::topology::ELEMENT_RANK), elems ); + + for (auto && elem : elems) + { + get_partially_and_fully_coincident_elements(mesh, elem, coincident_elems); + + int new_owner = mesh.parallel_owner_rank(elem); + for (auto && nbr : coincident_elems) + { + const int elem_owner = mesh.parallel_owner_rank(nbr); + if (elem_owner < new_owner) new_owner = elem_owner; + } + + if (new_owner != mesh.parallel_owner_rank(elem)) + { + entities_to_move.push_back(stk::mesh::EntityProc(elem, new_owner)); + } + } + + const int local_made_moves = !entities_to_move.empty(); + int global_made_moves = false; + stk::all_reduce_max(mesh.parallel(), &local_made_moves, &global_made_moves, 1); + + if (global_made_moves) + { + mesh.change_entity_owner(entities_to_move); + } + return global_made_moves; +} + +bool +fix_face_and_edge_ownership(stk::mesh::BulkData & mesh) +{ + // This method exploits aura to choose which processor the faces and edges should be owned by + if (!mesh.is_automatic_aura_on()) + { + // Make no changes, hope for the best. + return false; + } + + stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + stk::mesh::Selector locally_owned_selector(meta.locally_owned_part()); + + std::vector< stk::mesh::Entity> entities; + std::vector< stk::mesh::Entity> entity_elems; + + std::vector entities_to_move; + + for (stk::mesh::EntityRank entity_rank = stk::topology::EDGE_RANK; entity_rank <= stk::topology::FACE_RANK; ++entity_rank) + { + stk::mesh::get_selected_entities( locally_owned_selector, mesh.buckets(entity_rank), entities ); + + for (auto && entity : entities) + { + entity_elems.assign(mesh.begin_elements(entity), mesh.end_elements(entity)); + ThrowRequire(!entity_elems.empty()); + + int new_owner = mesh.parallel_size(); + for (auto && entity_elem : entity_elems) + { + const int elem_owner = mesh.parallel_owner_rank(entity_elem); + if (elem_owner < new_owner) new_owner = elem_owner; + } + + if (new_owner != mesh.parallel_owner_rank(entity)) + { + entities_to_move.push_back(stk::mesh::EntityProc(entity, new_owner)); + } + } + } + + const int local_made_moves = !entities_to_move.empty(); + int global_made_moves = false; + stk::all_reduce_max(mesh.parallel(), &local_made_moves, &global_made_moves, 1); + + if (global_made_moves) + { + mesh.change_entity_owner(entities_to_move); + } + return global_made_moves; +} + +bool +check_face_and_edge_ownership(const stk::mesh::BulkData & mesh) +{ + // This method exploits aura to choose which processor the faces and edges should be owned by + if (!mesh.is_automatic_aura_on()) + { + // Skip check if we don't have aura + return true; + } + + const stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + stk::mesh::Selector locally_owned_selector(meta.locally_owned_part()); + + std::vector< stk::mesh::Entity> entities; + std::vector< stk::mesh::Entity> entity_elems; + std::vector< stk::mesh::Entity> entity_nodes; + + for (stk::mesh::EntityRank entity_rank = stk::topology::EDGE_RANK; entity_rank <= stk::topology::FACE_RANK; ++entity_rank) + { + const stk::mesh::BucketVector & buckets = mesh.get_buckets( entity_rank, locally_owned_selector ); + + stk::mesh::BucketVector::const_iterator ib = buckets.begin(); + stk::mesh::BucketVector::const_iterator ib_end = buckets.end(); + + for ( ; ib != ib_end; ++ib ) + { + const stk::mesh::Bucket & b = **ib; + const size_t length = b.size(); + for (size_t it_entity = 0; it_entity < length; ++it_entity) + { + stk::mesh::Entity entity = b[it_entity]; + const int entity_owner = mesh.parallel_owner_rank(entity); + + entity_nodes.assign(mesh.begin_nodes(entity), mesh.end_nodes(entity)); + entity_elems.assign(mesh.begin_elements(entity), mesh.end_elements(entity)); + + bool have_element_on_owning_proc = false; + for (std::vector::iterator it_elem = entity_elems.begin(); it_elem != entity_elems.end(); ++it_elem) + { + if (mesh.parallel_owner_rank(*it_elem) == entity_owner) + { + have_element_on_owning_proc = true; + break; + } + } + ThrowRequireMsg(have_element_on_owning_proc, "Error: " << mesh.entity_key(entity) + << " is owned on processor " << entity_owner + << " but does not have any elements that are owned on this processor."); + } + } + } + const bool success = true; + return success; +} + +bool +set_region_id_on_unset_entity_and_neighbors(stk::mesh::BulkData & mesh, stk::mesh::Entity & entity, stk::mesh::EntityRank entity_rank, stk::mesh::FieldBase & region_id_field, const unsigned region_id) +{ + unsigned * region_id_data = stk::mesh::field_data(reinterpret_cast&>(region_id_field), entity); + ThrowAssert(NULL != region_id_data); + if (*region_id_data != 0) + { + ThrowAssert(*region_id_data == region_id); + return false; + } + + *region_id_data = region_id; + + // now visit neighbors + const stk::mesh::Entity* begin_nodes = mesh.begin_nodes(entity); + const stk::mesh::Entity* end_nodes = mesh.end_nodes(entity); + + for (const stk::mesh::Entity* it_node = begin_nodes; it_node != end_nodes; ++it_node) + { + stk::mesh::Entity node = *it_node; + + const stk::mesh::Entity* node_entities_begin = mesh.begin(node, entity_rank); + const stk::mesh::Entity* node_entities_end = mesh.end(node, entity_rank); + + for (const stk::mesh::Entity* it_nbr = node_entities_begin; it_nbr != node_entities_end; ++it_nbr) + { + stk::mesh::Entity neighbor = *it_nbr; + set_region_id_on_unset_entity_and_neighbors(mesh, neighbor, entity_rank, region_id_field, region_id); + } + } + + return true; +} + +void +identify_isolated_regions(stk::mesh::Part & part, stk::mesh::FieldBase & region_id_field) +{ /* %TRACE[ON]% */ Trace trace__("krino::Mesh::identify_isolated_portions_of_part(stk::mesh::Part & part)"); /* %TRACE% */ + stk::mesh::BulkData & mesh = part.mesh_bulk_data(); + stk::mesh::MetaData & meta = mesh.mesh_meta_data(); + + stk::mesh::EntityRank entity_rank = part.primary_entity_rank(); + stk::mesh::Selector selector = part & meta.locally_owned_part(); + + std::vector entities; + stk::mesh::get_selected_entities( selector, mesh.buckets(entity_rank), entities ); + + // initialize region id + const stk::mesh::BucketVector & buckets = mesh.get_buckets(entity_rank, selector); + for (stk::mesh::BucketVector::const_iterator ib = buckets.begin(); ib != buckets.end(); ++ib ) + { + const stk::mesh::Bucket & b = **ib; + const size_t length = b.size(); + unsigned * region_id = stk::mesh::field_data(reinterpret_cast&>(region_id_field), b); + ThrowAssert(NULL != region_id); + std::fill(region_id, region_id+length, 0); + } + + unsigned local_region_counter = 0; + unsigned region_id = 1 + local_region_counter*mesh.parallel_size() + mesh.parallel_rank(); + + for (std::vector::iterator it_entity=entities.begin(); it_entity!=entities.end(); ++it_entity) + { + stk::mesh::Entity entity = *it_entity; + + const bool made_changes = set_region_id_on_unset_entity_and_neighbors(mesh, entity, entity_rank, region_id_field, region_id); + if (made_changes) + { + ++local_region_counter; + region_id = 1 + local_region_counter*mesh.parallel_size() + mesh.parallel_rank(); + } + } +} + +const unsigned * get_side_node_ordinals(stk::topology topology, unsigned side_ordinal) +{ + static std::vector< std::vector< std::vector > > all_side_node_ordinals(stk::topology::BEGIN_TOPOLOGY + stk::topology::NUM_TOPOLOGIES); + std::vector< std::vector > & topology_side_node_ordinals = all_side_node_ordinals[topology.value()]; + if (topology_side_node_ordinals.empty()) + { + const size_t num_sides = topology.num_sides(); + topology_side_node_ordinals.resize(num_sides); + for (size_t side_index = 0; side_index < num_sides; ++side_index) + { + std::vector & side_node_ordinals = topology_side_node_ordinals[side_index]; + side_node_ordinals.resize(topology.side_topology(side_index).num_nodes()); + topology.side_node_ordinals(side_index, side_node_ordinals.begin()); + } + } + return &topology_side_node_ordinals[side_ordinal][0]; +} + +const unsigned * get_edge_node_ordinals(stk::topology topology, unsigned edge_ordinal) +{ + static std::vector< std::vector< std::vector > > all_edge_node_ordinals(stk::topology::BEGIN_TOPOLOGY + stk::topology::NUM_TOPOLOGIES); + std::vector< std::vector > & topology_edge_node_ordinals = all_edge_node_ordinals[topology.value()]; + if (topology_edge_node_ordinals.empty()) + { + const size_t num_edges = topology.num_edges(); + topology_edge_node_ordinals.resize(num_edges); + for (size_t edge_index = 0; edge_index < num_edges; ++edge_index) + { + std::vector & edge_node_ordinals = topology_edge_node_ordinals[edge_index]; + edge_node_ordinals.resize(topology.edge_topology(edge_index).num_nodes()); + topology.edge_node_ordinals(edge_index, edge_node_ordinals.begin()); + } + } + return &topology_edge_node_ordinals[edge_ordinal][0]; +} + +stk::mesh::PartVector get_common_io_parts(const stk::mesh::BulkData & mesh, const std::vector entities) +{ + stk::mesh::PartVector common_io_parts; + + bool first = true; + for (auto&& entity : entities) + { + stk::mesh::PartVector entity_io_parts = filter_non_io_parts(mesh.bucket(entity).supersets()); + std::sort(entity_io_parts.begin(), entity_io_parts.end()); + if (first) + { + first = false; + common_io_parts.swap(entity_io_parts); + } + else + { + stk::mesh::PartVector working_set; + working_set.swap(common_io_parts); + std::set_intersection(working_set.begin(),working_set.end(),entity_io_parts.begin(),entity_io_parts.end(),std::back_inserter(common_io_parts)); + } + } + return common_io_parts; +} + +stk::mesh::PartVector get_removable_parts(const stk::mesh::BulkData & mesh, const stk::mesh::Bucket & bucket) +{ + stk::mesh::PartVector removable_parts; + for ( auto&& part : bucket.supersets() ) + { + stk::mesh::EntityRank part_rank = part->primary_entity_rank(); + if ((part_rank == stk::topology::INVALID_RANK || part_rank == bucket.entity_rank()) && + (!stk::mesh::is_auto_declared_part(*part) || stk::mesh::is_topology_root_part(*part))) + { + removable_parts.push_back(part); + } + } + return removable_parts; +} + +stk::mesh::PartVector get_removable_parts(const stk::mesh::BulkData & mesh, const stk::mesh::Entity entity) +{ + return get_removable_parts(mesh, mesh.bucket(entity)); +} + +//-------------------------------------------------------------------------------- +bool is_refinement_child(const stk::mesh::BulkData & stk_bulk, const stk::mesh::Entity entity) +{ + const stk::mesh::EntityRank entity_rank = stk_bulk.entity_rank(entity); + const unsigned num_family_trees = stk_bulk.num_connectivity(entity, stk::topology::CONSTRAINT_RANK); + const stk::mesh::Entity* family_trees = stk_bulk.begin(entity, stk::topology::CONSTRAINT_RANK); + for (unsigned ifamily=0; ifamily < num_family_trees; ++ifamily) + { + const stk::mesh::Entity ft = family_trees[ifamily]; + const stk::mesh::Entity* family_tree_entities = stk_bulk.begin(ft, entity_rank); + if(family_tree_entities[0] != entity) return true; + } + return false; +} + +void +get_refinement_immediate_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity parent, std::vector & children) +{ + children.clear(); + stk::mesh::EntityRank entity_rank = stk_bulk.entity_rank(parent); + const unsigned num_family_trees = stk_bulk.num_connectivity(parent, stk::topology::CONSTRAINT_RANK); + const stk::mesh::Entity* family_trees = stk_bulk.begin(parent, stk::topology::CONSTRAINT_RANK); + for (unsigned ifamily=0; ifamily < num_family_trees; ++ifamily) + { + const stk::mesh::Entity* family_tree_entities = stk_bulk.begin(family_trees[ifamily], entity_rank); + const stk::mesh::Entity tree_parent = family_tree_entities[0]; // 0th entry in the family_tree is the parent + if (parent == tree_parent) // I am the parent + { + const unsigned num_family_tree_entities = stk_bulk.num_connectivity(family_trees[ifamily], entity_rank); + for (unsigned ichild=1; ichild < num_family_tree_entities; ++ichild) + { + children.push_back(family_tree_entities[ichild]); + } + } + } +} + +//-------------------------------------------------------------------------------- + +bool +has_refinement_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity parent) +{ + stk::mesh::EntityRank entity_rank = stk_bulk.entity_rank(parent); + const unsigned num_family_trees = stk_bulk.num_connectivity(parent, stk::topology::CONSTRAINT_RANK); + const stk::mesh::Entity* family_trees = stk_bulk.begin(parent, stk::topology::CONSTRAINT_RANK); + for (unsigned ifamily=0; ifamily < num_family_trees; ++ifamily) + { + const stk::mesh::Entity* family_tree_entities = stk_bulk.begin(family_trees[ifamily], entity_rank); + const stk::mesh::Entity tree_parent = family_tree_entities[0]; // 0th entry in the family_tree is the parent + if (parent == tree_parent) // I am the parent + { + return true; + } + } + return false; +} + +//-------------------------------------------------------------------------------- + +namespace { +void +get_refinement_leaf_children(const stk::mesh::BulkData& stk_bulk, const std::vector & children, std::vector & leaf_children) +{ + std::vector grand_children; + for (auto&& child : children) + { + get_refinement_immediate_children(stk_bulk, child, grand_children); + if (grand_children.empty()) + { + leaf_children.push_back(child); + } + else + { + get_refinement_leaf_children(stk_bulk, grand_children, leaf_children); + } + } +} + +void +get_refinement_all_children(const stk::mesh::BulkData& stk_bulk, const std::vector & children, std::vector & all_children) +{ + std::vector grand_children; + for (auto&& child : children) + { + get_refinement_immediate_children(stk_bulk, child, grand_children); + all_children.insert(all_children.end(), grand_children.begin(), grand_children.end()); + get_refinement_all_children(stk_bulk, grand_children, all_children); + } +} +} + +//-------------------------------------------------------------------------------- + +void +get_refinement_leaf_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity entity, std::vector & leaf_children) +{ + leaf_children.clear(); + std::vector children; + get_refinement_immediate_children(stk_bulk, entity, children); + get_refinement_leaf_children(stk_bulk, children, leaf_children); +} + +void +get_refinement_all_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity entity, std::vector & all_children) +{ + std::vector children; + get_refinement_immediate_children(stk_bulk, entity, children); + all_children = children; + get_refinement_all_children(stk_bulk, children, all_children); +} + +void set_relation_permutation(stk::mesh::BulkData & mesh, stk::mesh::Entity from, stk::mesh::Entity to, stk::mesh::ConnectivityOrdinal to_ord, stk::mesh::Permutation to_permutation) +{ + const stk::mesh::EntityRank from_rank = mesh.entity_rank(from); + const stk::mesh::EntityRank to_rank = mesh.entity_rank(to); + + stk::mesh::Entity const* fwd_rels = mesh.begin(from, to_rank); + stk::mesh::ConnectivityOrdinal const* fwd_ords = mesh.begin_ordinals(from, to_rank); + stk::mesh::Permutation * fwd_perms = const_cast(mesh.begin_permutations(from, to_rank)); + const int num_fwd = mesh.num_connectivity(from, to_rank); + + stk::mesh::Entity const* back_rels = mesh.begin(to, from_rank); + stk::mesh::ConnectivityOrdinal const* back_ords = mesh.begin_ordinals(to, from_rank); + stk::mesh::Permutation * back_perms = const_cast(mesh.begin_permutations(to, from_rank)); + const int num_back = mesh.num_connectivity(to,from_rank); + + // Find and change fwd connectivity + for (int i = 0; i < num_fwd; ++i, ++fwd_rels, ++fwd_ords, ++fwd_perms) { + // Allow clients to make changes to permutation + // Permutations do not affect Relation ordering, so this is safe. + if (*fwd_rels == to && *fwd_ords == to_ord) { + *fwd_perms = to_permutation; + } + } + + // Find and change back connectivity + for (int i = 0; i < num_back; ++i, ++back_rels, ++back_ords, ++back_perms) { + // Allow clients to make changes to permutation + // Permutations do not affect Relation ordering, so this is safe. + if (*back_rels == from && *back_ords == to_ord) { + *back_perms = to_permutation; + } + } +} + +std::pair +determine_shell_side_ordinal_and_permutation(const stk::mesh::BulkData & mesh, stk::mesh::Entity shell, stk::mesh::Entity side) +{ + // The input shell may or may not be attached to side, but should be coincident with the side. + // We will figure out what the ordinal for this shell-side relation based on the existing + // connectivity of the side. + const unsigned num_side_elems = mesh.num_elements(side); + ThrowRequireMsg(num_side_elems > 0, "Cannot determine shell_side_ordinal for completely disconnected side."); + + stk::mesh::EntityRank side_rank = mesh.mesh_meta_data().side_rank(); + stk::topology side_topology = mesh.bucket(side).topology(); + const stk::mesh::Entity * side_elems = mesh.begin_elements(side); + const stk::mesh::ConnectivityOrdinal * side_elem_ordinals = mesh.begin_element_ordinals(side); + const stk::mesh::Permutation * side_elem_permutations = mesh.begin_element_permutations(side); + + std::pair result(stk::mesh::INVALID_CONNECTIVITY_ORDINAL, stk::mesh::INVALID_PERMUTATION); + for (unsigned it = 0; it relative_rank); + + const stk::mesh::Entity * relative_nodes = mesh.begin_nodes(relative); + + const stk::EquivalentPermutation equiv = stk::mesh::sub_rank_equivalent(mesh, entity, ordinal, relative_rank, relative_nodes); + if(!equiv.is_equivalent) + { + ThrowErrorMsg("Could not find connection between " << mesh.entity_key(entity) <<" and " + << mesh.entity_key(relative) << " with ordinal " << ordinal + << debug_entity(mesh, entity) << debug_entity(mesh, relative)); + } + + return static_cast(equiv.permutation_number); +} + +std::pair +determine_ordinal_and_permutation(const stk::mesh::BulkData & mesh, const stk::mesh::Entity entity, const stk::mesh::Entity relative) +{ + const stk::mesh::EntityRank relative_rank = mesh.entity_rank(relative); + ThrowAssert(mesh.entity_rank(entity) > relative_rank); + stk::topology relative_topology = mesh.bucket(relative).topology(); + ThrowAssert(relative_topology.num_nodes() == mesh.num_nodes(relative)); + + const stk::mesh::Entity * relative_nodes = mesh.begin_nodes(relative); + + stk::topology entity_topology = mesh.bucket(entity).topology(); + const bool looking_for_shell_side = entity_topology.is_shell() && relative_rank == mesh.mesh_meta_data().side_rank(); + + for(size_t i = 0; i < entity_topology.num_sub_topology(relative_rank); ++i) + { + if (entity_topology.sub_topology(relative_rank, i) == relative_topology) + { + const stk::EquivalentPermutation equiv = stk::mesh::sub_rank_equivalent(mesh, entity, stk::mesh::ConnectivityOrdinal(i), relative_rank, relative_nodes); + const bool match = equiv.is_equivalent && (!looking_for_shell_side || equiv.permutation_number < relative_topology.num_positive_permutations()); + if(match) + { + return std::pair(static_cast(i), static_cast(equiv.permutation_number)); + } + } + } + ThrowRuntimeError("Could not find connection between " << mesh.entity_key(entity) << " and " << mesh.entity_key(relative) << debug_entity(mesh, entity) << debug_entity(mesh, relative)); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_MeshHelpers.hpp b/packages/krino/krino/krino_lib/Akri_MeshHelpers.hpp new file mode 100644 index 000000000000..563be698eaf4 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshHelpers.hpp @@ -0,0 +1,182 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MeshHelpers_h +#define Akri_MeshHelpers_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { class FieldRef; } + +namespace krino { + +typedef std::pair TopologyPartPair; +typedef std::vector TopologyPartVector; + +struct StkMeshEntities +{ + typedef stk::mesh::Entity value_type; + const value_type *mBegin; + const value_type *mEnd; + const value_type * begin() const { return mBegin; } + const value_type * end() const { return mEnd; } + size_t size() const { return mEnd - mBegin; } + bool empty() const { return mEnd == mBegin; } + value_type operator[](int i) const { return *(mBegin + i); } +}; + +void fill_element_node_coordinates(const stk::mesh::BulkData & mesh, stk::mesh::Entity element, const FieldRef coordsField, std::vector & elementNodeCoords); +void fill_procs_owning_or_sharing_or_ghosting_node(const stk::mesh::BulkData& bulkData, stk::mesh::Entity node, std::vector & procsOwningSharingOrGhostingNode); +double compute_maximum_element_size(stk::mesh::BulkData& mesh); +void compute_element_quality(const stk::mesh::BulkData & mesh, double & minEdgeLength, double & maxEdgeLength, double & minVolume, double & maxVolume); +void delete_all_entities_using_nodes_with_nodal_volume_below_threshold(stk::mesh::BulkData & mesh, const stk::mesh::Selector & blockSelector, const double threshold); +std::vector get_side_permutation(stk::topology topology, stk::mesh::Permutation node_permutation); +const stk::mesh::Part & find_element_part(const stk::mesh::BulkData& mesh, stk::mesh::Entity elem); +bool check_induced_parts(const stk::mesh::BulkData & mesh); +void attach_sides_to_elements(stk::mesh::BulkData & mesh); +void attach_entity_to_elements(stk::mesh::BulkData & mesh, stk::mesh::Entity entity); +void unpack_entities_from_other_procs(const stk::mesh::BulkData & mesh, std::set & entities, stk::CommSparse &commSparse); +void update_node_activation(stk::mesh::BulkData & mesh, stk::mesh::Part & active_part); +void activate_all_entities(stk::mesh::BulkData & mesh, stk::mesh::Part & active_part); +void destroy_custom_ghostings(stk::mesh::BulkData & mesh); +void delete_mesh_entities(stk::mesh::BulkData & mesh, std::vector & child_elems); +void debug_print_selector_parts(const stk::mesh::Selector & selector); +stk::mesh::PartVector filter_non_io_parts(const stk::mesh::PartVector & all_parts); +void activate_selected_sides_touching_active_elements(stk::mesh::BulkData & mesh, const stk::mesh::Selector & side_selector, stk::mesh::Part & active_part); +void get_partially_and_fully_coincident_elements(const stk::mesh::BulkData & mesh, stk::mesh::Entity elem, std::vector & coincident_elems); +bool check_element_side_connectivity(const stk::mesh::BulkData & mesh, const stk::mesh::Part & exterior_boundary_part, const stk::mesh::Part & active_part); +bool check_coincident_elements(const stk::mesh::BulkData & mesh, const stk::mesh::Part & active_part); +bool fix_coincident_element_ownership(stk::mesh::BulkData & mesh); +bool fix_face_and_edge_ownership(stk::mesh::BulkData & mesh); +bool check_face_and_edge_ownership(const stk::mesh::BulkData & mesh); +bool check_face_and_edge_relations(const stk::mesh::BulkData & mesh); +bool check_shared_entity_nodes(const stk::mesh::BulkData & mesh, stk::mesh::EntityKey remote_entity_key, std::vector & remote_entity_node_ids); +bool check_shared_entity_nodes(const stk::mesh::BulkData & mesh, std::vector & entities); +bool check_shared_entity_nodes(const stk::mesh::BulkData & mesh); +void disconnect_entity(stk::mesh::BulkData & mesh, stk::mesh::Entity entity); +bool disconnect_and_destroy_entity(stk::mesh::BulkData & mesh, stk::mesh::Entity entity); + +stk::mesh::PartVector get_common_io_parts(const stk::mesh::BulkData & mesh, const std::vector entities); +stk::mesh::PartVector get_removable_parts(const stk::mesh::BulkData & mesh, const stk::mesh::Bucket & bucket); +stk::mesh::PartVector get_removable_parts(const stk::mesh::BulkData & mesh, const stk::mesh::Entity entity); + +void +store_edge_node_parent_ids(const stk::mesh::BulkData & mesh, + const FieldRef & parent_id_field, + stk::mesh::Entity edge_node_entity, + stk::mesh::EntityId parent0_id, + stk::mesh::EntityId parent1_id); + +std::array +get_edge_node_parent_ids(const stk::mesh::BulkData & mesh, + const FieldRef & parent_id_field, + const stk::mesh::Entity edge_node_entity); + +void get_parent_nodes_from_child(const stk::mesh::BulkData & mesh, + stk::mesh::Entity child, const FieldRef & parent_id_field, + std::set & parent_nodes); + +double compute_child_position(const stk::mesh::BulkData & mesh, stk::mesh::Entity child, stk::mesh::Entity parent0, stk::mesh::Entity parent1); + +// topology helpers +// NOTE: These use static storage, but it does not depend on anything so it should be ok for nested or multithreaded usage. +const unsigned * get_side_node_ordinals(stk::topology topology, unsigned side_ordinal); +const unsigned * get_edge_node_ordinals(stk::topology topology, unsigned edge_ordinal); + +std::string debug_entity(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity); +std::string debug_entity(const stk::mesh::BulkData & mesh, stk::mesh::Entity entity, const bool includeFields); + +struct ChildNodeRequest +{ + std::vector parents; + stk::mesh::Entity* child; + + ChildNodeRequest(const std::vector & in_parents, stk::mesh::Entity *in_child) + : parents(in_parents), child(in_child) {} +}; +struct SideRequest +{ + stk::mesh::Entity element; + unsigned element_side_ordinal; + stk::mesh::PartVector side_parts; + + SideRequest(stk::mesh::Entity in_element, unsigned in_element_side_ordinal, const stk::mesh::PartVector & in_side_parts) + : element(in_element), element_side_ordinal(in_element_side_ordinal), side_parts(in_side_parts) {} +}; + +void batch_create_child_nodes(stk::mesh::BulkData & mesh, const std::vector< ChildNodeRequest > & child_node_requests, const stk::mesh::PartVector & node_parts, bool assert_32bit_ids, bool make_64bit_ids); +void batch_create_sides(stk::mesh::BulkData & mesh, const std::vector< SideRequest > & side_requests); +void make_side_ids_consistent_with_stk_convention(stk::mesh::BulkData & mesh); + +double compute_element_volume_to_edge_ratio(stk::mesh::BulkData & mesh, stk::mesh::Entity element, const stk::mesh::Field * const coords_field); + +bool is_refinement_child(const stk::mesh::BulkData & stk_bulk, stk::mesh::Entity entity); +bool has_refinement_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity parent); +void get_refinement_immediate_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity parent, std::vector & children); +void get_refinement_leaf_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity entity, std::vector & leaf_children); +void get_refinement_all_children(const stk::mesh::BulkData& stk_bulk, stk::mesh::Entity entity, std::vector & children); + +// Temporary method for manually correcting the relation permutation +void set_relation_permutation(stk::mesh::BulkData & mesh, stk::mesh::Entity from, stk::mesh::Entity to, stk::mesh::ConnectivityOrdinal to_ord, stk::mesh::Permutation to_permutation); + +std::pair +determine_shell_side_ordinal_and_permutation(const stk::mesh::BulkData & mesh, stk::mesh::Entity shell, stk::mesh::Entity side); + +stk::mesh::Permutation +determine_permutation(const stk::mesh::BulkData & mesh, const stk::mesh::Entity entity, const stk::mesh::Entity relative, const stk::mesh::ConnectivityOrdinal ordinal); + +std::pair +determine_ordinal_and_permutation(const stk::mesh::BulkData & mesh, const stk::mesh::Entity entity, const stk::mesh::Entity relative); + +inline stk::mesh::Entity find_entity_by_ordinal(const stk::mesh::BulkData &mesh, stk::mesh::Entity entity, stk::mesh::EntityRank rank, const unsigned ordinal) +{ + stk::mesh::ConnectivityOrdinal const* relative_ordinals = mesh.begin_ordinals(entity, rank); + stk::mesh::Entity const* relatives = mesh.begin(entity, rank); + const int num_relatives = mesh.num_connectivity(entity, rank); + for (int i = 0; i < num_relatives; ++i) + { + if (relative_ordinals[i] == ordinal) { + return relatives[i]; + } + } + return stk::mesh::Entity(); +} + +template +void pack_entities_for_owning_proc(const stk::mesh::BulkData & mesh, + const CONTAINER & entities, + stk::CommSparse &commSparse) +{ + stk::pack_and_communicate(commSparse,[&]() + { + for (auto entity : entities) + { + const int entityOwner = mesh.parallel_owner_rank(entity); + if (commSparse.parallel_rank() != entityOwner) + commSparse.send_buffer(entityOwner).pack(mesh.entity_key(entity)); + } + }); +} + +} // namespace krino + +#endif // Akri_MeshHelpers_h diff --git a/packages/krino/krino/krino_lib/Akri_MeshInputOptions.cpp b/packages/krino/krino/krino_lib/Akri_MeshInputOptions.cpp new file mode 100644 index 000000000000..aeda2c5e95eb --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshInputOptions.cpp @@ -0,0 +1,52 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +namespace krino { + +MeshInputOptions::Registry MeshInputOptions::the_registry; + +std::shared_ptr +MeshInputOptions::get_or_create(const std::string & model_name) +{ + std::shared_ptr options = std::make_shared(model_name); + std::pair result = the_registry.insert(options); + + if ( ! result.second ) { + stk::RuntimeWarningAdHoc() << "A mesh database named '" << model_name + << "' has already been defined and will be reused"; + } + + return *result.first; +} + +MeshInputOptions * +MeshInputOptions::get(const std::string &model_name) +{ + MeshInputOptions *options = nullptr; + std::shared_ptr tmp = std::make_shared(model_name); + + Registry::iterator iter = the_registry.find(tmp); + + if ( iter != the_registry.end() ) + options = iter->get() ; + + return options; +} + +int MeshInputOptions::get_generated_mesh_spatial_dimension() const +{ + ThrowRequire(use_generated_mesh()); + return (my_generated_mesh_domain_type == GENERATED_2D_MESH_FOR_INTERFACE_BOUNDING_BOX || my_generated_mesh_domain.size() == 4) ? 2 : 3; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_MeshInputOptions.hpp b/packages/krino/krino/krino_lib/Akri_MeshInputOptions.hpp new file mode 100644 index 000000000000..203d4ee5d6bd --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshInputOptions.hpp @@ -0,0 +1,112 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MeshInputOptions_h +#define Akri_MeshInputOptions_h + +#include +#include +#include +#include +#include +#include + +namespace krino { + + class MeshInputOptions { + + public: + static std::shared_ptr get_or_create(const std::string & model_name); + static MeshInputOptions * get(const std::string & model_name); + + enum GeneratedMeshDomainType + { + NO_GENERATED_MESH=0, + GENERATED_MESH_FOR_SPECIFIED_DOMAIN, + GENERATED_2D_MESH_FOR_INTERFACE_BOUNDING_BOX, + GENERATED_3D_MESH_FOR_INTERFACE_BOUNDING_BOX + }; + + MeshInputOptions(const std::string & name) : + my_name(name), + my_filename("%B.g"), + my_filetype("exodusII"), + my_generated_mesh_domain_type(NO_GENERATED_MESH), + myGeneratedMeshStructureType(BoundingBoxMeshStructureType::CUBIC_BOUNDING_BOX_MESH), + my_generated_mesh_size(-1.0) {} + + const std::string & get_name() const { return my_name; } + bool is_valid() const + { + return !my_name.empty() && ((! my_filetype.empty() && ! my_filename.empty()) || use_generated_mesh()) ; + } + + void set_generated_mesh_structure_type(BoundingBoxMeshStructureType type) { myGeneratedMeshStructureType = type; } + BoundingBoxMeshStructureType get_generated_mesh_structure_type() const { return myGeneratedMeshStructureType; } + + bool use_generated_mesh() const { return my_generated_mesh_domain_type != NO_GENERATED_MESH; } + + void set_generated_mesh_domain_type(GeneratedMeshDomainType type) { my_generated_mesh_domain_type = type; } + GeneratedMeshDomainType get_generated_mesh_domain_type() const { return my_generated_mesh_domain_type; } + + int get_generated_mesh_spatial_dimension() const; + + void set_generated_mesh_size(double size) { my_generated_mesh_size = size; } + double get_generated_mesh_size() const { return my_generated_mesh_size; } + + void set_generated_mesh_element_type(stk::topology top) { my_generated_mesh_element_type = top; } + stk::topology get_generated_mesh_element_type() const { return my_generated_mesh_element_type; } + + void set_generated_mesh_domain(const std::vector & domain) { my_generated_mesh_domain = domain; } + const std::vector & get_generated_mesh_domain() const { return my_generated_mesh_domain; } + + void set_filename(std::string filename) { my_filename = filename; } + const std::string & get_filename() const { return my_filename; } + + void set_filetype(std::string type) { my_filetype = type; } + const std::string & get_filetype() const { return my_filetype; } + + void set_decomposition_method(std::string decomposition_method) { my_decomposition_method = decomposition_method; } + const std::string & get_decomposition_method() const { return my_decomposition_method; } + + void set_coordinate_system(std::string coordinate_system) { my_coordinate_system = coordinate_system; } + const std::string & get_coordinate_system() const { return my_coordinate_system; } + + void add_property(const Ioss::Property & property) { my_properties.add(property); } + Ioss::PropertyManager & get_properties() { return my_properties; } + + private: + struct compare_ptr { + bool operator()( std::shared_ptr const lhs , + std::shared_ptr const rhs ) const + { + return lhs.get() == nullptr ? rhs.get() != nullptr : + ( rhs.get() == nullptr ? false : lhs->get_name() < rhs->get_name() ); + } + }; + + typedef std::set, compare_ptr> Registry; + static Registry the_registry; + + private: + std::string my_name; + std::string my_filename; + std::string my_filetype; + GeneratedMeshDomainType my_generated_mesh_domain_type; + BoundingBoxMeshStructureType myGeneratedMeshStructureType; + std::vector my_generated_mesh_domain; + stk::topology my_generated_mesh_element_type = stk::topology::INVALID_TOPOLOGY; + double my_generated_mesh_size; + std::string my_decomposition_method; + std::string my_coordinate_system; + Ioss::PropertyManager my_properties; + }; + +} // namespace krino + +#endif /* Akri_MeshInputOptions_h */ diff --git a/packages/krino/krino/krino_lib/Akri_MeshSurface.cpp b/packages/krino/krino/krino_lib/Akri_MeshSurface.cpp new file mode 100644 index 000000000000..36a535fb3da4 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshSurface.cpp @@ -0,0 +1,1019 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace krino{ + +void Parallel_Facet_File_Reader::read(const std::string & read_description, const std::function & read_function) +{ + bool ok_locally = true; + + if (0 == stk::EnvData::parallel_rank() ) + { + my_input.exceptions(std::ifstream::failbit | std::ifstream::badbit); + try + { + read_function(); + } + catch (std::ifstream::failure & e) + { + ok_locally = false; + } + } + const bool ok_globally = stk::is_true_on_all_procs(stk::EnvData::parallel_comm(), ok_locally); + if (!ok_globally) + { + ThrowRuntimeError("Error " << read_description << " for file " << my_filename); + } +} + +void Parallel_Facet_File_Reader::open_file() +{ + read("opening file", [this](){my_input.open(my_filename.c_str());}); +} + +void Parallel_Facet_File_Reader::get_batch_size(const int local_num_facets, int & batch_size, int & num_batches) +{ + int num_facets = 0; + stk::all_reduce_sum( stk::EnvData::parallel_comm(), &local_num_facets, &num_facets, 1 ); + + const int min_batch_size = std::min(2048, num_facets); + batch_size = std::max(min_batch_size, 1+num_facets/stk::EnvData::parallel_size()); + num_batches = 1+num_facets/batch_size; + + if(krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "Reading " << num_facets << " facets, using " << num_batches << " batches of " << batch_size << "." << stk::diag::dendl; +} + +Faceted_Surface_From_File::Faceted_Surface_From_File(const std::string & surface_name, const stk::diag::Timer &parent_timer) +: Faceted_Surface(surface_name), + my_timer("Facet File Reader", parent_timer), + my_built_local_facets(false) +{ +} + +void Faceted_Surface_From_File::build_local_facets(const BoundingBox & proc_bbox) +{ + stk::diag::TimeBlock timer_(my_timer); + + if(my_built_local_facets) return; + + std::vector proc_bboxes; + BoundingBox::gather_bboxes( proc_bbox, proc_bboxes ); + + read_file(proc_bboxes); + my_built_local_facets = true; +} + +void Parallel_Facet_File_Reader::close_file() +{ + read("closing file", [this](){my_input.close();}); +} + +FACSurface::FACSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale) + : Faceted_Surface_From_File(surface_name, parent_timer), + my_reader(filename), + my_dist_sign(sign), + my_scale(scale) +{ + // Make sure the file is openable, but close it for now to avoid ulimit restrictions. + my_reader.open_file(); + my_reader.close_file(); +} + +BoundingBox +FACSurface::get_bounding_box() +{ + BoundingBox bbox; + + my_reader.open_file(); + + std::vector points; + my_reader.read("reading points", [this, &points](){read_points(points);}); + + for (auto && point : points) + { + bbox.accommodate(point); + } + + my_reader.close_file(); + + bbox.global_reduce(); + return bbox; +} + +void FACSurface::read_file(const std::vector & proc_bboxes) +{ + my_reader.open_file(); + + std::ifstream & input = my_reader.input(); + std::vector points; + my_reader.read("reading points", [this, &points](){read_points(points);}); + + int num_facets = 0; + my_reader.read("reading number of facets", [&input, &num_facets](){input >> num_facets; ThrowRequire(num_facets > 0);}); + + int batch_size = 0; + int num_batches = 0; + my_reader.get_batch_size(num_facets, batch_size, num_batches); + + for (int batch = 0; batch < num_batches; ++batch) + { + const int current_batch_size = std::min(batch_size, num_facets-batch*batch_size); + my_reader.read("reading facets", [this, &points, current_batch_size, num_facets](){read_facets(current_batch_size, num_facets, points);}); + parallel_distribute_facets(current_batch_size, proc_bboxes); + } + + my_reader.close_file(); +} + +void FACSurface::read_points(std::vector & points) +{ + std::ifstream & input = my_reader.input(); + int num_points; + input >> num_points; + ThrowRequire(num_points > 0); + points.reserve(num_points); + for ( int i = 0; i < num_points; i++ ) + { + int id; + double X, Y, Z; + input >> id; + ThrowRequire(id >= 0 && id < num_points); + input >> X >> Y >> Z; + points.emplace_back(X*my_scale[0], Y*my_scale[1], Z*my_scale[2]); + } +} + +void FACSurface::read_facets(const int batch_size, const int num_facets, const std::vector & points) +{ + std::ifstream & input = my_reader.input(); + const int num_points = points.size(); + for ( int i = 0; i < batch_size; ++i ) + { + int id; + int p1, p2, p3; + input >> id; + ThrowRequire(id >= 0 && id < num_facets); + input >> p1 >> p2 >> p3; + + ThrowRequire(p1 >= 0 && p1 < num_points); + ThrowRequire(p2 >= 0 && p2 < num_points); + ThrowRequire(p3 >= 0 && p3 < num_points); + + if (my_dist_sign == -1) + { + // permute to flip normal direction + int tmp = p1; + p1 = p2; + p2 = tmp; + } + std::unique_ptr facet = std::make_unique( points[p1], points[p2], points[p3] ); + add( std::move(facet) ); + } +} + +PLYSurface::PLYSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale) + : Faceted_Surface_From_File(surface_name, parent_timer), + my_reader(filename), + my_dist_sign(sign), + my_scale(scale) +{ + // Make sure the file is openable, but close it for now to avoid ulimit restrictions. + my_reader.open_file(); + my_reader.close_file(); +} + +BoundingBox PLYSurface::get_bounding_box() +{ + BoundingBox bbox; + + my_reader.open_file(); + + int num_points = 0; + int num_facets = 0; + + my_reader.read("reading file header", [this, &num_points, &num_facets](){read_header(num_points, num_facets);}); + + std::vector points; + my_reader.read("reading points", [this, num_points, &points](){read_points(num_points, points);}); + + for (auto && point : points) + { + bbox.accommodate(point); + } + + my_reader.close_file(); + + bbox.global_reduce(); + + return bbox; +} + +void PLYSurface::read_file(const std::vector & proc_bboxes) +{ + my_reader.open_file(); + + int num_points = 0; + int num_facets = 0; + + my_reader.read("reading file header", [this, &num_points, &num_facets](){read_header(num_points, num_facets);}); + + std::vector points; + my_reader.read("reading points", [this, num_points, &points](){read_points(num_points, points);}); + + int batch_size = 0; + int num_batches = 0; + my_reader.get_batch_size(num_facets, batch_size, num_batches); + + for (int batch = 0; batch < num_batches; ++batch) + { + const int current_batch_size = std::min(batch_size, num_facets-batch*batch_size); + my_reader.read("reading facets", + [this, &points, current_batch_size]() { read_facets(current_batch_size, points); }); + parallel_distribute_facets(current_batch_size, proc_bboxes); + } + + my_reader.close_file(); +} + +void PLYSurface::read_header(int & num_points, int & num_facets) +{ + std::ifstream & input = my_reader.input(); + num_points = 0; + num_facets = 0; + + // Read in the file identifier + std::string symbol; + input >> symbol; + ThrowRequire(symbol.compare("ply") == 0); + + while (symbol.compare("end_header") != 0) + { + ThrowErrorMsgIf(input.eof(), "Problem reading PLY file, reached end of file."); + input >> symbol; + if (symbol.compare("element") == 0) + { + input >> symbol; + if (symbol.compare("vertex") == 0) + { + input >> num_points; + } + else if (symbol.compare("face") == 0) + { + input >> num_facets; + } + } + } +} + +void PLYSurface::read_points(const int num_points, std::vector & points) +{ + std::ifstream & input = my_reader.input(); + points.clear(); + points.reserve(num_points); + for ( int i = 0; i < num_points; i++ ) + { + double X, Y, Z; + input >> X >> Y >> Z; + points.emplace_back(X*my_scale[0], Y*my_scale[1], Z*my_scale[2]); + } + // Move to start of next line to prepare for reading facets + std::string line; + std::getline(input,line); +} + +void PLYSurface::read_facets(const int batch_size, const std::vector & points) +{ + std::ifstream & input = my_reader.input(); + const unsigned num_points = points.size(); + std::string line; + for ( int i = 0; i < batch_size; ++i ) + { + unsigned num_facet_nodes; + unsigned p1, p2, p3; + std::getline(input,line); + std::stringstream linestream(line); + linestream >> num_facet_nodes; + ThrowRequireMsg(num_facet_nodes == 3, "Failed to read face connectivity correctly."); + linestream >> p1 >> p2 >> p3; + ThrowRequire(p1 < num_points); + ThrowRequire(p2 < num_points); + ThrowRequire(p3 < num_points); + + if (my_dist_sign == -1) + { + // permute to flip normal direction + int tmp = p1; + p1 = p2; + p2 = tmp; + } + std::unique_ptr facet = std::make_unique( points[p1], points[p2], points[p3] ); + add( std::move(facet) ); + } +} + +STLSurface::STLSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale) + : Faceted_Surface_From_File(surface_name, parent_timer), + my_is_ascii(true), + my_reader(filename), + my_dist_sign(sign), + my_scale(scale) +{ + // Make sure the file is openable, but close it for now to avoid ulimit restrictions. + my_reader.open_file(); + my_reader.close_file(); +} + +static std::unique_ptr build_facet(const std::array & pts, const int sign) +{ + unsigned p0 = 0; + unsigned p1 = 1; + unsigned p2 = 2; + if (sign == -1) + { + // permute to flip normal direction + p1 = 2; + p2 = 1; + } + + std::unique_ptr facet = std::make_unique( pts[p0], pts[p1], pts[p2] ); + return facet; +} + +static bool is_finite_normal(const Vector3d normal) +{ + bool finiteFlag = true; + if (!std::isfinite(normal[0]) || + !std::isfinite(normal[1]) || + !std::isfinite(normal[2]) ) + { + finiteFlag = false; + krinolog << "Krino::STLSurface found an invalid facet and is skipping." << std::endl; + krinolog << " facet normal " << normal[0] << " " << normal[1] << " " << normal[2] << std::endl; + } + return finiteFlag; +} + +static std::unique_ptr read_ascii_facet(std::ifstream & input, const Vector3d & scale, const int sign) +{ + std::string symbol, nx, ny, nz; + double X, Y, Z; + + // Read in the facet normal + input >> symbol; + ThrowRequire(symbol.compare("normal") == 0); + input >> nx >> ny >> nz; + const Vector3d normal(std::atof(nx.c_str()), std::atof(ny.c_str()), std::atof(nz.c_str())); + + // Read in the "outer loop" line + input >> symbol; + ThrowRequire(symbol.compare("outer") == 0); + input >> symbol; + ThrowRequire(symbol.compare("loop") == 0); + + // Read in the vertices + std::array pts; + for (int i=0; i<3; i++) { + input >> symbol; + ThrowRequire(symbol.compare("vertex") == 0); + input >> X >> Y >> Z; + + pts[i] = Vector3d(X*scale[0], Y*scale[1], Z*scale[2]); + } + + // Read in the "endloop" and "endfacet" lines + input >> symbol; + ThrowRequire(symbol.compare("endloop") == 0); + input >> symbol; + ThrowRequire(symbol.compare("endfacet") == 0); + + std::unique_ptr facet; + if (is_finite_normal(normal)) + { + facet = build_facet(pts, sign); + if (facet->degenerate()) + facet.reset(); + } + + return facet; +} + +static bool read_start_of_next_ascii_facet(std::ifstream & input) +{ + // Read the next line + std::string symbol; + input >> symbol; + return symbol.compare("facet") == 0; +} + +static float read_binary_float(std::ifstream& input) +{ + float val = 0; + input.read((char *) &val, sizeof(val)); + return val; +} + +static Vector3d read_binary_vector(std::ifstream& input) +{ + float x = read_binary_float(input); + float y = read_binary_float(input); + float z = read_binary_float(input); + return Vector3d(x, y, z); +} + +static std::unique_ptr read_binary_facet(std::ifstream & input, const Vector3d & scale, const int sign) +{ + const Vector3d normal = read_binary_vector(input); + + std::array pts; + for (auto && pt : pts) + { + const Vector3d vec = read_binary_vector(input); + pt = Vector3d(vec[0]*scale[0], vec[1]*scale[1], vec[2]*scale[2]); + } + + char dummy[2]; + input.read(dummy, 2); + + std::unique_ptr facet; + if (is_finite_normal(normal)) + { + facet = build_facet(pts, sign); + if (facet->degenerate()) + facet.reset(); + } + + return facet; +} + +BoundingBox +STLSurface::get_bounding_box() +{ + BoundingBox bbox; + + my_reader.open_file(); + + my_reader.read("reading file header", [this](){read_header();}); + my_is_ascii = stk::is_true_on_all_procs(stk::EnvData::parallel_comm(), my_is_ascii); + + if (my_is_ascii) + { + my_reader.read("reading facets", [this, &bbox](){bbox = get_ascii_facet_bounding_box();}); + } + else + { + unsigned numFacets = 0; + my_reader.read("reading num facets", [this, &numFacets](){numFacets = read_num_binary_facets();}); + my_reader.read("reading facets", [this, &bbox, numFacets](){bbox = get_binary_facet_bounding_box(numFacets);}); + } + + my_reader.close_file(); + + bbox.global_reduce(); + return bbox; +} + +void +STLSurface::read_file(const std::vector & proc_bboxes) +{ + my_reader.open_file(); + + my_reader.read("reading file header", [this](){read_header();}); + my_is_ascii = stk::is_true_on_all_procs(stk::EnvData::parallel_comm(), my_is_ascii); + + unsigned numBinaryFacets = 0; + if (!my_is_ascii) + my_reader.read("reading num facets", [this, &numBinaryFacets](){numBinaryFacets = read_num_binary_facets();}); + + const unsigned maxBatchSize = 8192; + + unsigned numValidFacetsRead = 0; + bool done = false; + while (!done) + { + unsigned batchSize = 0; + + if (my_is_ascii) + { + my_reader.read("reading facets", [this, &batchSize](){batchSize = read_ascii_facets(maxBatchSize);}); + } + else + { + my_reader.read("reading facets", [this, &batchSize, &numBinaryFacets](){batchSize = read_binary_facets(maxBatchSize, numBinaryFacets);}); + } + + const unsigned localBatchSize = batchSize; + stk::all_reduce_max( stk::EnvData::parallel_comm(), &localBatchSize, &batchSize, 1 ); + numValidFacetsRead += batchSize; + + parallel_distribute_facets(batchSize, proc_bboxes); + + done = batchSize < maxBatchSize; + } + + my_reader.close_file(); + + krinolog << "Read " << (my_is_ascii ? "ASCII" : "binary") << " STL file " << my_reader.filename() << " with " << numValidFacetsRead << " valid facets." << stk::diag::dendl; +} + +void +STLSurface::read_header() +{ + std::ifstream & input = my_reader.input(); + + std::string symbol; + input >> symbol; + my_is_ascii = symbol.compare("solid") == 0; + + if (my_is_ascii) + { + // Read in strings until you get to facet + while (symbol.compare("facet") != 0) + { + ThrowErrorMsgIf(input.eof(), "Problem reading STL file, no facets found."); + input >> symbol; + } + krinolog << "Reading ASCII STL file." << stk::diag::dendl; + } + else + { + char header_info[80] = ""; + input.clear(); + input.seekg(0); + input.read(header_info, 80); + ThrowErrorMsgIf(!input.good(), "Problem reading STL file, cannot read binary file header."); + } +} + +unsigned STLSurface::read_num_binary_facets() +{ + ThrowAssert(!my_is_ascii); + std::ifstream & input = my_reader.input(); + unsigned numFacets = 0; + input.read((char *) &numFacets, sizeof(numFacets)); + ThrowErrorMsgIf(!input.good(), "Problem reading STL file, cannot read number of facets."); + krinolog << "Reading binary STL file with " << numFacets << " facets." << stk::diag::dendl; + return numFacets; +} + +unsigned STLSurface::read_ascii_facets(const unsigned max_batch_size) +{ + std::ifstream & input = my_reader.input(); + + unsigned count = 0; + bool done = false; + while (!done) + { + std::unique_ptr facet = read_ascii_facet(input, my_scale, my_dist_sign); + if (facet) + { + add(std::move(facet)); + ++count; + } + done = (!read_start_of_next_ascii_facet(input) || count >= max_batch_size); + } + return count; +} + +unsigned STLSurface::read_binary_facets(const unsigned maxBatchSize, unsigned & numRemainingInFile) +{ + std::ifstream & input = my_reader.input(); + + unsigned count = 0; + bool done = numRemainingInFile == 0; + while (!done) + { + std::unique_ptr facet = read_binary_facet(input, my_scale, my_dist_sign); + if (facet) + { + add(std::move(facet)); + ++count; + } + done = (--numRemainingInFile == 0 || count >= maxBatchSize); + } + + return count; +} + +BoundingBox STLSurface::get_ascii_facet_bounding_box() +{ + std::ifstream & input = my_reader.input(); + + BoundingBox bbox; + + bool done = false; + while (!done) + { + std::unique_ptr facet = read_ascii_facet(input, my_scale, my_dist_sign); + if (facet) + bbox.accommodate(facet->bounding_box()); + done = !read_start_of_next_ascii_facet(input); + } + return bbox; +} + +BoundingBox STLSurface::get_binary_facet_bounding_box(const unsigned numFacets) +{ + std::ifstream & input = my_reader.input(); + + BoundingBox bbox; + + for (unsigned count=0; count facet = read_binary_facet(input, my_scale, my_dist_sign); + if (facet) + bbox.accommodate(facet->bounding_box()); + } + return bbox; +} + +EXOSurface::EXOSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale) + : Faceted_Surface_From_File(surface_name, parent_timer), + my_filename(filename), + my_dist_sign(sign), + my_scale(scale) +{ +} + +BoundingBox +EXOSurface::get_bounding_box() +{ + BoundingBox bbox; + + std::vector xyz; + std::unique_ptr io; + if ( 0 == stk::EnvData::parallel_rank() ) + { + /* open file */ + Ioss::DatabaseIO *db = Ioss::IOFactory::create("exodusII", my_filename.c_str(), Ioss::READ_MODEL, MPI_COMM_SELF); + if ( !db ) { + ThrowRuntimeError("error reading file " << my_filename); + } + io = std::make_unique(db, "EXOSurface IC Region"); + + /* exodus description */ + int num_dim = io->get_property("spatial_dimension").get_int(); + int num_nodes = io->get_property("node_count").get_int(); + + ThrowAssert( 3 == num_dim ); + + /* generate coordinates */ + xyz.resize(num_nodes*num_dim); + + Ioss::NodeBlock *nb = io->get_node_blocks()[0]; + nb->get_field_data("mesh_model_coordinates", xyz); + + for (int n = 0; n & proc_bboxes) +{ + const int nodes_per_elem = 3; + int num_elem_blk = 0; + std::vector xyz; + std::vector nmap; + std::unique_ptr io; + if ( 0 == stk::EnvData::parallel_rank() ) + { + /* open file */ + Ioss::DatabaseIO *db = Ioss::IOFactory::create("exodusII", my_filename.c_str(), Ioss::READ_MODEL, MPI_COMM_SELF); + if ( !db ) { + ThrowRuntimeError("error reading file " << my_filename); + } + io = std::make_unique(db, "EXOSurface IC Region"); + + krinolog << "opened file " << my_filename << " for reading..." << std::endl; + + /* exodus description */ + int num_dim = io->get_property("spatial_dimension").get_int(); + int num_nodes = io->get_property("node_count").get_int(); + int num_elem = io->get_property("element_count").get_int(); + num_elem_blk = io->get_property("element_block_count").get_int(); + + krinolog + << "num dim = " << num_dim + << ", num nodes = " << num_nodes + << ", num elems = " << num_elem + << ", num elem blks = " << num_elem_blk << std::endl; + + ThrowAssert( 3 == num_dim ); + + /* generate coordinates */ + xyz.resize(num_nodes*num_dim); + nmap.resize(num_nodes); + + Ioss::NodeBlock *nb = io->get_node_blocks()[0]; + nb->get_field_data("mesh_model_coordinates", xyz); + nb->get_field_data("ids", nmap); + } + + const int local_num_elem_blk = num_elem_blk; + stk::all_reduce_max( stk::EnvData::parallel_comm(), &local_num_elem_blk, &num_elem_blk, 1 ); + + + for (int blk = 0; blk < num_elem_blk; blk++ ) + { + int num_elem_in_blk = 0; + std::vector conn; + + if ( 0 == stk::EnvData::parallel_rank() ) + { + Ioss::ElementBlockContainer ebs = io->get_element_blocks(); + Ioss::ElementBlock *eb = ebs[blk]; + num_elem_in_blk = eb->get_property("entity_count").get_int(); + + std::string eb_name = eb->name(); + krinolog + << "Reading elem blk #" << blk+1 + << " with name " << eb_name << "..." << std::endl; + + int nodes_per_elem_in_blk = eb->get_property("topology_node_count").get_int(); + ThrowAssert( nodes_per_elem_in_blk == nodes_per_elem ); + + conn.resize(num_elem_in_blk*nodes_per_elem_in_blk); + + krinolog + << " num elem in blk = " << num_elem_in_blk + << ", nodes per elem in blk = " << nodes_per_elem_in_blk << std::endl; + + eb->get_field_data("connectivity", conn); + } + + int batch_size = 0; + int num_batches = 0; + Parallel_Facet_File_Reader::get_batch_size(num_elem_in_blk, batch_size, num_batches); + + for (int batch = 0; batch < num_batches; ++batch) + { + const int current_batch_size = std::min(batch_size, num_elem_in_blk-batch*batch_size); + if ( 0 == stk::EnvData::parallel_rank() ) + { + Vector3d pt[3]; + for ( int e = 0; e < current_batch_size; e++ ) + { + for ( int j = 0; j < nodes_per_elem; j++ ) + { + const int nodeid = conn[e*nodes_per_elem + j]; + const int n = std::distance(nmap.begin(), std::find(nmap.begin(), nmap.end(), nodeid)); + double nodeX = xyz[3*n+0]; + double nodeY = xyz[3*n+1]; + double nodeZ = xyz[3*n+2]; + pt[j] = Vector3d( nodeX*my_scale[0], nodeY*my_scale[1], nodeZ*my_scale[2] ); + } + + unsigned p0 = 0; + unsigned p1 = 1; + unsigned p2 = 2; + if (my_dist_sign == -1) + { + // permute to flip normal direction + p1 = 2; + p2 = 1; + } + + std::unique_ptr facet = std::make_unique( pt[p0], pt[p1], pt[p2] ); + add( std::move(facet) ); + } + } + parallel_distribute_facets(current_batch_size, proc_bboxes); + } + } +} + +MeshSurface::MeshSurface(const stk::mesh::MetaData & meta, + const stk::mesh::Field& coord_ref, + const stk::mesh::Selector & surface_selector, + const int sign) + : Faceted_Surface("Mesh surface"), + my_sign(sign), + my_mesh_meta(meta), + my_coord_ref(coord_ref), + my_surface_selector(surface_selector) +{ +} + +void +MeshSurface::build_local_facets(const BoundingBox & proc_bbox) +{ + /* %TRACE[ON]% */ Trace trace__("krino::MeshSurface::build_local_facets()"); /* %TRACE% */ + const stk::mesh::BulkData & mesh = my_mesh_meta.mesh_bulk_data(); + stk::mesh::Selector active_locally_owned_selector = + (NULL != my_mesh_meta.get_part("ACTIVE_CONTEXT_BIT")) ? + (*my_mesh_meta.get_part("ACTIVE_CONTEXT_BIT") & my_mesh_meta.locally_owned_part()) : + stk::mesh::Selector(my_mesh_meta.locally_owned_part()); + + stk::mesh::Selector active_locally_owned_part_selector = active_locally_owned_selector & my_surface_selector; + + stk::mesh::BucketVector const& buckets = mesh.get_buckets( my_mesh_meta.side_rank(), active_locally_owned_part_selector); + + clear(); + + stk::mesh::BucketVector::const_iterator ib = buckets.begin(); + stk::mesh::BucketVector::const_iterator ib_end = buckets.end(); + + for ( ; ib != ib_end ; ++ib ) + { + const stk::mesh::Bucket & b = **ib; + const unsigned length = b.size(); + + for ( unsigned iSide = 0; iSide < length; ++iSide ) + { + stk::mesh::Entity side = b[iSide]; + + stk::topology side_topology = mesh.bucket(side).topology(); + if (stk::topology::TRI_3 == side_topology) + { + add_facet3d(mesh,side,0,1,2); + } + else if (stk::topology::QUAD_4 == side_topology) + { + add_quad3d(mesh,side,0,1,2,3); + } + else if (stk::topology::TRI_6 == side_topology) + { + add_facet3d(mesh,side,0,3,5); + add_facet3d(mesh,side,1,4,3); + add_facet3d(mesh,side,2,5,4); + add_facet3d(mesh,side,3,4,5); + } + else if (stk::topology::LINE_2 == side_topology) + { + add_facet2d(mesh,side,0,1); + } + else if (stk::topology::LINE_3 == side_topology) + { + add_facet2d(mesh,side,0,2); + add_facet2d(mesh,side,2,1); + } + else + { + ThrowRuntimeError("Elements with side topology " << side_topology.name() << " not supported for mesh surface initialization."); + } + } + } +} + +void +MeshSurface::add_facet2d(const stk::mesh::BulkData& mesh, stk::mesh::Entity side, unsigned p0, unsigned p1) +{ + ThrowAssert(2 == my_mesh_meta.spatial_dimension()); + Vector3d pt[2]; + + if (my_sign == -1) + { + // permute to flip normal direction + const unsigned tmp = p0; + p0 = p1; + p1 = tmp; + } + + const stk::mesh::Entity* side_nodes = mesh.begin_nodes(side); + + const double * pt0 = stk::mesh::field_data(my_coord_ref, side_nodes[p0]); + const double * pt1 = stk::mesh::field_data(my_coord_ref, side_nodes[p1]); + + pt[0][0] = pt0[0]; + pt[0][1] = pt0[1]; + pt[0][2] = 0.0; + pt[1][0] = pt1[0]; + pt[1][1] = pt1[1]; + pt[1][2] = 0.0; + + add_facet2d(pt[0], pt[1]); +} + +void +MeshSurface::add_facet3d(const stk::mesh::BulkData& mesh, stk::mesh::Entity side, unsigned p0, unsigned p1, unsigned p2) +{ + ThrowAssert(3 == my_mesh_meta.spatial_dimension()); + Vector3d pt[3]; + + if (my_sign == -1) + { + // permute to flip normal direction + const unsigned tmp = p1; + p1 = p2; + p2 = tmp; + } + + const stk::mesh::Entity* side_nodes = mesh.begin_nodes(side); + + pt[0] = Vector3d(stk::mesh::field_data(my_coord_ref, side_nodes[p0])); + pt[1] = Vector3d(stk::mesh::field_data(my_coord_ref, side_nodes[p1])); + pt[2] = Vector3d(stk::mesh::field_data(my_coord_ref, side_nodes[p2])); + + add_facet3d(pt[0], pt[1], pt[2]); +} + +void +MeshSurface::add_quad3d(const stk::mesh::BulkData& mesh, stk::mesh::Entity side, unsigned p0, unsigned p1, unsigned p2, unsigned p3) +{ + ThrowAssert(3 == my_mesh_meta.spatial_dimension()); + Vector3d pt[5]; + + if (my_sign == -1) + { + // permute to flip normal direction + const unsigned tmp = p1; + p1 = p3; + p3 = tmp; + } + + const stk::mesh::Entity* side_nodes = mesh.begin_nodes(side); + + pt[0] = Vector3d(stk::mesh::field_data(my_coord_ref, side_nodes[p0])); + pt[1] = Vector3d(stk::mesh::field_data(my_coord_ref, side_nodes[p1])); + pt[2] = Vector3d(stk::mesh::field_data(my_coord_ref, side_nodes[p2])); + pt[3] = Vector3d(stk::mesh::field_data(my_coord_ref, side_nodes[p3])); + + // scale is the RMS of the diagonal lengths, sqr_scale is scale*scale, or the average of the lengths squared + const double sqr_scale = 0.5*((pt[0]-pt[2]).length_squared()+(pt[1]-pt[3]).length_squared()); + // tol is 1e-4, sqr_tol is 1e-8 + const double sqr_tol = 1.e-8; + // Test is that the distance between the midpoints of the 2 diagonals is less than tol*scale + const bool is_planar = (0.5*(pt[0]+pt[2])-0.5*(pt[1]+pt[3])).length_squared() < sqr_tol*sqr_scale; + //const bool is_planar = false; // conservative approach, forces 4 facets per quad + + if (is_planar) + { + add_facet3d(pt[0], pt[1], pt[2]); + add_facet3d(pt[0], pt[2], pt[3]); + } + else + { + pt[4] = 0.25 * (pt[0]+pt[1]+pt[2]+pt[3]); + add_facet3d(pt[0], pt[1], pt[4]); + add_facet3d(pt[1], pt[2], pt[4]); + add_facet3d(pt[2], pt[3], pt[4]); + add_facet3d(pt[3], pt[0], pt[4]); + } +} + +void +MeshSurface::add_facet2d(Vector3d & p0, Vector3d & p1) +{ + ThrowAssert(2 == my_mesh_meta.spatial_dimension()); + + std::unique_ptr facet = std::make_unique( p0, p1 ); + add( std::move(facet) ); +} + +void +MeshSurface::add_facet3d(Vector3d & p0, Vector3d & p1, Vector3d & p2) +{ + ThrowAssert(3 == my_mesh_meta.spatial_dimension()); + + std::unique_ptr facet = std::make_unique( p0, p1, p2 ); + add( std::move(facet) ); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_MeshSurface.hpp b/packages/krino/krino/krino_lib/Akri_MeshSurface.hpp new file mode 100644 index 000000000000..7f2ec3c199c7 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MeshSurface.hpp @@ -0,0 +1,177 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MeshSurface_h +#define Akri_MeshSurface_h + +#include + +#include +#include + +#include +#include + +namespace stk { namespace mesh { class BulkData; } } +namespace stk { namespace mesh { class Entity; } } +namespace stk { namespace mesh { class MetaData; } } +namespace stk { namespace mesh { class Selector; } } + +namespace krino { + +class MeshSurface : public Faceted_Surface { +public: + MeshSurface(const stk::mesh::MetaData & meta, + const stk::mesh::Field& coord_ref, + const stk::mesh::Selector & surface_selector, + const int sign); + + virtual ~MeshSurface() {} + virtual void build_local_facets(const BoundingBox & proc_bbox) override; + +protected: + void add_facet2d(const stk::mesh::BulkData& mesh, stk::mesh::Entity side, unsigned p0, unsigned p1); + void add_facet3d(const stk::mesh::BulkData& mesh, stk::mesh::Entity side, unsigned p0, unsigned p1, unsigned p2); + void add_quad3d(const stk::mesh::BulkData& mesh, stk::mesh::Entity side, unsigned p0, unsigned p1, unsigned p2, unsigned p3); + void add_facet2d(Vector3d & p0, Vector3d & p1); + void add_facet3d(Vector3d & p0, Vector3d & p1, Vector3d & p2); + +private: + int my_sign; + const stk::mesh::MetaData & my_mesh_meta; + const stk::mesh::Field& my_coord_ref; + const stk::mesh::Selector my_surface_selector; +}; + +class Parallel_Facet_File_Reader { +public: + Parallel_Facet_File_Reader(const std::string & in_filename) : my_filename(in_filename) {} + + static void get_batch_size(const int local_num_facets, int & batch_size, int & num_batches); + + std::ifstream & input() { return my_input; } + const std::string & filename() const { return my_filename; } + + void open_file(); + void close_file(); + void read(const std::string & read_description, const std::function & read_function); + +private: + std::string my_filename; + std::ifstream my_input; +}; + +class Faceted_Surface_From_File : public Faceted_Surface { +public: + Faceted_Surface_From_File(const std::string & surface_name, const stk::diag::Timer &parent_timer); + virtual ~Faceted_Surface_From_File() {} + virtual void build_local_facets(const BoundingBox & proc_bbox) override; + virtual BoundingBox get_bounding_box() override = 0; + +protected: + virtual void read_file(const std::vector & proc_bboxes) = 0; +private: + stk::diag::Timer my_timer; + bool my_built_local_facets; +}; + +class STLSurface : public Faceted_Surface_From_File { +public: + STLSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale); + + virtual ~STLSurface() {} + virtual BoundingBox get_bounding_box() override; + +private: + virtual void read_file(const std::vector & proc_bboxes) override; + void read_header(); + unsigned read_num_binary_facets(); + unsigned read_ascii_facets(const unsigned max_batch_size); + unsigned read_binary_facets(const unsigned maxBatchSize, unsigned & numRemainingInFile); + BoundingBox get_ascii_facet_bounding_box(); + BoundingBox get_binary_facet_bounding_box(const unsigned numFacets); + +private: + bool my_is_ascii; + Parallel_Facet_File_Reader my_reader; + int my_dist_sign; + Vector3d my_scale; +}; + +class FACSurface : public Faceted_Surface_From_File { +public: + FACSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale); + + virtual ~FACSurface() {} + virtual BoundingBox get_bounding_box() override; + +private: + virtual void read_file(const std::vector & proc_bboxes) override; + void read_points(std::vector & points); + void read_facets(const int batch_size, const int num_facets, const std::vector & points); + +private: + Parallel_Facet_File_Reader my_reader; + int my_dist_sign; + Vector3d my_scale; +}; + +class PLYSurface : public Faceted_Surface_From_File { +public: + PLYSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale); + + virtual ~PLYSurface() {} + virtual BoundingBox get_bounding_box() override; + +private: + Parallel_Facet_File_Reader my_reader; + virtual void read_file(const std::vector & proc_bboxes) override; + void read_header(int & num_points, int & num_facets); + void read_points(const int num_points, std::vector & points); + void read_facets(const int batch_size, const std::vector & points); + +private: + int my_dist_sign; + Vector3d my_scale; +}; + +class EXOSurface : public Faceted_Surface_From_File { +public: + EXOSurface(const std::string & surface_name, + const stk::diag::Timer &parent_timer, + const std::string & filename, + const int sign, + const Vector3d & scale); + + virtual ~EXOSurface() {} + virtual BoundingBox get_bounding_box() override; + +private: + virtual void read_file(const std::vector & proc_bboxes) override; + +private: + std::string my_filename; + int my_dist_sign; + Vector3d my_scale; +}; + +} // namespace krino + +#endif // Akri_MeshSurface_h diff --git a/packages/krino/krino/krino_lib/Akri_MortonIndex.hpp b/packages/krino/krino/krino_lib/Akri_MortonIndex.hpp new file mode 100644 index 000000000000..4d811bbf1218 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_MortonIndex.hpp @@ -0,0 +1,58 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MortonIndex_h +#define Akri_MortonIndex_h + +#include +#include + +namespace krino { + +constexpr int MAX_VALID_MORTON3D_INDEX = 0x1fffff; + +uint64_t morton3d_encode_index(const int i) +{ + ThrowAssertMsg(i >= 0, "Index must be >= 0 to encode: " << i); + ThrowAssertMsg(i <= MAX_VALID_MORTON3D_INDEX, "Index must be <= " << MAX_VALID_MORTON3D_INDEX << " to encode: " << i); + uint64_t m = i & 0x1fffff; + m = (m | m << 32) & 0x001f00000000ffff; + m = (m | m << 16) & 0x001f0000ff0000ff; + m = (m | m << 8) & 0x100f00f00f00f00f; + m = (m | m << 4) & 0x10c30c30c30c30c3; + m = (m | m << 2) & 0x1249249249249249; + return m; +} + +int morton3d_decode_index(uint64_t m) +{ + m &= 0x1249249249249249; + m = (m ^ (m >> 2)) & 0x10c30c30c30c30c3; + m = (m ^ (m >> 4)) & 0x100f00f00f00f00f; + m = (m ^ (m >> 8)) & 0x001f0000ff0000ff; + m = (m ^ (m >> 16)) & 0x001f00000000ffff; + m = (m ^ (m >> 32)) & 0x1fffff; + return static_cast(m); +} + +uint64_t morton3d_encode_indices(const std::array & indices) +{ + return morton3d_encode_index(indices[2]) * 4 + morton3d_encode_index(indices[1]) * 2 + morton3d_encode_index(indices[0]); +} + +std::array morton3d_decode_indices(const uint64_t m) +{ + const int ix = morton3d_decode_index(m); + const int iy = morton3d_decode_index(m >> 1); + const int iz = morton3d_decode_index(m >> 2); + return {{ix,iy,iz}}; +} + +} // namespace krino + +#endif // Akri_MortonIndex_h diff --git a/packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.cpp b/packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.cpp new file mode 100644 index 000000000000..6f1f5d091d5c --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.cpp @@ -0,0 +1,121 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +namespace krino { + +static void pack_node_captured_domains_for_sharing_or_ghosting_procs(const stk::mesh::BulkData & mesh, + const stk::mesh::Entity node, + const std::vector & nodeCapturedDomains, + std::vector procsThatNeedToKnowAboutNode, + stk::CommSparse &commSparse) +{ + fill_procs_owning_or_sharing_or_ghosting_node(mesh, node, procsThatNeedToKnowAboutNode); + for (int procId : procsThatNeedToKnowAboutNode) + { + if (procId != mesh.parallel_rank()) + { + commSparse.send_buffer(procId).pack(mesh.identifier(node)); + commSparse.send_buffer(procId).pack(nodeCapturedDomains.size()); + for (int domain : nodeCapturedDomains) + commSparse.send_buffer(procId).pack(domain); + } + } +} + +static +void pack_all_node_captured_domains_for_sharing_or_ghosting_procs(const stk::mesh::BulkData & mesh, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + stk::CommSparse &commSparse) +{ + std::vector scratchVec; + stk::pack_and_communicate(commSparse,[&]() + { + for (auto && entry : nodesToCapturedDomains) + { + stk::mesh::Entity node = entry.first; + if (mesh.parallel_owner_rank(node) == mesh.parallel_rank()) + { + const auto & nodeCapturedDomains = entry.second; + pack_node_captured_domains_for_sharing_or_ghosting_procs(mesh, node, nodeCapturedDomains, scratchVec, commSparse); + } + } + }); +} + +static +void pack_node_captured_domains_of_given_nodes_for_sharing_or_ghosting_procs(const stk::mesh::BulkData & mesh, + const std::vector & nodes, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + stk::CommSparse &commSparse) +{ + std::vector scratchVec; + stk::pack_and_communicate(commSparse,[&]() + { + for (auto node : nodes) + { + if (mesh.parallel_owner_rank(node) == mesh.parallel_rank()) + { + const auto nodeCapturedDomains = nodesToCapturedDomains.at(node); + pack_node_captured_domains_for_sharing_or_ghosting_procs(mesh, node, nodeCapturedDomains, scratchVec, commSparse); + } + } + }); +} + +static +void unpack_snap_node_domains(const stk::mesh::BulkData & mesh, + NodeToCapturedDomainsMap & nodesToSnappedDomains, + stk::CommSparse &commSparse) +{ + std::vector nodeSnappedDomains; + + stk::unpack_communications(commSparse, [&](int procId) + { + stk::CommBuffer & buffer = commSparse.recv_buffer(procId); + + while ( buffer.remaining() ) + { + stk::mesh::EntityId nodeId; + commSparse.recv_buffer(procId).unpack(nodeId); + size_t numNodes; + commSparse.recv_buffer(procId).unpack(numNodes); + nodeSnappedDomains.resize(numNodes); + for (auto && domain : nodeSnappedDomains) + commSparse.recv_buffer(procId).unpack(domain); + + stk::mesh::Entity snapNode = mesh.get_entity(stk::topology::NODE_RANK, nodeId); + nodesToSnappedDomains[snapNode] = nodeSnappedDomains; + } + }); +} + +void communicate_node_captured_domains_for_given_nodes(const stk::mesh::BulkData & mesh, + const std::vector & nodes, + NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + stk::CommSparse commSparse(mesh.parallel()); + pack_node_captured_domains_of_given_nodes_for_sharing_or_ghosting_procs(mesh, nodes, nodesToCapturedDomains, commSparse); + unpack_snap_node_domains(mesh, nodesToCapturedDomains, commSparse); +} + +void communicate_node_captured_domains_for_all_nodes(const stk::mesh::BulkData & mesh, + NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + stk::CommSparse commSparse(mesh.parallel()); + pack_all_node_captured_domains_for_sharing_or_ghosting_procs(mesh, nodesToCapturedDomains, commSparse); + unpack_snap_node_domains(mesh, nodesToCapturedDomains, commSparse); +} + +} + + diff --git a/packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.hpp b/packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.hpp new file mode 100644 index 000000000000..7245a6e9398c --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_NodeToCapturedDomains.hpp @@ -0,0 +1,31 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_NODETOCAPTUREDDOMAINS_H_ +#define KRINO_INCLUDE_AKRI_NODETOCAPTUREDDOMAINS_H_ +#include +#include +#include +#include + +namespace krino { + +typedef std::map> NodeToCapturedDomainsMap; + +void communicate_node_captured_domains_for_given_nodes(const stk::mesh::BulkData & mesh, + const std::vector & nodes, + NodeToCapturedDomainsMap & nodesToCapturedDomains); + +void communicate_node_captured_domains_for_all_nodes(const stk::mesh::BulkData & mesh, + NodeToCapturedDomainsMap & nodesToCapturedDomains); + +typedef std::map ElementToDomainMap; + +} + +#endif /* KRINO_INCLUDE_AKRI_NODETOCAPTUREDDOMAINS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_OrderedIdPair.hpp b/packages/krino/krino/krino_lib/Akri_OrderedIdPair.hpp new file mode 100644 index 000000000000..9f89f71a210a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_OrderedIdPair.hpp @@ -0,0 +1,50 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_ORDEREDIDPAIR_H_ +#define AKRI_ORDEREDIDPAIR_H_ + +#include +#include + +namespace krino { + +class OrderedIdPair { +public: + OrderedIdPair(stk::mesh::EntityId id0, stk::mesh::EntityId id1) + : my_ids(id0 < id1 ? id0 : id1, id0 < id1 ? id1 : id0) {} + + stk::mesh::EntityId first() const { return my_ids.first; } + stk::mesh::EntityId second() const { return my_ids.second; } + +private: + std::pair my_ids; +}; + +inline bool operator<(const OrderedIdPair lhs, const OrderedIdPair rhs) +{ + if(lhs.first() < rhs.first()) return true; + if(rhs.first() < lhs.first()) return false; + if(lhs.second() < rhs.second()) return true; + return false; +} + +inline bool operator==(const OrderedIdPair lhs, const OrderedIdPair rhs) +{ + return (lhs.first() == rhs.first()) && (lhs.second() == rhs.second()); +} + +inline bool operator!=(const OrderedIdPair lhs, const OrderedIdPair rhs) +{ + return !(lhs == rhs); +} + +} + + +#endif /* AKRI_ORDEREDIDPAIR_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_ParallelCommHelpers.hpp b/packages/krino/krino/krino_lib/Akri_ParallelCommHelpers.hpp new file mode 100644 index 000000000000..273e4ab441b7 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ParallelCommHelpers.hpp @@ -0,0 +1,77 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_PARALLELCOMMHELPERS_H_ +#define KRINO_INCLUDE_AKRI_PARALLELCOMMHELPERS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +// PACK_OP shoudl have an operator()(stk::CommBuffer & buffer, const stk::mesh::EntityKey & entity_key) +// UNPACK_OP should have an operator()(CommBuffer & buffer) +template +void communicate_with_shared_procs(const stk::mesh::BulkData& mesh, + const std::vector & local_entities, + PACK_OP pack_op, + UNPACK_OP unpack_op) +{ + // The input vector, local_entities, must only contain entities that are locally_owned or globally_shared (or both). + + stk::CommSparse comm_spec(mesh.parallel()); + std::vector sharing_procs; + + for (int phase=0;phase<2;++phase) + { + for (auto&& local_entity : local_entities) + { + ThrowAssert(mesh.bucket(local_entity).owned() || mesh.bucket(local_entity).shared()); + mesh.comm_shared_procs(local_entity, sharing_procs); + for (auto&& sharing_proc : sharing_procs) + { + if (sharing_proc != mesh.parallel_rank()) + { + pack_op(comm_spec.send_buffer(sharing_proc), mesh.entity_key(local_entity)); + } + } + } + + if ( phase == 0 ) + { + comm_spec.allocate_buffers(); + } + else + { + comm_spec.communicate(); + } + } + + for(int i = 0; i < mesh.parallel_size(); ++i) + { + if(i != mesh.parallel_rank()) + { + auto & recv_buffer = comm_spec.recv_buffer(i); + while(recv_buffer.remaining()) + { + unpack_op(recv_buffer); + } + } + } +} + +} + +#endif /* KRINO_INCLUDE_AKRI_PARALLELCOMMHELPERS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.cpp b/packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.cpp new file mode 100644 index 000000000000..4881453ec8a3 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.cpp @@ -0,0 +1,30 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +namespace krino +{ + +ParallelErrorMessage::ParallelErrorMessage(const stk::Parallel & c) : comm(c) {} + +std::pair ParallelErrorMessage::gather_message() const +{ + const std::string & local_str = local_message.str(); + + std::ostringstream result; + stk::all_write_string(comm.parallel(), result, local_str); + + int err = result.str().size(); + int global_err; + stk::all_reduce_sum(comm.parallel(), &err, &global_err, 1); + + return make_pair(static_cast(global_err), result.str()); +} +} diff --git a/packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.hpp b/packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.hpp new file mode 100644 index 000000000000..b2b76e3a4ee0 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ParallelErrorMessage.hpp @@ -0,0 +1,43 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_PARALLELERRORMESSAGE_H_ +#define KRINO_INCLUDE_AKRI_PARALLELERRORMESSAGE_H_ + +#include +#include +#include + +namespace krino +{ + +class ParallelErrorMessage +{ +public: + ParallelErrorMessage(const stk::Parallel & c); + + template ParallelErrorMessage & operator<<(const T & t) + { + local_message << t; + return *this; + } + + // This will gather the messages from all ranks to rank 0 where it can be output. + // and additionally return a parallel-consistent bool for whether or not any errors + // were logged. + std::pair gather_message() const; + + bool have_local_error() const { return !local_message.str().empty(); } + +private: + const stk::Parallel comm; + std::ostringstream local_message; +}; +} + +#endif /* KRINO_INCLUDE_AKRI_PARALLELERRORMESSAGE_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_ParentsToChildMapper.cpp b/packages/krino/krino/krino_lib/Akri_ParentsToChildMapper.cpp new file mode 100644 index 000000000000..6f217818262f --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ParentsToChildMapper.cpp @@ -0,0 +1,188 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +static bool determine_if_mesh_has_higher_order_midside_nodes_with_level_set_locally(const stk::mesh::BulkData & mesh, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + // Assume we can check master element on one bucket and get the right answer + + for ( auto && bucket_ptr : mesh.get_buckets(stk::topology::ELEMENT_RANK, phaseSupport.get_all_decomposed_blocks_selector()) ) + { + if (bucket_ptr->topology() != bucket_ptr->topology().base()) + { + for (int ls_index = 0; ls_index < cdfemSupport.num_ls_fields(); ++ls_index) + { + stk::topology fieldTopology = MasterElementDeterminer::get_field_topology(*bucket_ptr, cdfemSupport.ls_field(ls_index).isovar); + if (fieldTopology != fieldTopology.base()) + return true; + } + } + } + return false; +} + +void fill_edge_nodes( + const stk::mesh::BulkData & mesh, + const stk::mesh::Entity node0, + const stk::mesh::Entity node1, + const ParentsToChildMapper & parentToChildMapper, + std::vector & edgeNodes) +{ + stk::mesh::Entity child = parentToChildMapper.get_child(mesh, node0, node1); + + if (mesh.is_valid(child)) + { + ThrowRequire(child != node0); + ThrowRequire(child != node1); + fill_edge_nodes(mesh, node0,child, parentToChildMapper, edgeNodes); + fill_edge_nodes(mesh, child,node1, parentToChildMapper, edgeNodes); + } + else + { + if (edgeNodes.empty()) + { + edgeNodes.push_back(node0); + } + edgeNodes.push_back(node1); + } +} + +static void fill_edge_nodes_and_positions( + const double pos0, stk::mesh::Entity node0, + const double pos1, stk::mesh::Entity node1, + std::vector & edgeNodes, + std::vector & edgeNodePositions) +{ + if (edgeNodePositions.empty()) + { + edgeNodePositions.push_back(pos0); + edgeNodes.push_back(node0); + } + edgeNodePositions.push_back(pos1); + edgeNodes.push_back(node1); +} + +static void fill_edge_nodes_and_positions( + const stk::mesh::BulkData & mesh, + const double pos0, stk::mesh::Entity node0, + const double pos1, stk::mesh::Entity node1, + const ParentsToChildMapper & parent_child_mapper, + std::vector & edgeNodes, + std::vector & edgeNodePositions) +{ + stk::mesh::Entity child = parent_child_mapper.get_child(mesh, node0, node1); + + if (mesh.is_valid(child)) + { + ThrowRequire(child != node0); + ThrowRequire(child != node1); + const double child_rel_pos = compute_child_position(mesh, child, node0, node1); + const double child_abs_pos = pos0 * (1.-child_rel_pos) + pos1 * child_rel_pos; + fill_edge_nodes_and_positions(mesh,pos0,node0,child_abs_pos,child, parent_child_mapper, edgeNodes, edgeNodePositions); + fill_edge_nodes_and_positions(mesh,child_abs_pos,child,pos1,node1, parent_child_mapper, edgeNodes, edgeNodePositions); + } + else + { + fill_edge_nodes_and_positions(pos0, node0, pos1, node1, edgeNodes, edgeNodePositions); + } +} + +void fill_edge_nodes_and_positions(const stk::mesh::BulkData & mesh, + stk::mesh::Entity node0, stk::mesh::Entity node1, + const ParentsToChildMapper & parent_child_mapper, + std::vector & edgeNodes, + std::vector & edgeNodePositions) +{ + edgeNodes.clear(); + edgeNodePositions.clear(); + fill_edge_nodes_and_positions(mesh, 0.0, node0, 1.0, node1, parent_child_mapper, edgeNodes, edgeNodePositions); +} + +void fill_linear_edge_nodes_and_positions(stk::mesh::Entity node0, stk::mesh::Entity node1, + std::vector & edgeNodes, + std::vector & edgeNodePositions) +{ + edgeNodes.clear(); + edgeNodePositions.clear(); + fill_edge_nodes_and_positions(0.0, node0, 1.0, node1, edgeNodes, edgeNodePositions); +} + + +void ParentsToChildMapper::build_map(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & activePart, + const CDFEM_Support & cdfemSupport, + const Phase_Support & phaseSupport) +{ + const bool addHigherOrderMidsideNodes = stk::is_true_on_any_proc(mesh.parallel(), determine_if_mesh_has_higher_order_midside_nodes_with_level_set_locally(mesh, cdfemSupport, phaseSupport)); + + stk::mesh::Selector elementSelectorForParentChildMapper = activePart & mesh.mesh_meta_data().locally_owned_part(); + + build_map(mesh, cdfemSupport.get_parent_node_ids_field(), elementSelectorForParentChildMapper, addHigherOrderMidsideNodes); +} + +void ParentsToChildMapper::build_map(const stk::mesh::BulkData & mesh, const FieldRef & parent_ids_field, const stk::mesh::Selector & elementSelector, bool add_higher_order_midside_nodes) +{ + my_parents_to_child_map.clear(); + + if (parent_ids_field.valid()) + { + const auto & field_selector = stk::mesh::selectField(parent_ids_field); + + ThrowRequire(!mesh.in_modifiable_state()); + + const auto & buckets = mesh.get_buckets(stk::topology::NODE_RANK, field_selector); + for(auto && b_ptr : buckets) + { + for(unsigned i=0; i < b_ptr->size(); ++i) + { + const stk::mesh::Entity child_node = (*b_ptr)[i]; + const auto parent_ids = get_edge_node_parent_ids(mesh, parent_ids_field, child_node); + my_parents_to_child_map[{parent_ids[0], parent_ids[1]}] = mesh.identifier(child_node); + } + } + } + + if (add_higher_order_midside_nodes) + { + // Add higher order edge "children" + for(auto && b_ptr : mesh.get_buckets(stk::topology::ELEMENT_RANK, elementSelector)) + { + const stk::topology elem_topology = b_ptr->topology(); + if (elem_topology == stk::topology::TRIANGLE_6_2D || elem_topology == stk::topology::TETRAHEDRON_10) + { + for (auto elem : *b_ptr) + { + const stk::mesh::Entity * elem_nodes = mesh.begin_nodes(elem); + for (unsigned edge_i=0; edge_i + +#include +#include +#include + +namespace krino { class FieldRef; } + +namespace krino { + +class CDFEM_Support; +class Phase_Support; + +class ParentsToChildMapper +{ +public: + ParentsToChildMapper() = default; + + void build_map(const stk::mesh::BulkData & mesh, const stk::mesh::Part & activePart, const CDFEM_Support & cdfemSupport, const Phase_Support & phaseSupport); + void build_map(const stk::mesh::BulkData & mesh, const FieldRef & parent_ids_field, const stk::mesh::Selector & elementSelector, bool add_higher_order_midside_nodes); + + bool get_child_id(const stk::mesh::EntityId parent0, + const stk::mesh::EntityId parent1, + stk::mesh::EntityId & child) const + { + const auto it = my_parents_to_child_map.find({parent0, parent1}); + if(it == my_parents_to_child_map.end()) return false; + child = it->second; + return true; + } + + stk::mesh::Entity get_child(const stk::mesh::BulkData & mesh, const stk::mesh::Entity parent0, + const stk::mesh::Entity parent1) const + { + const auto it = + my_parents_to_child_map.find({mesh.identifier(parent0), mesh.identifier(parent1)}); + if(it == my_parents_to_child_map.end()) return stk::mesh::Entity(); + return mesh.get_entity(stk::topology::NODE_RANK, it->second); + } + +private: + typedef OrderedIdPair ParentsToChildKey; + typedef std::map ParentsToChildMap; + ParentsToChildMap my_parents_to_child_map; +}; + +void fill_edge_nodes( + const stk::mesh::BulkData & mesh, + const stk::mesh::Entity node0, + const stk::mesh::Entity node1, + const ParentsToChildMapper & parentToChildMapper, + std::vector & edgeNodes); + +void fill_edge_nodes_and_positions(const stk::mesh::BulkData & mesh, + stk::mesh::Entity node0, stk::mesh::Entity node1, + const ParentsToChildMapper & parent_child_mapper, + std::vector & edgeNodes, + std::vector & edgeNodePositions); + +void fill_linear_edge_nodes_and_positions(stk::mesh::Entity node0, + stk::mesh::Entity node1, + std::vector & edgeNodes, + std::vector & edgeNodePositions); + +} // namespace krino + +#endif /* AKRI_PARENTSTOCHILDMAPPER_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_PhaseTag.cpp b/packages/krino/krino/krino_lib/Akri_PhaseTag.cpp new file mode 100644 index 000000000000..5f029c295be4 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_PhaseTag.cpp @@ -0,0 +1,112 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +namespace krino{ + +std::map LS_SideTag::the_composite_ls_map; + +void LS_SideTag::declare_composite(const LevelSet_Identifier constituent, const LevelSet_Identifier composite) +{ + LS_SideTag constituent_tag(constituent,0); + LS_SideTag composite_tag(composite,0); + + // verbose version of operator[] to avoid needing default constructor for LevelSet_Interface + auto existing_entry = the_composite_ls_map.find(constituent_tag.my_ls_identifier); + if (existing_entry != the_composite_ls_map.end()) + { + existing_entry->second = composite_tag.my_ls_identifier; + } + else + { + the_composite_ls_map.insert(std::make_pair(constituent_tag.my_ls_identifier, composite_tag.my_ls_identifier)); + } + +} + +std::ostream& +operator<<(std::ostream & os, const LS_SideTag & ls_side) +{ + /* %TRACE[NONE]% */ /* %TRACE% */ + os << "(" << ls_side.my_ls_identifier << "," << ls_side.my_ls_sign << ")"; + return os; +} + +std::ostream& +operator<<(std::ostream & os, const PhaseTag & phase) +{ + /* %TRACE[NONE]% */ /* %TRACE% */ + os << " with ls sides: "; + const std::set & ls_sides = phase.ls_sides(); + for (std::set::const_iterator it = ls_sides.begin(); it != ls_sides.end(); ++it) + { + os << *it << " "; + } + return os; +} + +bool PhaseTag::contain(const PhaseTag & phase) const +{ + // This phase is considered to contain "phase" if every phase in "phase" is contained. + // Example: "this": (LS1,-1), (LS2,-1) + // "phase": (LS1,-1) + // Then "this" contains "phase", but "phase" doesn't contain "this" + for (std::set::const_iterator it=phase.my_ls_sides.begin(); it != phase.my_ls_sides.end(); ++it) + { + if (!contain(*it)) return false; + } + return true; +} + +bool +PhaseTag::is_nonconformal() const +{ + for (std::set::const_iterator it=my_ls_sides.begin(); it != my_ls_sides.end(); ++it) + { + const LS_SideTag & ls_side = *it; + + if (ls_side.is_interface()) + { + return true; + } + } + return false; +} + +bool +PhaseTag::operator== (const PhaseTag & rhs) const +{ + if (my_ls_sides.size() != rhs.my_ls_sides.size()) return false; + for (std::set::const_iterator it=my_ls_sides.begin(); it != my_ls_sides.end(); ++it) + { + const LS_SideTag & ls_side = *it; + + if (!rhs.contain(ls_side)) return false; + } + return true; +} + +PhaseTag & +PhaseTag::operator &= ( const PhaseTag & rhs ) +{ + for (std::set::const_iterator it=my_ls_sides.begin(); it != my_ls_sides.end();) + { + if (!rhs.contain(*it)) + { + my_ls_sides.erase(it++); + } + else + { + ++it; + } + } + return *this; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_PhaseTag.hpp b/packages/krino/krino/krino_lib/Akri_PhaseTag.hpp new file mode 100644 index 000000000000..94fa63b58f18 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_PhaseTag.hpp @@ -0,0 +1,171 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_PhaseTag_h +#define Akri_PhaseTag_h + +#include +#include + +#include +#include +#include + +namespace krino { + +class LS_SideTag { +public: + LS_SideTag(const LevelSet_Identifier ls_identifier, const int ls_sign) : my_ls_identifier(ls_identifier), my_ls_sign(ls_sign) {} + LS_SideTag(const LS_SideTag & orig, const int ls_sign) : my_ls_identifier(orig.my_ls_identifier), my_ls_sign(ls_sign) {} + ~LS_SideTag() {} +public: + static void declare_composite(const LevelSet_Identifier constituent, const LevelSet_Identifier composite); + LS_SideTag opposite_side() const {return LS_SideTag(my_ls_identifier,-1*my_ls_sign);} + LS_SideTag composite() const + { + auto it = the_composite_ls_map.find(my_ls_identifier); + if (it != the_composite_ls_map.end()) + { + return LS_SideTag(it->second, my_ls_sign); + } + else + { + return *this; + } + } + bool is_interface() const {return (0 == my_ls_sign);} + int get_ls_sign() const {return my_ls_sign;} + LevelSet_Identifier get_ls_identifier() const {return my_ls_identifier;} + bool operator < ( const LS_SideTag & RHS ) const { return my_ls_identifier < RHS.my_ls_identifier || (my_ls_identifier == RHS.my_ls_identifier && my_ls_sign < RHS.my_ls_sign); } + bool operator == ( const LS_SideTag & RHS ) const { return (my_ls_identifier == RHS.my_ls_identifier && my_ls_sign == RHS.my_ls_sign); } + bool operator != ( const LS_SideTag & RHS ) const { return (my_ls_identifier != RHS.my_ls_identifier || my_ls_sign != RHS.my_ls_sign); } + friend std::ostream& operator<<(std::ostream & os, const LS_SideTag & phase); +protected: + const LevelSet_Identifier my_ls_identifier; + const int my_ls_sign; + static std::map the_composite_ls_map; +}; + +class PhaseTag { +public: + PhaseTag() {} + ~PhaseTag() {} +public: + bool empty() const { return my_ls_sides.empty(); } + void clear() { my_ls_sides.clear(); } + const std::set & ls_sides() const { return my_ls_sides; } + void add(const LevelSet_Identifier ls_identifier, const int ls_sign) { add(LS_SideTag(ls_identifier, ls_sign)); } + void add(const PhaseTag & phase) { for (auto && ls_side : phase.my_ls_sides) add(ls_side); } + void add_opposite_sides(const PhaseTag & phase) { for (auto && ls_side : phase.my_ls_sides) add(ls_side.opposite_side()); } + void add(const LS_SideTag & ls_side) + { + const LS_SideTag ls_side_composite = ls_side.composite(); + my_ls_sides.insert(ls_side_composite); + if (ls_side_composite != ls_side) + { + // Apply priority rule. -1 (inside) beats +1 (outside) + if (my_ls_sides.count(LS_SideTag(ls_side_composite.get_ls_identifier(),-1))) + { + my_ls_sides.erase(LS_SideTag(ls_side_composite.get_ls_identifier(),+1)); + } + } + } + void remove(const LevelSet_Identifier ls_identifier, const int ls_sign){ remove(LS_SideTag(ls_identifier,ls_sign)); } + void remove(const LS_SideTag & ls_side) { my_ls_sides.erase(ls_side.composite()); } + + bool contain(const PhaseTag & phase) const; + bool contain(const LevelSet_Identifier ls_identifier, const int ls_sign) const { return contain(LS_SideTag(ls_identifier,ls_sign)); } + bool contain(const LS_SideTag & ls_side) const { return my_ls_sides.count(ls_side.composite()); } + + bool is_nonconformal() const; + bool operator!= (const PhaseTag & rhs) const { return !operator==(rhs); } + bool operator== (const PhaseTag & rhs) const; + bool operator < ( const PhaseTag & RHS ) const { return (my_ls_sides < RHS.my_ls_sides); } + friend std::ostream& operator<<(std::ostream & os, const PhaseTag & phase); + /** \brief Intersection: this = this INTERSECT ( expression ) */ + PhaseTag & operator &= ( const PhaseTag & rhs); + + void get_data(std::vector & phase_data) const + { + for (std::set::const_iterator side_it = ls_sides().begin(); side_it != ls_sides().end(); ++side_it) + { + phase_data.push_back(side_it->get_ls_identifier().get()); + phase_data.push_back(side_it->get_ls_sign()); + } + } + void set_from_data(const std::vector & phase_data) + { + clear(); + ThrowAssert(phase_data.size() % 2 == 0); + const unsigned num_phases = phase_data.size() / 2; + for (unsigned phase_index=0; phase_index my_ls_sides; +}; + +class NamedPhase +{ +public: + NamedPhase(std::string in_name) : my_name(in_name) {} + NamedPhase(std::string in_name, const PhaseTag & in_tag) : my_name(in_name), my_tag(in_tag) {} + const std::string & name() const { return my_name; } + const PhaseTag & tag() const { return my_tag; } + PhaseTag & tag() { return my_tag; } +protected: + std::string my_name; + PhaseTag my_tag; +}; +typedef std::vector< NamedPhase > PhaseVec; + +class PhasePartTag { +public: + // For non-interface phase parts + PhasePartTag(const unsigned & conformal_part_ordinal, const unsigned & nonconformal_part_ordinal, const unsigned & original_part_ordinal, const PhaseTag & vol_phase) + : my_conformal_part_ordinal(conformal_part_ordinal), + my_nonconformal_part_ordinal(nonconformal_part_ordinal), + my_original_part_ordinal(original_part_ordinal), + my_touching_vol_phase(vol_phase) { ThrowRequire(!vol_phase.empty()); } + // For interface phase parts defined as intersection of touching_vol_phase and opposite_vol_phase + PhasePartTag(const unsigned & conformal_part_ordinal, const unsigned & nonconformal_part_ordinal, const unsigned & original_part_ordinal, const PhaseTag & touching_vol_phase, const PhaseTag & opposite_vol_phase) + : my_conformal_part_ordinal(conformal_part_ordinal), + my_nonconformal_part_ordinal(nonconformal_part_ordinal), + my_original_part_ordinal(original_part_ordinal), + my_touching_vol_phase(touching_vol_phase), + my_opposite_vol_phase(opposite_vol_phase) { ThrowRequire(!touching_vol_phase.empty() && !opposite_vol_phase.empty()); } + ~PhasePartTag() {} +public: + unsigned get_conformal_part_ordinal() const { return my_conformal_part_ordinal; } + unsigned get_nonconformal_part_ordinal() const { return my_nonconformal_part_ordinal; } + unsigned get_original_part_ordinal() const { return my_original_part_ordinal; } + bool is_interface() const { return !my_opposite_vol_phase.empty(); } + const PhaseTag & get_phase() const { ThrowRequire(!is_interface()); return my_touching_vol_phase; } + const PhaseTag & get_touching_phase() const { ThrowRequire(is_interface()); return my_touching_vol_phase; } + const PhaseTag & get_opposite_phase() const { ThrowRequire(is_interface()); return my_opposite_vol_phase; } + bool operator<(PhasePartTag rhs) const + { + // There must be a 1-1 mapping between conformal parts and PhasePartTags + // therefore the conformal part ordinal must be a unique identifier for the PhasePartTag and + // we can sort on just it. + return my_conformal_part_ordinal < rhs.my_conformal_part_ordinal; + } +protected: + unsigned my_conformal_part_ordinal; + unsigned my_nonconformal_part_ordinal; + unsigned my_original_part_ordinal; + PhaseTag my_touching_vol_phase; + PhaseTag my_opposite_vol_phase; +}; +typedef std::set< PhasePartTag > PhasePartSet; + +} // namespace krino + +#endif // Akri_PhaseTag_h diff --git a/packages/krino/krino/krino_lib/Akri_Phase_Support.cpp b/packages/krino/krino/krino_lib/Akri_Phase_Support.cpp new file mode 100644 index 000000000000..e611f2649c5e --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Phase_Support.cpp @@ -0,0 +1,961 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace krino{ + +bool Phase_Support::oneLevelSetPerPhase = false; +std::map Phase_Support::the_mesh_phases; +std::map Phase_Support::the_mesh_block_phases_by_name; + +void Phase_Support::check_phase_parts() const +{ + const LevelSetManager & region_ls = LevelSetManager::get(my_meta); + bool error = false; + for (auto&& phase_part : my_phase_parts) + { + const PhaseTag & part_phase = (phase_part.is_interface()) ? phase_part.get_touching_phase() : phase_part.get_phase(); + const std::set & phase_ls_sides = part_phase.ls_sides(); + for (std::set::const_iterator side_it = phase_ls_sides.begin(); side_it != phase_ls_sides.end(); ++side_it) + { + const std::string phase_ls_name = LevelSet::get_name(side_it->get_ls_identifier()); + if (!region_ls.has_levelSet(phase_ls_name)) + { + error = true; + stk::mesh::Part & conformal_part = my_meta.get_part(phase_part.get_conformal_part_ordinal()); + krinolog << "Error: Phase \"" << conformal_part.name() << "\" uses level set \"" << phase_ls_name << "\" but no such level set exists." << stk::diag::dendl; + } + } + } + ThrowErrorMsgIf(error, "Error: Phases are not defined correctly."); +} + +Phase_Support::Phase_Support(stk::mesh::MetaData & meta) +: my_meta(meta), + my_aux_meta(AuxMetaData::get(meta)), + nonconformalLsMapsAreFilled_(false) {} + +Phase_Support & +Phase_Support::get(stk::mesh::MetaData & meta) +{ + Phase_Support * support = const_cast(meta.get_attribute()); + if (nullptr == support) + { + support = new Phase_Support(meta); + ThrowAssert(nullptr != support); + meta.declare_attribute_with_delete(support); + } + return *support; +} + +Phase_Support & +Phase_Support::get(const stk::mesh::MetaData & meta) +{ + Phase_Support * support = const_cast(meta.get_attribute()); + ThrowRequireMsg(nullptr != support, "No Phase_Support found for MetaData."); + return *support; +} + +bool +Phase_Support::exists_and_has_phases_defined(const stk::mesh::MetaData & meta) +{ + Phase_Support * support = const_cast(meta.get_attribute()); + return support != nullptr && support->phases_defined(); +} + +stk::mesh::Selector Phase_Support::get_all_conformal_surfaces_selector() const +{ + std::vector surfaceParts; + for (auto && part : get_conformal_parts()) + if(is_interface(part)) + surfaceParts.push_back(part); + return selectUnion(surfaceParts); +} + +void +Phase_Support::addPhasePart(stk::mesh::Part & io_part, PhasePartSet & phase_parts, const NamedPhase & ls_phase) +{ + const std::string & phase_name = ls_phase.name(); + std::string phase_part_name = io_part.name(); + if(phase_name != "") // The alive phase for death problems just uses the io part as the conformal part + { + phase_part_name += "_" + phase_name; + } + + stk::mesh::Part & conformal_io_part = my_aux_meta.declare_io_part(phase_part_name, io_part.primary_entity_rank()); + ThrowAssert(phase_name != "" || conformal_io_part.mesh_meta_data_ordinal() == io_part.mesh_meta_data_ordinal()); + std::string topology_name = "INVALID_TOPOLOGY"; + if (stk::topology::INVALID_TOPOLOGY != io_part.topology()) + { + stk::mesh::set_topology(conformal_io_part, io_part.topology()); + topology_name = conformal_io_part.topology().name(); + } + + const std::string nonconf_name = io_part.name() + "_nonconformal"; + if(krinolog.shouldPrint(LOG_PARTS)) + { + krinolog << "Created conformal IO part " << conformal_io_part.name() << " with topology " << topology_name << stk::diag::dendl; + krinolog << " Adding phase part: conformal_part_name=" << conformal_io_part.name() + << ", nonconformal_part_name=" << nonconf_name + << ", original_mesh_part_name=" << io_part.name(); + krinolog << " " << ls_phase.tag(); + krinolog << stk::diag::dendl; + } + + + stk::mesh::Part & nonconf_part = my_aux_meta.get_part(nonconf_name); + phase_parts.insert(PhasePartTag(conformal_io_part.mesh_meta_data_ordinal(), nonconf_part.mesh_meta_data_ordinal(), io_part.mesh_meta_data_ordinal(), ls_phase.tag())); + + all_decomposed_blocks_selector |= conformal_io_part; + part_is_conformal_map[&conformal_io_part] = true; + part_to_nonconformal_part_map[&conformal_io_part] = &nonconf_part; + part_to_phase_map[&conformal_io_part] = ls_phase.tag(); + PhaseTagToPartMap & phase_to_conformal_map = nonconformal_to_phase_conformal_map[&nonconf_part]; + phase_to_conformal_map[ls_phase.tag()] = &conformal_io_part; +} + +void +Phase_Support::create_nonconformal_parts(const PartSet & decomposed_ioparts) +{ + const std::string nonconformal_part_suffix = "_nonconformal"; + for(PartSet::const_iterator it = decomposed_ioparts.begin(); it != decomposed_ioparts.end(); ++it) + { + const stk::mesh::Part & iopart = my_aux_meta.get_part((*it)->name()); + const std::string nonconformal_part_name = iopart.name() + nonconformal_part_suffix; + + if(!my_aux_meta.has_part(nonconformal_part_name)) + { + const bool restartOnlyIOPart = true; + stk::mesh::Part & nonconformal_part = + (iopart.topology() == stk::topology::INVALID_TOPOLOGY) ? + my_aux_meta.declare_io_part(nonconformal_part_name, iopart.primary_entity_rank(), restartOnlyIOPart) : + my_aux_meta.declare_io_part_with_topology(nonconformal_part_name, iopart.topology(), restartOnlyIOPart); + if(krinolog.shouldPrint(LOG_PARTS)) krinolog << "Created nonconformal part " << nonconformal_part_name << stk::diag::dendl; + + all_decomposed_blocks_selector |= iopart; + all_decomposed_blocks_selector |= nonconformal_part; + part_to_nonconformal_part_map[&iopart] = &nonconformal_part; + part_is_nonconformal_map[&nonconformal_part] = true; + } + } +} + +stk::mesh::PartVector +Phase_Support::get_nonconformal_parts() const +{ + stk::mesh::PartVector result; + for(const auto & entry : part_is_nonconformal_map) + { + if(entry.second) result.push_back(const_cast(entry.first)); + } + return result; +} + +stk::mesh::PartVector +Phase_Support::get_conformal_parts() const +{ + stk::mesh::PartVector result; + for(const auto & entry : part_is_conformal_map) + { + if(entry.second) result.push_back(const_cast(entry.first)); + } + return result; +} + +std::vector +Phase_Support::get_level_set_phases(const PhaseVec & mesh_phases, const LevelSet & levelSet) +{ + // gather up the set of phases that depend on this levelSet + PhaseTag ls_neg_phase; + PhaseTag ls_pos_phase; + ls_neg_phase.add(levelSet.get_identifier(),-1); + ls_pos_phase.add(levelSet.get_identifier(),+1); + std::vector ls_phases; + for(unsigned phase_index=0; phase_index touching_surface_ordinals; + get_input_surfaces_touching_block(input_block_surface_info, block_ptr->mesh_meta_data_ordinal(), touching_surface_ordinals); + for (auto && surf_ordinal : touching_surface_ordinals) + { + stk::mesh::Part & surf_part = mesh_meta.get_part(surf_ordinal); + blocks_and_touching_sides.insert(&surf_part); + + // Add all subset parts to the set of parts + if (!(surf_part.subsets().empty())) + { + for (stk::mesh::PartVector::const_iterator subset = surf_part.subsets().begin(); subset != surf_part.subsets().end(); ++subset) + { + blocks_and_touching_sides.insert(*subset); + } + } + } + } + return blocks_and_touching_sides; +} + +void +Phase_Support::create_phase_parts(const PhaseVec& ls_phases, const PartSet& decomposed_ioparts) +{ + for (auto && ls_phase : ls_phases) + { + for (auto && io_part : decomposed_ioparts) + { + addPhasePart(*io_part, my_phase_parts, ls_phase); + } + } +} + +void +Phase_Support::subset_and_alias_surface_phase_parts(const PhaseVec& ls_phases, + const PartSet& decomposed_ioparts) +{ + std::set touching_block_ordinals; + + for (auto && io_part : decomposed_ioparts) + { + if (!(io_part->subsets().empty())) + { + for (auto && ls_phase_entry : ls_phases) + { + const PhaseTag & ls_phase = ls_phase_entry.tag(); + //FIXME: remove const_cast's + stk::mesh::Part * conformal_iopart = const_cast(find_conformal_io_part(*io_part, ls_phase)); + ThrowRequire(NULL != conformal_iopart); + + stk::mesh::Part * nonconformal_iopart = const_cast(find_nonconformal_part(*io_part)); + ThrowRequire(NULL != nonconformal_iopart); + + for (stk::mesh::PartVector::const_iterator subset = io_part->subsets().begin(); + subset != io_part->subsets().end(); ++subset) + { + stk::mesh::Part * io_part_subset = *subset; + ThrowRequire(NULL != io_part_subset); + + stk::mesh::Part * conformal_iopart_subset = const_cast(find_conformal_io_part(*io_part_subset, ls_phase)); + ThrowRequire(NULL != conformal_iopart_subset); + + if(krinolog.shouldPrint(LOG_PARTS)) krinolog << "Adding " << conformal_iopart_subset->name() << " as subset of " << conformal_iopart->name() << stk::diag::dendl; + my_meta.declare_part_subset(*conformal_iopart, *conformal_iopart_subset); + + stk::mesh::Part * nonconformal_iopart_subset = const_cast(find_nonconformal_part(*io_part_subset)); + ThrowRequire(NULL != nonconformal_iopart_subset); + + if(krinolog.shouldPrint(LOG_PARTS)) krinolog << "Adding " << nonconformal_iopart_subset->name() << " as subset of " << nonconformal_iopart->name() << stk::diag::dendl; + my_meta.declare_part_subset(*nonconformal_iopart, *nonconformal_iopart_subset); + + get_input_blocks_touching_surface(my_input_block_surface_connectivity, io_part_subset->mesh_meta_data_ordinal(), touching_block_ordinals); + for (auto && touching_block_ordinal : touching_block_ordinals) + { + const std::string conformal_part_alias = conformal_iopart->name() + "_" + my_meta.get_part(touching_block_ordinal).name(); + if(krinolog.shouldPrint(LOG_PARTS)) krinolog << "Adding alias " << conformal_part_alias << " for conformal part " << conformal_iopart_subset->name() << stk::diag::dendl; + my_aux_meta.define_part_alias(*conformal_iopart_subset, conformal_part_alias); + } + } + } + } + } +} + +void +Phase_Support::build_decomposed_block_surface_connectivity() +{ + for (auto && part : my_meta.get_mesh_parts()) + { + if (part->primary_entity_rank() != my_meta.side_rank()) continue; + const PhasePartTag * phase_part = find_conformal_phase_part(*part); + if (nullptr == phase_part) continue; + stk::mesh::Part & orig_part = my_meta.get_part(phase_part->get_original_part_ordinal()); + if (orig_part == my_meta.universal_part()) continue; + + if (phase_part->is_interface()) + { + const stk::mesh::Part * conformal_touching_block = find_conformal_io_part(orig_part, phase_part->get_touching_phase()); + ThrowRequire(conformal_touching_block); + if(krinolog.shouldPrint(LOG_PARTS)) krinolog << "Surface " << part->name() << " touches block " << conformal_touching_block->name() << "\n"; + std::vector touching_blocks = my_meta.get_blocks_touching_surface(part); + if (std::find(touching_blocks.begin(), touching_blocks.end(), conformal_touching_block) == touching_blocks.end()) + { + touching_blocks.push_back(conformal_touching_block); + } + my_meta.set_surface_to_block_mapping(part, touching_blocks); + } + else + { + std::set touching_block_ordinals; + get_input_blocks_touching_surface(my_input_block_surface_connectivity, orig_part.mesh_meta_data_ordinal(), touching_block_ordinals); + + for (auto && touching_block_ordinal : touching_block_ordinals) + { + stk::mesh::Part & touching_block = my_meta.get_part(touching_block_ordinal); + const stk::mesh::Part * conformal_touching_block = find_conformal_io_part(touching_block, phase_part->get_phase()); + ThrowRequire(conformal_touching_block); + if(krinolog.shouldPrint(LOG_PARTS)) krinolog << "Surface " << part->name() << " touches block " << conformal_touching_block->name() << "\n"; + std::vector touching_blocks = my_meta.get_blocks_touching_surface(part); + if (std::find(touching_blocks.begin(), touching_blocks.end(), conformal_touching_block) == touching_blocks.end()) + { + touching_blocks.push_back(conformal_touching_block); + } + my_meta.set_surface_to_block_mapping(part, touching_blocks); + } + } + } +} + +void +Phase_Support::create_interface_phase_parts( + const PhaseVec& ls_phases, + const PartSet& decomposed_ioparts, + const Interface_Name_Generator& interface_name_gen) +{ + const int num_ls_phases = ls_phases.size(); + if (num_ls_phases > 1) + { + const int num_interfaces = num_ls_phases * (num_ls_phases - 1) / 2; + std::vector > interface_phases; + interface_phases.reserve(num_interfaces); + for (int i = 0; i < num_ls_phases; ++i) + { + for (int j = i+1; j < num_ls_phases; ++j) + { + std::pair phase_pair(i, j); + interface_phases.push_back(phase_pair); + } + } + ThrowRequire((int) interface_phases.size() == num_interfaces); + + for (int i = 0; i < num_interfaces; ++i) + { + const int name_compare = ls_phases[interface_phases[i].first].name().compare(ls_phases[interface_phases[i].second].name()); + ThrowRequire(name_compare != 0); + // form phase intersection + const auto & phase0 = (name_compare < 0) ? ls_phases[interface_phases[i].first] : ls_phases[interface_phases[i].second]; + const auto & phase1 = (name_compare < 0) ? ls_phases[interface_phases[i].second] : ls_phases[interface_phases[i].first]; + std::string superset_interface_phase_name = phase0.name() + "_" + phase1.name(); + const std::string superset_phase_part_name = interface_name_gen.interface_superset_name(superset_interface_phase_name); + + // assumes all sides have same topology + stk::mesh::Part &superset_phase_part = my_aux_meta.declare_io_part(superset_phase_part_name, my_meta.side_rank()); + + if(krinolog.shouldPrint(LOG_PARTS)) krinolog << "Adding interfacial phase part: conformal_part_name=" << superset_phase_part.name() << "\n"; + + for (auto && phase_pair : {std::make_pair(phase0,phase1), std::make_pair(phase1,phase0)}) + { + const auto & touching_phase = phase_pair.first.tag(); + const auto & opposite_phase = phase_pair.second.tag(); + + my_phase_parts.insert(PhasePartTag( + superset_phase_part.mesh_meta_data_ordinal(), my_meta.universal_part().mesh_meta_data_ordinal(), + my_meta.universal_part().mesh_meta_data_ordinal(), touching_phase, + opposite_phase)); + + for (auto * io_part : decomposed_ioparts) + { + ThrowRequire(NULL != io_part); + // only handle blocks for now -> creating interfacial surface "phases" + if (io_part->primary_entity_rank() == stk::topology::ELEMENT_RANK) + { + std::string subset_interface_phase_name = phase_pair.first.name() + "_" + phase_pair.second.name(); + const std::string subset_phase_part_name = interface_name_gen.interface_subset_name(io_part->name(), subset_interface_phase_name); + const bool restartOnlyIOPart = true; // Subset part is only output for restart + stk::mesh::Part &subset_phase_part = my_aux_meta.declare_io_part_with_topology(subset_phase_part_name, io_part->topology().side_topology(), restartOnlyIOPart); + my_meta.declare_part_subset(superset_phase_part, subset_phase_part); + + const stk::mesh::Part * const nonconf_part = find_nonconformal_part(*io_part); + ThrowRequire(NULL != nonconf_part); + + if(krinolog.shouldPrint(LOG_PARTS)) + { + krinolog << " Adding subset interfacial phase part: conformal_part_name=" << subset_phase_part.name() + << ", nonconformal_part_name=" << nonconf_part->name(); + krinolog << ", touching_phase=" << touching_phase + << ", opposite_phase=" << opposite_phase; + krinolog << "\n"; + } + + my_phase_parts.insert(PhasePartTag( + subset_phase_part.mesh_meta_data_ordinal(), nonconf_part->mesh_meta_data_ordinal(), + io_part->mesh_meta_data_ordinal(), touching_phase, + opposite_phase)); + + all_decomposed_blocks_selector |= subset_phase_part; + part_is_conformal_map[&subset_phase_part] = true; + part_to_nonconformal_part_map[&subset_phase_part] = nonconf_part; + auto & phase_to_conformal_map = nonconformal_to_phase_conformal_map[nonconf_part]; + auto & touching_phase_part = phase_to_conformal_map[touching_phase]; + auto & opposite_phase_part = phase_to_conformal_map[opposite_phase]; + auto key = std::make_pair(touching_phase_part, opposite_phase_part); + volume_to_interface_parts_map[key] = &subset_phase_part; + } + } + } + } + } +} + +void +Phase_Support::decompose_blocks( + const stk::mesh::PartVector& blocks_decomposed_by_ls, + const PhaseVec & ls_phases, + const Interface_Name_Generator & interface_name_gen) +{ + if(ls_phases.empty()) return; + + stk::topology topo; + for(auto && decompose_block : blocks_decomposed_by_ls) + { + topo = decompose_block->topology(); + if(topo != stk::topology::TET_4 && topo != stk::topology::TET_10 + && topo != stk::topology::TRI_3_2D && topo != stk::topology::TRI_6_2D) + { + stk::RuntimeDoomedAdHoc() << "Currently only Tetrahedron 4 or 10 (in 3D) and Triangle 3 or 6 (in 2D) element types are supported when using CDFEM.\n"; + return; + } + } + + PartSet decomposed_ioparts = get_blocks_and_touching_surfaces(my_meta, blocks_decomposed_by_ls, my_input_block_surface_connectivity); + + create_nonconformal_parts(decomposed_ioparts); + + create_phase_parts(ls_phases, decomposed_ioparts); + + subset_and_alias_surface_phase_parts(ls_phases, decomposed_ioparts); + + create_interface_phase_parts(ls_phases, decomposed_ioparts, interface_name_gen); +} + +//-------------------------------------------------------------------------------- + +void +Phase_Support::determine_block_phases(const std::set & FEmodel_block_names, const krino::PhaseVec & FEmodel_phases) +{ + if (FEmodel_phases.empty()) return; + + ThrowAssertMsg(my_mesh_block_phases.empty(), "determine_block_phases should only be called once per mesh"); + + for (auto && part : my_meta.get_parts()) + { + if (part->primary_entity_rank() == stk::topology::ELEMENT_RANK) + { + auto & block_phases = my_mesh_block_phases[part->mesh_meta_data_ordinal()]; + for(unsigned phase_index=0; phase_indexname() + "_" + phase_name; + std::transform(phase_part_name.begin(), phase_part_name.end(), phase_part_name.begin(), ::tolower); + if (FEmodel_block_names.find(phase_part_name) != FEmodel_block_names.end() ) + { + block_phases.insert(phase_index); + } + } + } + } +} + +//-------------------------------------------------------------------------------- + +void +Phase_Support::determine_block_phases(const PhaseVec & FEmodel_phases, const PartnamePhasenameMap & FEmodel_block_phase_names) +{ + if (FEmodel_phases.empty()) return; + + ThrowAssertMsg(my_mesh_block_phases.empty(), "determine_block_phases should only be called once per mesh"); + + if (FEmodel_block_phase_names.empty()) + { + stk::RuntimeDoomedAdHoc() << "Failure in determine_block_phases. Were the subdomains specified for the decomposed blocks in the finite element model specification?\n"; + return; + } + + std::map phase_name_to_index_map; + for(unsigned phase_index=0; phase_indexsecond); + } + } + } + else + { + stk::RuntimeDoomedAdHoc() << "The block " << map_entry.first << " is listed in the finite element model specification, but is not in the input mesh.\n"; + } + } +} + +//-------------------------------------------------------------------------------- + +std::vector +Phase_Support::get_blocks_decomposed_by_levelset(const std::vector ls_phases) const +{ + std::vector blocks_decomposed_by_ls; + for (auto && map_it : my_mesh_block_phases) + { + stk::mesh::Part & FEmodel_block = my_meta.get_part(map_it.first); + const auto & block_phases = map_it.second; + for(auto ls_phase : ls_phases) + { + if (block_phases.find(ls_phase) != block_phases.end()) + { + blocks_decomposed_by_ls.push_back(&FEmodel_block); + break; + } + } + } + + return blocks_decomposed_by_ls; +} + +//-------------------------------------------------------------------------------- + +void +Phase_Support::setup_phases(const krino::PhaseVec & FEmodel_phases) +{ + for (auto && map_it : my_mesh_block_phases) + { + stk::mesh::Part & FEmodel_block = my_meta.get_part(map_it.first); + const auto & block_phase_ordinals = map_it.second; + + PhaseVec block_phases; + for(auto && block_phase_ordinal : block_phase_ordinals) + { + block_phases.push_back(FEmodel_phases[block_phase_ordinal]); + } + + LS_Name_Generator interface_name_gen; + decompose_blocks({&FEmodel_block}, block_phases, interface_name_gen); + } + + build_decomposed_block_surface_connectivity(); +} + +//-------------------------------------------------------------------------------- + +void +Phase_Support::get_iopart_roots(const stk::mesh::Part & iopart, std::vector & subsets) +{ /* %TRACE% */ /* %TRACE% */ + if ( iopart.subsets().empty() ) + { + subsets.push_back(&iopart); + } + else + { + for (stk::mesh::PartVector::const_iterator subset = iopart.subsets().begin(); subset != iopart.subsets().end() ; ++subset ) + { + ThrowRequire(stk::io::is_part_io_part(**subset)); + get_iopart_roots(**subset, subsets); + } + } +} +//-------------------------------------------------------------------------------- +void +Phase_Support::get_blocks_touching_surface(const std::string & surface_name, std::vector & block_names) +{ /* %TRACE% */ /* %TRACE% */ + + std::set block_ordinal_set; + for (auto&& block_ordinal : block_ordinal_set) + { + block_names.push_back(my_meta.get_part(block_ordinal).name()); + } +} +//-------------------------------------------------------------------------------- +void +Phase_Support::get_input_surfaces_touching_block(const Block_Surface_Connectivity & input_block_surface_connectivity, + const stk::mesh::PartOrdinal block_ordinal, std::set & surface_ordinals) +{ + input_block_surface_connectivity.get_surfaces_touching_block(block_ordinal, surface_ordinals); +} + +//-------------------------------------------------------------------------------- +void +Phase_Support::get_input_blocks_touching_surface(const Block_Surface_Connectivity & input_block_surface_connectivity, + const stk::mesh::PartOrdinal surfaceOrdinal, std::set & blockOrdinals) +{ + input_block_surface_connectivity.get_blocks_touching_surface(surfaceOrdinal, blockOrdinals); +} +//-------------------------------------------------------------------------------- +const stk::mesh::Part * +Phase_Support::find_conformal_io_part(const stk::mesh::Part & io_part, const PhaseTag & phase) const +{ + const stk::mesh::Part * const nonconf_part = find_nonconformal_part(io_part); + auto entry = nonconformal_to_phase_conformal_map.find(nonconf_part); + if(entry == nonconformal_to_phase_conformal_map.end()) return &io_part; + PhaseTagToPartMap & tag_to_conformal_map = (*entry).second; + + const auto find_part = tag_to_conformal_map.find(phase); + if(find_part != tag_to_conformal_map.end()) + { + auto & tag_part_pair = *find_part; + return tag_part_pair.second; + } + + // This search is to handle the case where the phase passed in contains additional LS side tags that the + // conformal part phase tag doesn't care about. For example if the conformal phase is defined as "where LS1 is negative" + // it should match a phase tag that has (0,1) (1,-1) even though the tags are not == + // TODO: If we knew about all the LS's in the problem when decompose_blocks was called we could populate all the necessary entries + // then and wouldn't need this search. + for(auto && tag_part_pair : tag_to_conformal_map) + { + if(phase.contain(tag_part_pair.first)) + { + tag_to_conformal_map[phase] = tag_part_pair.second; + return tag_part_pair.second; + } + } + + return &io_part; +} + +//-------------------------------------------------------------------------------- + +bool +Phase_Support::is_conformal(const stk::mesh::Part * io_part) const +{ + PartToBoolMap::const_iterator entry = part_is_conformal_map.find(io_part); + if (entry != part_is_conformal_map.end()) return (entry->second); + return false; +} + +//-------------------------------------------------------------------------------- + +bool +Phase_Support::is_nonconformal(const stk::mesh::Part * io_part) const +{ + PartToBoolMap::const_iterator entry = part_is_nonconformal_map.find(io_part); + if (entry != part_is_nonconformal_map.end()) return (entry->second); + return false; +} + +//-------------------------------------------------------------------------------- + +bool +Phase_Support::is_interface(const stk::mesh::Part * io_part) const +{ + ThrowAssert(io_part); + auto phase_part = find_conformal_phase_part(*io_part); + return phase_part && phase_part->is_interface(); +} + +//-------------------------------------------------------------------------------- + +const stk::mesh::Part * +Phase_Support::find_nonconformal_part(const stk::mesh::Part & io_part) const +{ + PartToPartMap::const_iterator entry = part_to_nonconformal_part_map.find(&io_part); + if (entry != part_to_nonconformal_part_map.end()) return (entry->second); + return &io_part; +} + +//-------------------------------------------------------------------------------- + +const stk::mesh::Part * +Phase_Support::find_interface_part(const stk::mesh::Part & vol0, const stk::mesh::Part & vol1) const +{ + auto key = std::make_pair(&vol0, &vol1); + auto find_it = volume_to_interface_parts_map.find(key); + if(find_it != volume_to_interface_parts_map.end()) return find_it->second; + + return nullptr; +} + +//-------------------------------------------------------------------------------- + +const PhaseTag & +Phase_Support::get_iopart_phase(const stk::mesh::Part & io_part) const +{ + PartToPhaseTagMap::const_iterator entry = part_to_phase_map.find(&io_part); + if (entry != part_to_phase_map.end()) return (entry->second); + + static const PhaseTag empty_phase; + return empty_phase; +} + +//-------------------------------------------------------------------------------- + +void Phase_Support::register_blocks_for_level_set(LevelSet * levelSet, + const std::vector & blocks_decomposed_by_ls) +{ + nonconformalLsMapsAreFilled_ = false; + + // Loop over block names + for (auto && block_ptr : blocks_decomposed_by_ls) + { + // Add direct relationship between this block and level set + lsUsedByParts_[levelSet].insert(block_ptr); + + // Now get surfaces touching this block + std::set surfaceOrdinals; + get_input_surfaces_touching_block(my_input_block_surface_connectivity, block_ptr->mesh_meta_data_ordinal(), surfaceOrdinals); + for (auto && surfaceOrdinal : surfaceOrdinals) + { + // For each surface, add IO Part/Level Set pairing to maps + stk::mesh::Part & surfaceIOPart = my_meta.get_part(surfaceOrdinal); + lsUsedByParts_[levelSet].insert(&surfaceIOPart); + } + } +} + + +//-------------------------------------------------------------------------------- +bool Phase_Support::level_set_is_used_by_nonconformal_part(const LevelSet * levelSet, + const stk::mesh::Part * const ioPart) const +{ + if (!nonconformalLsMapsAreFilled_) + fill_nonconformal_level_set_maps(); + + IOPartSet & ioPartSet = lsUsedByNonconformalParts_[levelSet]; + bool contains = (ioPartSet.find(ioPart) != ioPartSet.end()); + return contains; +} + +//-------------------------------------------------------------------------------- +void Phase_Support::fill_nonconformal_level_set_maps() const +{ + if (nonconformalLsMapsAreFilled_ == true) return; + nonconformalLsMapsAreFilled_ = true; + + // In this method we duplicate the level-set-to-part maps, but use + // the nonconformal versions of each IOPart instead + + // lsUsedByNonconformalParts_ + lsUsedByNonconformalParts_.clear(); + std::map::const_iterator iterLSMap; + for (iterLSMap = lsUsedByParts_.begin(); iterLSMap != lsUsedByParts_.end(); ++iterLSMap) + { + const LevelSet * levelSet = iterLSMap->first; + const IOPartSet & ioPartSet = iterLSMap->second; + IOPartSet::const_iterator iterPart; + for (iterPart = ioPartSet.begin(); iterPart != ioPartSet.end(); ++iterPart) + { + const stk::mesh::Part * ioPart = *iterPart; + const stk::mesh::Part * nonconformalIOPart = find_nonconformal_part(*ioPart); + lsUsedByNonconformalParts_[levelSet].insert(nonconformalIOPart); + } + } +} + +const PhasePartTag * Phase_Support::find_conformal_phase_part(const stk::mesh::Part & conformal_part) const +{ + const PhasePartTag * result = NULL; + + const unsigned conformal_part_ordinal = conformal_part.mesh_meta_data_ordinal(); + + // Take advantage of the fact that PhasePartTags are uniquely identified by the conformal_part_ordinal + // and therefore the set of phase parts is sorted only based on that to use the binary search + // my_phase_parts.find(). + PhaseTag dummy_tag; + dummy_tag.add(LevelSet_Identifier(0), 1); + PhasePartTag search_tag(conformal_part_ordinal, 0, 0, dummy_tag); + + auto it = my_phase_parts.find(search_tag); + if(it != my_phase_parts.end()) result = &(*it); + + return result; +} + +//-------------------------------------------------------------------------------- +CDFEM_Irreversible_Phase_Support & +CDFEM_Irreversible_Phase_Support::get(stk::mesh::MetaData & meta) +{ + CDFEM_Irreversible_Phase_Support * support = const_cast(meta.get_attribute()); + if (NULL == support) + { + support = new CDFEM_Irreversible_Phase_Support(); + meta.declare_attribute_with_delete(support); + } + return *support; +} +//-------------------------------------------------------------------------- +CDFEM_Inequality_Spec * CDFEM_Irreversible_Phase_Support::add_death_spec(const std::string & death_name, bool is_death) +{ + // CDFEM death needs the decomposition to occur at the end of the time step, irreversible phase change + // needs it to occur at the start of the time step. For now throw if the user tries to do both in the same problem. + if(is_death) + { + has_death = true; + } + else + { + has_irreversible_phase_change = true; + } + ThrowInvalidArgMsgIf(has_death && has_irreversible_phase_change, + "Cannot have both CDFEM death and CDFEM irreversible phase change in the same problem."); + + for (auto&& death_spec : my_death_specs) + { + ThrowInvalidArgMsgIf(death_spec.name() == death_name, + "Only one CDFEM death specification with the same name is allowed per mesh (" << death_name << "). "); + } + my_death_specs.push_back(CDFEM_Inequality_Spec(death_name)); + return &my_death_specs.back(); +} + +//-------------------------------------------------------------------------- + +CDFEM_Inequality_Spec::CDFEM_Inequality_Spec(const std::string & name_) + : my_name(name_), + my_criterion_compare_type(INEQUALITY_CRITERION_TYPE_UNDEFINED), + my_ls(NULL) +{} + +CDFEM_Inequality_Spec::InequalityCriterionType +CDFEM_Inequality_Spec::int_to_inequality_criterion_type(const int inequality_criterion) +{ + /* %TRACE[ON]% */ Trace trace__("krino::CDFEM_Death_Spec::int_to_death_criterion_compare_type(const int death_criterion_compare)"); /* %TRACE% */ + + switch (inequality_criterion) + { + case int(INEQUALITY_CRITERION_TYPE_UNDEFINED): + return INEQUALITY_CRITERION_TYPE_UNDEFINED ; + case int(LESS_THAN ): + return LESS_THAN ; + case int(GREATER_THAN ): + return GREATER_THAN ; + default: + ThrowRuntimeError("Bad integer passed. Could not convert passed in integer " + << "death_criterion_compare = " << inequality_criterion + << " into a CDFEM_Death_Spec::DeathCriterionCompareType enum. " + << "The code and the Sierra XML database may be out of sync." << std::endl + << StackTrace); + } +} + +void CDFEM_Inequality_Spec::add_element_volume_name (const std::string & a_element_volume_name) +{ + /* %TRACE[NONE]% */ /* %TRACE% */ + + // Note: It is ok to add multiple volume names to vector, thus no return status given + my_element_volume_names.push_back(a_element_volume_name); +} + +bool CDFEM_Inequality_Spec::set_threshold_variable_name (const std::string & a_threshold_variable_name) +{ + /* %TRACE[ON]% */ Trace trace__("krino::CDFEM_Death_Spec::set_death_variable_name(const std::string & a_death_variable_name)"); /* %TRACE% */ + + // Set return status to false if value has already been set + bool return_status = (0 == my_threshold_variable_name.length()); + + if (return_status) + my_threshold_variable_name = a_threshold_variable_name; + + return return_status; +} + +bool CDFEM_Inequality_Spec::set_threshold_value (const double a_threshold_value) +{ + /* %TRACE[ON]% */ Trace trace__("krino::CDFEM_Death_Spec::set_threshold_value(const double a_threshold_value)"); /* %TRACE% */ + + // Set return status to false if value has already been set + bool return_status = (std::numeric_limits::max() != a_threshold_value); + + if (return_status) + my_threshold_value = a_threshold_value; + + return return_status; +} + +bool CDFEM_Inequality_Spec::set_criterion_compare_type + (const CDFEM_Inequality_Spec::InequalityCriterionType & a_criterion_compare_type) +{ + /* %TRACE[ON]% */ Trace trace__("krino::CDFEM_Death_Spec::set_criterion_compare_type(const CDFEM_Death_Spec::DeathCriterionCompareType & a_criterion_compare_type)"); /* %TRACE% */ + + // Set return status to false if value has already been set + bool return_status = (CDFEM_Inequality_Spec::INEQUALITY_CRITERION_TYPE_UNDEFINED == my_criterion_compare_type); + + if (return_status) + my_criterion_compare_type = a_criterion_compare_type; + + return return_status; +} + +void CDFEM_Inequality_Spec::sanity_check(const std::vector mesh_elem_blocks) const +{ + /* %TRACE[ON]% */ Trace trace__("krino::CDFEM_Death_Spec::sanity_check()"); /* %TRACE% */ + + ThrowErrorMsgIf(CDFEM_Inequality_Spec::INEQUALITY_CRITERION_TYPE_UNDEFINED == my_criterion_compare_type, + "CDFEM Death is not properly setup. Was the death criterion specified?"); + + const std::vector & volume_names = get_element_volume_names(); + + for (auto && block_name : volume_names) + { + ThrowErrorMsgIf( + std::find(mesh_elem_blocks.begin(), mesh_elem_blocks.end(), block_name) == mesh_elem_blocks.end(), + "Could not find an element volume named '" + << block_name << "' that was specified in the " + << "cdfem death command block for the cdfem death named '" + << name() << "'."); + } +} + +void CDFEM_Inequality_Spec::create_levelset(stk::mesh::MetaData & meta, stk::diag::Timer & parent_timer) +{ + /* %TRACE[ON]% */ Trace trace__("krino::CDFEM_Death_Spec::create_levelset()"); /* %TRACE% */ + + ThrowInvalidArgMsgIf( + LevelSetManager::get(meta).has_levelSet(my_name), + "Region already has a LevelSet named " << my_name); + + my_ls = &LevelSet::build(meta, my_name, parent_timer); + my_ls->set_isovar(get_threshold_variable_name(), get_threshold_value()); + + int deactivated_phase_sign = ( get_criterion_compare_type() == CDFEM_Inequality_Spec::LESS_THAN ) ? -1 : 1; + my_deactivated_phase.add(my_ls->get_identifier(), deactivated_phase_sign); + my_active_phase.add(my_ls->get_identifier(), -1 * deactivated_phase_sign); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Phase_Support.hpp b/packages/krino/krino/krino_lib/Akri_Phase_Support.hpp new file mode 100644 index 000000000000..05eb9443e4bf --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Phase_Support.hpp @@ -0,0 +1,212 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Phase_Support_h +#define Akri_Phase_Support_h +// +#include +#include + +#include +#include +#include + +#include + +namespace stk { namespace mesh { class BulkData; } } +namespace stk { namespace mesh { class MetaData; } } +namespace stk { namespace diag { class Timer; } } + +namespace krino { + +class AuxMetaData; +class LevelSet; +struct LS_Field; + +typedef std::set< const stk::mesh::Part * > IOPartSet; +typedef std::set< LevelSet * > LevelSetSet; +typedef std::map> PartPhaseMap; +typedef std::map> PartnamePhasenameMap; + +class Phase_Support { +public: + typedef std::set PartSet; + + static bool exists_and_has_phases_defined(const stk::mesh::MetaData & meta); + static Phase_Support & get(stk::mesh::MetaData & meta); + static Phase_Support & get(const stk::mesh::MetaData & meta); + + void get_blocks_touching_surface(const std::string & surface_name, std::vector & block_names); + static void get_input_surfaces_touching_block(const Block_Surface_Connectivity & input_block_surface_info, + const stk::mesh::PartOrdinal block_ordinal, std::set & surface_ordinals); + void get_input_blocks_touching_surface(const Block_Surface_Connectivity & input_block_surface_info, + const stk::mesh::PartOrdinal surfaceOrdinal, std::set & blockOrdinals); + + void check_phase_parts() const; + + bool phases_defined() const { return !my_phase_parts.empty(); } + + void decompose_blocks(const stk::mesh::PartVector& blocks_decomposed_by_ls, + const PhaseVec & ls_phases, const Interface_Name_Generator & interface_name_gen); + std::vector get_blocks_decomposed_by_levelset(const std::vector ls_phases) const; + void setup_phases(const krino::PhaseVec & FEmodel_phases); + void set_input_block_surface_connectivity(const Block_Surface_Connectivity & input_block_surface_info) { my_input_block_surface_connectivity = input_block_surface_info; } + void build_decomposed_block_surface_connectivity(); + + bool is_nonconformal(const stk::mesh::Part * io_part) const; + bool is_conformal(const stk::mesh::Part * io_part) const; + bool is_interface(const stk::mesh::Part * io_part) const; + const stk::mesh::Part * find_conformal_io_part(const stk::mesh::Part & io_part, const PhaseTag & phase) const; + const stk::mesh::Part * find_nonconformal_part(const stk::mesh::Part & io_part) const; + const stk::mesh::Part * find_interface_part(const stk::mesh::Part & vol0, const stk::mesh::Part & vol1) const; + const PhaseTag & get_iopart_phase(const stk::mesh::Part & io_part) const; + + void add_decomposed_part(const stk::mesh::Part & part) { all_decomposed_blocks_selector |= part; } + const stk::mesh::Selector & get_all_decomposed_blocks_selector() const { return all_decomposed_blocks_selector; } + + stk::mesh::Selector get_interface_part_selector(const LS_Field & ls_field); + void register_blocks_for_level_set(LevelSet * levelSet, + const std::vector & blocks_decomposed_by_ls); + stk::mesh::Selector get_all_conformal_surfaces_selector() const; + + bool level_set_is_used_by_nonconformal_part(const LevelSet * levelSet, const stk::mesh::Part * const ioPart) const; + + static bool has_one_levelset_per_phase() { return oneLevelSetPerPhase; } + static void set_one_levelset_per_phase(const bool val) { oneLevelSetPerPhase = val; } + + stk::mesh::PartVector get_nonconformal_parts() const; + stk::mesh::PartVector get_conformal_parts() const; + + void determine_block_phases(const std::set & FEmodel_block_names, const PhaseVec & FEmodel_phases); + void determine_block_phases(const PhaseVec & FEmodel_phases, const PartnamePhasenameMap & FEmodel_block_phase_names); + static PartSet get_blocks_and_touching_surfaces(const stk::mesh::MetaData & mesh_meta, const stk::mesh::PartVector& input_blocks, const Block_Surface_Connectivity & input_block_surface_info); + + static bool has_phases(const std::string & mesh_name) {std::map::const_iterator pos=the_mesh_phases.find(mesh_name); return (pos != the_mesh_phases.end());} + static PhaseVec & get_phases(const std::string & mesh_name) {return the_mesh_phases[mesh_name];} + static std::vector get_level_set_phases(const PhaseVec & mesh_phases, const LevelSet & levelSet); + static PartnamePhasenameMap & get_block_phases_by_name(const std::string & mesh_name) {return the_mesh_block_phases_by_name[mesh_name];} + +private: + Phase_Support(stk::mesh::MetaData & meta); + + const PhasePartTag * find_conformal_phase_part(const stk::mesh::Part & conformal_part) const; + void create_nonconformal_parts(const PartSet & decomposed_ioparts); + void addPhasePart(stk::mesh::Part & io_part, PhasePartSet & phase_parts, const NamedPhase & ls_phase); + void create_phase_parts(const PhaseVec& ls_phases, const PartSet& decomposed_ioparts); + void subset_and_alias_surface_phase_parts(const PhaseVec& ls_phases, const PartSet& decomposed_ioparts); + void create_interface_phase_parts(const PhaseVec& ls_phases, const PartSet& decomposed_ioparts, const Interface_Name_Generator& interface_name_gen); + void get_iopart_roots(const stk::mesh::Part & iopart, std::vector & subsets); + void fill_nonconformal_level_set_maps() const; + +private: + stk::mesh::MetaData & my_meta; + AuxMetaData & my_aux_meta; + PhasePartSet my_phase_parts; + Block_Surface_Connectivity my_input_block_surface_connectivity; + PartPhaseMap my_mesh_block_phases; + std::map lsUsedByParts_; + mutable bool nonconformalLsMapsAreFilled_; + + mutable std::map lsUsedByNonconformalParts_; + static bool oneLevelSetPerPhase; + static std::map the_mesh_phases; + static std::map the_mesh_block_phases_by_name; + + typedef std::map< const stk::mesh::Part *, bool, stk::mesh::PartLess> PartToBoolMap; + typedef std::map< const stk::mesh::Part *, const stk::mesh::Part *, stk::mesh::PartLess > PartToPartMap; + typedef std::map PhaseTagToPartMap; + typedef std::map PartToPhaseTagToPartMap; + typedef std::map PartToPhaseTagMap; + typedef std::map, + const stk::mesh::Part *> VolumePartsToInterfacePartMap; + mutable PartToPhaseTagToPartMap nonconformal_to_phase_conformal_map; + VolumePartsToInterfacePartMap volume_to_interface_parts_map; + PartToBoolMap part_is_conformal_map; + PartToBoolMap part_is_nonconformal_map; + PartToPartMap part_to_nonconformal_part_map; + PartToPhaseTagMap part_to_phase_map; + + stk::mesh::Selector all_decomposed_blocks_selector; +}; + +class CDFEM_Inequality_Spec { +public: + + CDFEM_Inequality_Spec(const std::string & name_); + ~CDFEM_Inequality_Spec() {} + const std::string & name() const {return my_name;} + + enum InequalityCriterionType + { + INEQUALITY_CRITERION_TYPE_UNDEFINED = 0, + LESS_THAN , + GREATER_THAN + }; + + static InequalityCriterionType int_to_inequality_criterion_type(const int inequality_criterion); + + void add_element_volume_name(const std::string & a_element_volue_name); + bool set_threshold_variable_name(const std::string & a_threshold_variable_name); + bool set_threshold_value(const double a_threshold_value); + bool set_criterion_compare_type(const InequalityCriterionType & a_criterion_compare_type); + + void sanity_check(const std::vector mesh_elem_blocks) const; + const std::vector & get_element_volume_names() const { return my_element_volume_names; } + const std::string & get_threshold_variable_name () const { return my_threshold_variable_name; } + double get_threshold_value () const { return my_threshold_value; } + InequalityCriterionType get_criterion_compare_type() const { return my_criterion_compare_type; } + const PhaseTag & get_deactivated_phase() const { return my_deactivated_phase; } + const PhaseTag & get_active_phase() const { return my_active_phase; } + void create_levelset(stk::mesh::MetaData & meta, stk::diag::Timer & parent_timer); + const LevelSet & get_levelset() const { ThrowRequire(my_ls != NULL); return *my_ls; } + LevelSet & get_levelset() { ThrowRequire(my_ls != NULL); return *my_ls; } + + // for unit testing + void set_phases(const PhaseTag & active_phase, const PhaseTag & inactive_phase) + { + my_active_phase = active_phase; + my_deactivated_phase = inactive_phase; + } +protected: + std::string my_name; + std::vector my_element_volume_names; + std::string my_threshold_variable_name; + double my_threshold_value; + InequalityCriterionType my_criterion_compare_type; + LevelSet * my_ls; + PhaseTag my_deactivated_phase; + PhaseTag my_active_phase; +}; +typedef std::vector< CDFEM_Inequality_Spec > CDFEM_Inequality_Spec_Vec; + +class CDFEM_Irreversible_Phase_Support { +public: + ~CDFEM_Irreversible_Phase_Support() {} + + static CDFEM_Irreversible_Phase_Support & get(stk::mesh::MetaData & meta); + + CDFEM_Inequality_Spec * add_death_spec(const std::string & death_name, bool is_death); + + bool is_active() const {return has_death || has_irreversible_phase_change;} + + const CDFEM_Inequality_Spec_Vec & get_death_specs() const {return my_death_specs;} + + // For irreversible phase changes we decompose at the start of the time step, for death at the end. + bool decompose_at_start_of_time_step() { ThrowAssert(has_irreversible_phase_change == !has_death); return has_irreversible_phase_change; } + +private: + CDFEM_Irreversible_Phase_Support() : has_death(false), has_irreversible_phase_change(false) {} +private: + bool has_death; + bool has_irreversible_phase_change; + CDFEM_Inequality_Spec_Vec my_death_specs; +}; + +} // namespace krino + +#endif // Akri_Phase_Support_h diff --git a/packages/krino/krino/krino_lib/Akri_Plane.hpp b/packages/krino/krino/krino_lib/Akri_Plane.hpp new file mode 100644 index 000000000000..b2c7773a50f6 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Plane.hpp @@ -0,0 +1,20 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Plane3d_h +#define Akri_Plane3d_h + +#include + +namespace krino { + +typedef stk::math::Plane3 Plane3d; + +} // namespace krino + +#endif // Akri_Plane3d_h diff --git a/packages/krino/krino/krino_lib/Akri_Plane_Intersections.cpp b/packages/krino/krino/krino_lib/Akri_Plane_Intersections.cpp new file mode 100644 index 000000000000..ce4e72be2807 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Plane_Intersections.cpp @@ -0,0 +1,144 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +namespace krino +{ + +bool +find_intersection_of_three_planes(const Plane3d & plane0, const Plane3d & plane1, const Plane3d & plane2, Vector3d & point) +{ + const Vector3d & normal0 = plane0.normal(); + const Vector3d & normal1 = plane1.normal(); + const Vector3d & normal2 = plane2.normal(); + + const double a00 = normal0[0]; + const double a01 = normal0[1]; + const double a02 = normal0[2]; + const double a10 = normal1[0]; + const double a11 = normal1[1]; + const double a12 = normal1[2]; + const double a20 = normal2[0]; + const double a21 = normal2[1]; + const double a22 = normal2[2]; + const double b0 = plane0.constant(); + const double b1 = plane1.constant(); + const double b2 = plane2.constant(); + const double det = a00*(a22*a11-a21*a12)-a10*(a22*a01-a21*a02)+a20*(a12*a01-a11*a02); + + if (det == 0) + return false; + + const double x =( b0*(a22*a11-a21*a12)-b1*(a22*a01-a21*a02)+b2*(a12*a01-a11*a02))/det; + const double y =(-b0*(a22*a10-a20*a12)+b1*(a22*a00-a20*a02)-b2*(a12*a00-a10*a02))/det; + const double z =( b0*(a21*a10-a20*a11)-b1*(a21*a00-a20*a01)+b2*(a11*a00-a10*a01))/det; + + point = Vector3d{x,y,z}; + return true; +} + +Vector3d +triangle_parametric_coordinates_of_projected_point(const std::array & triCoords, const Vector3d & pt) +{ + const Vector3d v1 = triCoords[1] - triCoords[0]; + const Vector3d v2 = triCoords[2] - triCoords[0]; + const Vector3d normal = Cross(v1,v2); + const double invNormalMag2 = 1./normal.length_squared(); + const Vector3d diff = pt - triCoords[0]; + Vector3d paramPt; + paramPt[0] = Dot(Cross(diff,v2),normal) * invNormalMag2; + paramPt[1] = Dot(Cross(v1,diff),normal) * invNormalMag2; + paramPt[2] = 0.; + return paramPt; +} + +static bool within_tet_bounds(const Vector3d & pt) +{ + const double tol = 1.e-13; + return (pt[0] > -tol) && + (pt[1] > -tol) && + (pt[2] > -tol) && + ((1.-pt[0]-pt[1]-pt[2]) > -tol); +} + +bool find_intersection_of_three_planes_within_tet(const Plane3d & plane0, const Plane3d & plane1, const Plane3d & plane2, Vector3d & intersectionPoint) +{ + return find_intersection_of_three_planes(plane0, plane1, plane2, intersectionPoint) && within_tet_bounds(intersectionPoint); +} + +static bool +find_intersection_of_two_planes_and_coordinate_plane(const int coordinatePlane, const Plane3d & plane0, const Plane3d & plane1, Vector3d & point) +{ + const Vector3d & normal0 = plane0.normal(); + const Vector3d & normal1 = plane1.normal(); + + const std::array,3> activeCoordsByPlane = {{ {{1,2}}, {{0,2}}, {{0,1}} }}; + const int i0 = activeCoordsByPlane[coordinatePlane][0]; + const int i1 = activeCoordsByPlane[coordinatePlane][1]; + + const double a00 = normal0[i0]; + const double a01 = normal0[i1]; + const double a10 = normal1[i0]; + const double a11 = normal1[i1]; + const double b0 = plane0.constant(); + const double b1 = plane1.constant(); + const double det = a00*a11-a10*a01; + + if (det == 0) + return false; + + point[i0] = (b0*a11-b1*a01)/det; + point[i1] = (b1*a00-b0*a10)/det; + point[coordinatePlane] = 0.; + + return true; +} + +bool find_intersection_of_two_planes_and_side_of_tet(const int side, const Plane3d & plane0, const Plane3d & plane1, Vector3d & intersectionPoint) +{ + bool intersectsPlaneThatCoincidesWithSideOfTet = false; + if (side == 1) + { + const Plane3d side1Plane{Vector3d{1.,0.,0.},Vector3d{0.,0.,1.},Vector3d{0.,1.,0.}}; + intersectsPlaneThatCoincidesWithSideOfTet = find_intersection_of_three_planes(plane0, plane1, side1Plane, intersectionPoint); + } + else + { + const int coordinatePlane = (side == 0) ? 1 : ((side == 2) ? 0 : 2); + intersectsPlaneThatCoincidesWithSideOfTet = find_intersection_of_two_planes_and_coordinate_plane(coordinatePlane, plane0, plane1, intersectionPoint); + } + + return intersectsPlaneThatCoincidesWithSideOfTet && within_tet_bounds(intersectionPoint); + +} + +bool find_intersection_of_two_2D_planes(const Plane3d & plane0, const Plane3d & plane1, Vector3d & intersectionPoint) +{ + return find_intersection_of_two_planes_and_coordinate_plane(2, plane0, plane1, intersectionPoint); +} + +bool within_tri_bounds(const Vector3d & triangleParamCoords) +{ + const double tol = 1.e-13; + return (triangleParamCoords[0] > -tol) && + (triangleParamCoords[1] > -tol) && + ((1.-triangleParamCoords[0]-triangleParamCoords[1]) > -tol); +} + +bool find_intersection_of_two_2D_planes_within_tri(const Plane3d & plane0, const Plane3d & plane1, Vector3d & intersectionPoint) +{ + return find_intersection_of_two_planes_and_coordinate_plane(2, plane0, plane1, intersectionPoint) && within_tri_bounds(intersectionPoint); +} + +} // namespace krino + + + diff --git a/packages/krino/krino/krino_lib/Akri_Plane_Intersections.hpp b/packages/krino/krino/krino_lib/Akri_Plane_Intersections.hpp new file mode 100644 index 000000000000..b7a20f805b00 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Plane_Intersections.hpp @@ -0,0 +1,31 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_PLANE_INTERSECTIONS_H_ +#define KRINO_INCLUDE_AKRI_PLANE_INTERSECTIONS_H_ +#include +#include + +namespace krino { + +bool find_intersection_of_three_planes(const Plane3d & plane0, const Plane3d & plane1, const Plane3d & plane2, Vector3d & point); + +bool find_intersection_of_three_planes_within_tet(const Plane3d & plane0, const Plane3d & plane1, const Plane3d & plane2, Vector3d & point); + +bool find_intersection_of_two_planes_and_side_of_tet(const int side, const Plane3d & plane0, const Plane3d & plane1, Vector3d & intersectionPoint); + +bool find_intersection_of_two_2D_planes(const Plane3d & plane0, const Plane3d & plane1, Vector3d & intersectionPoint); + +bool find_intersection_of_two_2D_planes_within_tri(const Plane3d & plane0, const Plane3d & plane1, Vector3d & intersectionPoint); + +Vector3d triangle_parametric_coordinates_of_projected_point(const std::array & triCoords, const Vector3d & pt); + +bool within_tri_bounds(const Vector3d & triangleParamCoords); +} + +#endif /* KRINO_INCLUDE_AKRI_PLANE_INTERSECTIONS_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_ProlongationData.cpp b/packages/krino/krino/krino_lib/Akri_ProlongationData.cpp new file mode 100644 index 000000000000..84a59e23926a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ProlongationData.cpp @@ -0,0 +1,659 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace krino{ + +ProlongationElementData::ProlongationElementData(const stk::mesh::BulkData& stk_mesh, const std::vector & children_data, const std::vector< std::vector > & children_intg_wts) +{ + ThrowAssert(!children_data.empty() && children_data.size() == children_intg_wts.size()); + + const unsigned num_intg_pts = children_intg_wts[0].size(); + + // homogenize fields + const stk::mesh::FieldVector & all_fields = stk_mesh.mesh_meta_data().get_fields(); + my_field_indices.resize(all_fields.size(), -1); + for ( stk::mesh::FieldVector::const_iterator it = all_fields.begin(); it != all_fields.end() ; ++it ) + { + const FieldRef field = **it; + + if( field.entity_rank()!=stk::topology::ELEMENT_RANK || !field.type_is() ) continue; + + bool any_child_has_field = false; + for (auto && child_data : children_data) + { + if (NULL != child_data->get_field_data(field)) + { + any_child_has_field = true; + break; + } + } + + if (any_child_has_field) + { + const unsigned field_length = field.length(); + const unsigned field_data_index = my_field_data.size(); + my_field_indices[field.field().mesh_meta_data_ordinal()] = field_data_index; + my_field_data.resize(field_data_index+field_length, 0.0); + + // TODO: Add a method to distinguish between vector fields and gauss point fields. + const bool data_is_gauss_pt_field = (num_intg_pts == field_length); + + if (data_is_gauss_pt_field) + { + double tot_child_sum = 0.; + double tot_child_vol = 0.; + + for (unsigned n = 0; n < children_data.size(); ++n) + { + const double * subelem_field_data = children_data[n]->get_field_data(field); + if(NULL == subelem_field_data) + { + continue; + } + + const std::vector & child_intg_wts = children_intg_wts[n]; + ThrowAssertMsg(child_intg_wts.size() == num_intg_pts, "Children have different integration rules."); + + for (unsigned j=0; jget_field_data(field); + if(NULL == subelem_field_data) + { + continue; + } + + const std::vector & child_intg_wts = children_intg_wts[n]; + // We could relax this assertion if we had another way to distinguish gauss point fields from vector fields + ThrowAssertMsg(child_intg_wts.size() == num_intg_pts, "Children have different integration rules."); + + double child_vol = 0.; + for (unsigned j=0; j() ) continue; + + double * val = field_data(field, entity); + const bool has_field = (NULL != val); + + if (has_field) + { + const unsigned field_length = field.length(); + my_field_indices[field.field().mesh_meta_data_ordinal()] = my_field_data.size(); + for (unsigned i=0; i() ) continue; + const unsigned field_length = field.length(); + + double * val = field_data(field, entity); + const double * prolong_field = get_field_data(field); + + if (nullptr == val) continue; + if(nullptr == prolong_field) + { + std::stringstream err_msg; + err_msg << "Missing prolongation field data when restoring fields on entity:\n"; + err_msg << stk_mesh.identifier(entity) << " of rank " << stk_mesh.entity_rank(entity); + err_msg << " with parts:\n "; + for(auto && part : stk_mesh.bucket(entity).supersets()) + { + err_msg << part->name() << ", "; + } + err_msg << "\n"; + err_msg << "Missing field data for field " << field.name() << "\n"; + throw std::runtime_error(err_msg.str()); + } + + for (unsigned i=0; i +ProlongationNodeData::get_fields_on_node(const stk::mesh::BulkData& mesh, stk::mesh::Entity entity) +{ + const stk::mesh::FieldVector & all_fields = mesh.mesh_meta_data().get_fields(); + std::vector entity_fields; + entity_fields.reserve(all_fields.size()); + for ( auto && fieldPtr : all_fields ) + { + const FieldRef field = *fieldPtr; + if (field.field().entity_rank() == stk::topology::NODE_RANK && nullptr != field_data(field, entity)) + entity_fields.push_back(field.field().mesh_meta_data_ordinal()); + } + + std::sort(entity_fields.begin(), entity_fields.end()); + return entity_fields; +} + +Vector3d +ProlongationNodeData::get_node_coordinates(const CDMesh & mesh, stk::mesh::Entity node) +{ + const double * coordsPtr = field_data(mesh.get_coords_field(), node); + ThrowAssert(coordsPtr); + Vector3d coords(coordsPtr, mesh.spatial_dim()); + FieldRef cdfemSnapDispField = mesh.get_cdfem_support().get_cdfem_snap_displacements_field(); + if (cdfemSnapDispField.valid()) + { + FieldRef oldCdfemSnapDispField = cdfemSnapDispField.field_state(stk::mesh::StateOld); + double * cdfemSnapDispPtr = field_data(cdfemSnapDispField, node); + double * oldCdfemSnapDispPtr = field_data(oldCdfemSnapDispField, node); + if (nullptr != cdfemSnapDispPtr) + coords += Vector3d(oldCdfemSnapDispPtr, mesh.spatial_dim()) - Vector3d(cdfemSnapDispPtr, mesh.spatial_dim()); + } + return coords; +} + +std::vector +ProlongationNodeData::get_node_io_parts(const stk::mesh::BulkData& stk_mesh, stk::mesh::Entity entity) +{ + // This list of parts is used to determine if a node needs to be ALE prolonged + stk::mesh::PartVector node_parts = filter_non_io_parts(stk_mesh.bucket(entity).supersets()); + std::vector node_part_ids; + node_part_ids.reserve(node_parts.size()); + for (auto * node_part : node_parts) + node_part_ids.push_back(node_part->mesh_meta_data_ordinal()); + std::sort(node_part_ids.begin(), node_part_ids.end()); + return node_part_ids; +} + +ProlongationNodeData::ProlongationNodeData(const CDMesh & mesh, stk::mesh::Entity node, bool communicate_me_to_all_sharers) + : ProlongationPointData(get_node_coordinates(mesh, node)), + my_entityId(mesh.stk_bulk().identifier(node)), + myCommunicateMeToAllSharersFlag(communicate_me_to_all_sharers) +{ + const stk::mesh::BulkData& stk_mesh = mesh.stk_bulk(); + + my_fields = get_fields_on_node(stk_mesh, node); + my_ioparts = get_node_io_parts(stk_mesh, node); + + save_fields(stk_mesh, node); +} + +ProlongationPointData::ProlongationPointData(const CDMesh & mesh, const FacetDistanceQuery & facet_dist_query, + const std::vector & facet_nodes) + : my_coordinates(facet_dist_query.closest_point()) +{ + const Vector3d node_wts = facet_dist_query.closest_point_weights(); + + // interpolate fields + const stk::mesh::FieldVector & all_fields = mesh.stk_meta().get_fields(); + my_field_indices.resize(all_fields.size(), -1); + for ( stk::mesh::FieldVector::const_iterator it = all_fields.begin(); it != all_fields.end() ; ++it ) + { + const FieldRef field = **it; + + if( field.entity_rank()!=stk::topology::NODE_RANK || !field.type_is() ) continue; + + const unsigned field_length = field.length(); + + bool any_node_has_field = false; + for (auto && facet_node : facet_nodes) + { + if (NULL != facet_node->get_field_data(field)) + { + any_node_has_field = true; + break; + } + } + + if (any_node_has_field) + { + const unsigned field_data_index = my_field_data.size(); + my_field_indices[field.field().mesh_meta_data_ordinal()] = field_data_index; + my_field_data.resize(field_data_index+field_length, 0.0); + + double node_wt_sum = 0.0; + for (unsigned n = 0; n < facet_nodes.size(); ++n) + { + const double * node_field_data = facet_nodes[n]->get_field_data(field); + + if(node_field_data) + { + node_wt_sum += node_wts[n]; + for (unsigned i=0; i nodeSharedProcs; + stk::pack_and_communicate(commSparse,[&]() + { + for (auto entry : proc_prolong_nodes) + { + const ProlongationNodeData & prolongNode = *entry.second; + if (prolongNode.communicate_me_to_all_sharers()) + { + stk::mesh::Entity node = mesh.get_entity(stk::topology::NODE_RANK, prolongNode.entityId()); + mesh.comm_shared_procs(node, nodeSharedProcs); + for (int procId : nodeSharedProcs) + prolongNode.pack_into_buffer(commSparse.send_buffer(procId)); + } + } + }); +} + +static +void pack_facet_prolong_nodes(const stk::mesh::BulkData & mesh, const ProlongFacetVec & proc_prolong_facets, const std::vector & proc_target_bboxes, stk::CommSparse &commSparse) +{ + stk::pack_and_communicate(commSparse,[&]() + { + for ( int procId=0; procIdpack_into_buffer(buffer); + } + }); +} + +static +void receive_and_build_prolong_nodes(const CDMesh & mesh, EntityProlongationNodeMap & proc_prolong_nodes, stk::CommSparse &commSparse) +{ + stk::unpack_communications(commSparse, [&](int procId) + { + stk::CommBuffer & buffer = commSparse.recv_buffer(procId); + + while ( buffer.remaining() ) + { + ProlongationNodeData * node = ProlongationNodeData::unpack_from_buffer( buffer, mesh.stk_meta() ); // This calls new to create a new ProlongationNodeData + EntityProlongationNodeMap::iterator it = proc_prolong_nodes.find(node->entityId()); + if( it == proc_prolong_nodes.end() || it->second == nullptr ) + { + proc_prolong_nodes[node->entityId()] = node; + } + else + { + delete node; + } + } + }); +} + +void ProlongationFacet::communicate_shared_nodes( const CDMesh & mesh, EntityProlongationNodeMap & proc_prolong_nodes ) +{ + stk::CommSparse commSparse(mesh.stk_bulk().parallel()); + pack_nodes_that_need_to_be_communicated_to_sharers(mesh.stk_bulk(), proc_prolong_nodes, commSparse); + receive_and_build_prolong_nodes(mesh, proc_prolong_nodes, commSparse); +} + + +void +ProlongationFacet::communicate_facet_nodes( const CDMesh & mesh, const ProlongFacetVec & proc_prolong_facets, EntityProlongationNodeMap & proc_prolong_nodes, const std::vector & proc_target_bboxes ) +{ /* %TRACE[ON]% */ Trace trace__("krino:ProlongationFacet::communicate_facet_nodes()"); /* %TRACE% */ + const int num_procs = mesh.stk_bulk().parallel_size(); + if ( num_procs == 1 ) return; // Don't talk to yourself, it's embarrassing + + stk::CommSparse commSparse(mesh.stk_bulk().parallel()); + pack_facet_prolong_nodes(mesh.stk_bulk(), proc_prolong_facets, proc_target_bboxes, commSparse); + receive_and_build_prolong_nodes(mesh, proc_prolong_nodes, commSparse); +} + +void +ProlongationNodeData::pack_into_buffer(stk::CommBuffer & b) const +{ + b.pack(my_entityId); + + b.pack(my_coordinates.data(),3); + + const size_t num_fields = my_fields.size(); + b.pack(num_fields); + b.pack(my_fields.data(), my_fields.size()); + + const size_t num_parts = my_ioparts.size(); + b.pack(num_parts); + b.pack(my_ioparts.data(), my_ioparts.size()); + + b.pack(my_field_indices.data(),my_field_indices.size()); + + const size_t num_field_data = my_field_data.size(); + b.pack(num_field_data); + b.pack(my_field_data.data(),my_field_data.size()); +} + +ProlongationNodeData * +ProlongationNodeData::unpack_from_buffer( stk::CommBuffer & b, const stk::mesh::MetaData & stk_meta ) +{ + stk::mesh::EntityId global_id; + b.unpack(global_id); + + Vector3d coords; + b.unpack(coords.data(),3); + + size_t num_fields = 0; + b.unpack(num_fields); + std::vector node_fields(num_fields); + b.unpack(node_fields.data(), num_fields); + + size_t num_parts = 0; + b.unpack(num_parts); + std::vector node_ioparts(num_parts); + b.unpack(node_ioparts.data(), num_parts); + + ProlongationNodeData * node = new ProlongationNodeData(global_id, coords, node_fields, node_ioparts); + + const size_t len_field_indices = stk_meta.get_fields().size(); + std::vector & field_indices = node->get_field_indices(); + field_indices.resize(len_field_indices); + b.unpack(field_indices.data(), len_field_indices); + + size_t num_field_data = 0; + b.unpack(num_field_data); + std::vector & field_data = node->get_field_data(); + field_data.resize(num_field_data); + b.unpack(field_data.data(), num_field_data); + + return node; +} + +std::string +ProlongationData::missing_prolongation_fields_for_entity( const CDMesh & mesh, const stk::mesh::Entity dst ) const +{ + std::string missing_fields; + const FieldSet & ale_prolongation_fields = mesh.get_ale_prolongation_fields(); + for ( auto&& field : ale_prolongation_fields ) + { + if( !field.type_is() || field.entity_rank() != mesh.stk_bulk().entity_rank(dst) ) continue; + + double * val = field_data(field, dst); + if (NULL != val && NULL == get_field_data(field)) + { + missing_fields = missing_fields + " " + field.name(); + } + } + + return missing_fields; +} + +void ProlongationFacet::compute_common_fields() +{ + my_common_fields.clear(); + for (unsigned side_node_index=0; side_node_index & node_fields = my_prolong_nodes[side_node_index]->get_fields(); + if (0 == side_node_index) + { + my_common_fields = node_fields; + } + else + { + std::vector working_set; + working_set.swap(my_common_fields); + std::set_intersection(working_set.begin(),working_set.end(),node_fields.begin(),node_fields.end(),std::back_inserter(my_common_fields)); + } + } +} + +ProlongationFacet::ProlongationFacet(const CDMesh & mesh, stk::mesh::Entity side) +: my_mesh(mesh) +{ + const stk::mesh::BulkData & stk_mesh = my_mesh.stk_bulk(); + + ThrowAssert(stk_mesh.num_elements(side) > 0); + stk::mesh::Entity elem0 = stk_mesh.begin_elements(side)[0]; + const PhaseTag elem0_phase = mesh.determine_entity_phase(elem0); + + const unsigned num_side_nodes = stk_mesh.bucket(side).topology().base().num_nodes(); + const stk::mesh::Entity* side_nodes = stk_mesh.begin_nodes(side); + my_prolong_nodes.resize(num_side_nodes); + + for (unsigned side_node_index=0; side_node_index( my_prolong_nodes[0]->get_coordinates(), my_prolong_nodes[1]->get_coordinates()); + } + else + { + ThrowAssert(3 == my_prolong_nodes.size()); + my_facet = std::make_unique( my_prolong_nodes[0]->get_coordinates(), my_prolong_nodes[1]->get_coordinates(), my_prolong_nodes[2]->get_coordinates()); + } +} + +ProlongationFacet::ProlongationFacet(const CDMesh & mesh, const std::vector & prolong_nodes, const std::vector & common_fields) +: my_mesh (mesh), + my_prolong_nodes(prolong_nodes), + my_common_fields(common_fields) +{ + ThrowAssert((int)my_prolong_nodes.size() == my_mesh.spatial_dim()); + if (2 == my_prolong_nodes.size()) + { + my_facet = std::make_unique( my_prolong_nodes[0]->get_coordinates(), my_prolong_nodes[1]->get_coordinates()); + } + else + { + ThrowAssert(3 == my_prolong_nodes.size()); + my_facet = std::make_unique( my_prolong_nodes[0]->get_coordinates(), my_prolong_nodes[1]->get_coordinates(), my_prolong_nodes[2]->get_coordinates()); + } +} + +void ProlongationFacet::update_prolongation_point_data(const FacetDistanceQuery & dist_query) const +{ + ThrowAssert(&dist_query.facet() == my_facet.get()); + my_prolongation_point_data = std::make_unique(my_mesh, dist_query, my_prolong_nodes); +} + +bool ProlongationFacet::communicate_me(const BoundingBox & proc_target_bbox) const +{ + for(auto && prolong_node : my_prolong_nodes) + { + if (!proc_target_bbox.contains(prolong_node->get_coordinates())) + { + return false; + } + } + return true; +} + +std::set +ProlongationFacet::get_facet_nodes_to_communicate( const ProlongFacetVec & proc_prolong_facets, const BoundingBox & proc_target_bbox ) +{ + std::set procFacetNodes; + + for ( auto && facet : proc_prolong_facets ) + if (facet->communicate_me(proc_target_bbox)) + for (auto node : facet->my_prolong_nodes) + procFacetNodes.insert(node); + + return procFacetNodes; +} + +void +ProlongationFacet::communicate( const CDMesh & mesh, ProlongFacetVec & proc_prolong_facets, EntityProlongationNodeMap & proc_prolong_nodes, const std::vector & proc_target_bboxes ) +{ /* %TRACE[ON]% */ Trace trace__("krino:ProlongationFacet::communicate()"); /* %TRACE% */ + communicate_shared_nodes(mesh, proc_prolong_nodes); + communicate_facet_nodes(mesh, proc_prolong_facets, proc_prolong_nodes, proc_target_bboxes); + communicate_facets(mesh, proc_prolong_facets, proc_target_bboxes); +} + +static +void pack_facets_within_proc_bboxes(const stk::mesh::BulkData & mesh, const ProlongFacetVec & proc_prolong_facets, const std::vector & proc_target_bboxes, stk::CommSparse &commSparse) +{ + stk::pack_and_communicate(commSparse,[&]() + { + for ( int procId=0; procId & proc_target_bboxes ) +{ + stk::CommSparse commSparse(mesh.stk_bulk().parallel()); + pack_facets_within_proc_bboxes(mesh.stk_bulk(), proc_prolong_facets, proc_target_bboxes, commSparse); + receive_and_build_prolong_facets(mesh, proc_prolong_facets, commSparse); +} + +void +ProlongationFacet::pack_into_buffer(stk::CommBuffer & b) const +{ + const size_t num_prolong_nodes = my_prolong_nodes.size(); + b.pack(num_prolong_nodes); + for(auto && prolong_node : my_prolong_nodes) + { + b.pack(prolong_node->entityId()); + } + + b.pack(my_common_fields.size()); + for (unsigned field : my_common_fields) + b.pack(field); +} + +ProlongationFacet * +ProlongationFacet::unpack_from_buffer(const CDMesh & mesh, stk::CommBuffer & b ) +{ + size_t num_prolong_nodes = 0; + b.unpack(num_prolong_nodes); + std::vector prolong_nodes(num_prolong_nodes); + + for(auto && prolong_node : prolong_nodes) + { + stk::mesh::EntityId node_id; + b.unpack(node_id); + prolong_node = mesh.fetch_prolong_node(node_id); + ThrowRequireMsg(prolong_node, "Communication error, missing prolongation node " << node_id << " on processor " << mesh.stk_bulk().parallel_rank()); + } + + size_t num_common_fields = 0; + b.unpack(num_common_fields); + + std::vector common_fields(num_common_fields); + for (size_t ifield=0; ifield +#include +#include + +#include +#include +#include +#include + +namespace krino { + +class ProlongationData { +public: + ProlongationData() {} + ~ProlongationData() {} + + const double * get_field_data( const stk::mesh::FieldBase& state_field ) const { const int field_index = my_field_indices[state_field.mesh_meta_data_ordinal()]; return (field_index < 0) ? nullptr : &my_field_data[field_index]; } + std::string missing_prolongation_fields_for_entity( const CDMesh & mesh, const stk::mesh::Entity dst ) const; + void restore_fields(const stk::mesh::BulkData& stk_mesh, stk::mesh::Entity entity) const; + +protected: + void save_fields(const stk::mesh::BulkData& stk_mesh, stk::mesh::Entity entity); + const std::vector & get_field_data() const { return my_field_data; } + std::vector & get_field_data() { return my_field_data; } + const std::vector & get_field_indices() const { return my_field_indices; } + std::vector & get_field_indices() { return my_field_indices; } + +protected: + mutable std::vector my_field_indices; + mutable std::vector my_field_data; +}; + +class ProlongationPointData : public ProlongationData { +public: + ProlongationPointData(const Vector3d & coordinates) : my_coordinates(coordinates) {} + ProlongationPointData(const CDMesh & mesh, const FacetDistanceQuery & facet_dist_query, const std::vector & facet_nodes); + ~ProlongationPointData() {} + + const Vector3d & get_coordinates() const { return my_coordinates; } + Vector3d & get_coordinates() { return my_coordinates; } + +protected: + Vector3d my_coordinates; +}; + +class ProlongationElementData : public ProlongationData { +public: + ProlongationElementData(const stk::mesh::BulkData& stk_mesh, stk::mesh::Entity element) : ProlongationData() { save_fields(stk_mesh, element); } + ProlongationElementData(const stk::mesh::BulkData& stk_mesh, const std::vector & children_data, const std::vector< std::vector > & children_intg_wts); + ~ProlongationElementData() {} + +private: + //: copy constructor not allowed + ProlongationElementData(const ProlongationElementData & copy); +}; + +class ProlongationNodeData : public ProlongationPointData { +public: + ProlongationNodeData(const CDMesh & mesh, stk::mesh::Entity node, bool communicate_me_to_all_sharers); + ProlongationNodeData(const stk::mesh::EntityId in_entityId, const Vector3d & coordinates, const std::vector & fields, const std::vector & ioparts) + : ProlongationPointData(coordinates), my_entityId(in_entityId), myCommunicateMeToAllSharersFlag(false), my_fields(fields), my_ioparts(ioparts) {} + + ~ProlongationNodeData() {} + + static std::vector get_node_io_parts(const stk::mesh::BulkData& stk_mesh, stk::mesh::Entity entity); + static std::vector get_fields_on_node(const stk::mesh::BulkData& stk_mesh, stk::mesh::Entity entity); + static Vector3d get_node_coordinates(const CDMesh & mesh, stk::mesh::Entity node); + + static ProlongationNodeData * unpack_from_buffer( stk::CommBuffer & b, const stk::mesh::MetaData & stk_meta ); // static method that builds surface from data in buffer for off-processor communication + void pack_into_buffer(stk::CommBuffer & b) const; + bool communicate_me_to_all_sharers() const { return myCommunicateMeToAllSharersFlag; } + + const std::vector & get_fields() const { ThrowRequireMsg(!my_fields.empty(), "Fields not set for prolongation node."); return my_fields; } + const std::vector & get_io_parts() const { ThrowRequireMsg(!my_ioparts.empty(), "IO Parts not set for prolongation node."); return my_ioparts; } + stk::mesh::EntityId entityId() const { return my_entityId; } + +protected: + const stk::mesh::EntityId my_entityId; + const bool myCommunicateMeToAllSharersFlag; + mutable std::vector my_fields; + mutable std::vector my_ioparts; + +private: + //: copy constructor not allowed + ProlongationNodeData(const ProlongationNodeData & copy); +}; + +class ProlongationFacet { +public: + ProlongationFacet(const CDMesh & mesh, const std::vector & prolong_nodes, const std::vector & common_fields); + ProlongationFacet(const CDMesh & mesh, stk::mesh::Entity side); + + static void communicate( const CDMesh & mesh, ProlongFacetVec & proc_prolong_facets, EntityProlongationNodeMap & proc_prolong_nodes, const std::vector & proc_target_bboxes ); + static std::set get_facet_nodes_to_communicate( const ProlongFacetVec & proc_prolong_facets, const BoundingBox & proc_target_bbox ); + + void pack_into_buffer(stk::CommBuffer & b) const; + static ProlongationFacet * unpack_from_buffer( const CDMesh & mesh, stk::CommBuffer & b ); // static method that builds surface from data in buffer for off-processor communication + void compute_common_fields(); + + static const BoundingBox & get_bounding_box(const ProlongationFacet * prolong_facet) { return prolong_facet->get_facet()->bounding_box(); } + + Facet * get_facet() const { return my_facet.get(); } + const std::vector & get_common_fields() const { return my_common_fields; } + const ProlongationPointData * get_prolongation_point_data(const FacetDistanceQuery & dist_query) const { update_prolongation_point_data(dist_query); return my_prolongation_point_data.get(); } + const std::vector & get_prolongation_nodes() const { return my_prolong_nodes; } + bool communicate_me(const BoundingBox & proc_target_bbox) const; + +protected: + void update_prolongation_point_data(const FacetDistanceQuery & dist_query) const; + +protected: + const CDMesh & my_mesh; + std::unique_ptr my_facet; + mutable std::unique_ptr my_prolongation_point_data; + mutable std::vector my_prolong_nodes; + mutable std::vector my_common_fields; + +private: + //: copy constructor not allowed + ProlongationFacet(const ProlongationFacet & copy); + static void communicate_shared_nodes( const CDMesh & mesh, EntityProlongationNodeMap & proc_prolong_nodes ); + static void communicate_facets( const CDMesh & mesh, ProlongFacetVec & proc_prolong_facets, const std::vector & proc_target_bboxes ); + static void communicate_facet_nodes( const CDMesh & mesh, const ProlongFacetVec & proc_prolong_facets, EntityProlongationNodeMap & proc_prolong_nodes, const std::vector & proc_target_bboxes ); +}; + +} // namespace krino + +#endif // Akri_ProlongationData_h diff --git a/packages/krino/krino/krino_lib/Akri_QualityMetric.cpp b/packages/krino/krino/krino_lib/Akri_QualityMetric.cpp new file mode 100644 index 000000000000..fcc6a98472ce --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_QualityMetric.cpp @@ -0,0 +1,127 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +namespace krino +{ + +double calculate_tet_volume_using_sides(const stk::math::Vector3d &side0, const stk::math::Vector3d &side2, const stk::math::Vector3d &side3) +{ + return Dot(side3, Cross(side2, side0))/6.0; +} + +double MeanRatioQualityMetric::tet_mean_ratio(const std::vector &nodeLocations) +{ + ThrowAssert(nodeLocations.size() == 4 || nodeLocations.size() == 10); + + const stk::math::Vector3d side0 = nodeLocations[1] - nodeLocations[0]; + const stk::math::Vector3d side2 = nodeLocations[0] - nodeLocations[2]; + const stk::math::Vector3d side3 = nodeLocations[3] - nodeLocations[0]; + + const double volumeMin = 1.0E-30; + const double tetVolume = calculate_tet_volume_using_sides(side0, side2, side3); + if( std::abs( tetVolume ) < volumeMin ) + return 0.0; + + const stk::math::Vector3d side1 = nodeLocations[2] - nodeLocations[1]; + const stk::math::Vector3d side4 = nodeLocations[3] - nodeLocations[1]; + const stk::math::Vector3d side5 = nodeLocations[3] - nodeLocations[2]; + + const double side0_length_squared = side0.length_squared(); + const double side1_length_squared = side1.length_squared(); + const double side2_length_squared = side2.length_squared(); + const double side3_length_squared = side3.length_squared(); + const double side4_length_squared = side4.length_squared(); + const double side5_length_squared = side5.length_squared(); + + const int sign = tetVolume < 0. ? -1 : 1; + return sign * 12. * std::pow(3.*std::abs(tetVolume), 2./3.) / (side0_length_squared + side1_length_squared + side2_length_squared + side3_length_squared + side4_length_squared + side5_length_squared); +} + +double ScaledJacobianQualityMetric::tet_scaled_jacobian(const std::vector &nodeLocations) +{ + ThrowAssert(nodeLocations.size() == 4 || nodeLocations.size() == 10); + + const stk::math::Vector3d side0 = nodeLocations[1] - nodeLocations[0]; + const stk::math::Vector3d side1 = nodeLocations[2] - nodeLocations[1]; + const stk::math::Vector3d side2 = nodeLocations[0] - nodeLocations[2]; + const stk::math::Vector3d side3 = nodeLocations[3] - nodeLocations[0]; + const stk::math::Vector3d side4 = nodeLocations[3] - nodeLocations[1]; + const stk::math::Vector3d side5 = nodeLocations[3] - nodeLocations[2]; + + const double jacobi = Dot(side3, Cross(side2, side0)); + + // products of lengths squared of each edge attached to a node. + const double side0_length_squared = side0.length_squared(); + const double side1_length_squared = side1.length_squared(); + const double side2_length_squared = side2.length_squared(); + const double side3_length_squared = side3.length_squared(); + const double side4_length_squared = side4.length_squared(); + const double side5_length_squared = side5.length_squared(); + + const double length_squared[4] = { + side0_length_squared * side2_length_squared * side3_length_squared, + side0_length_squared * side1_length_squared * side4_length_squared, + side1_length_squared * side2_length_squared * side5_length_squared, + side3_length_squared * side4_length_squared * side5_length_squared + }; + int which_node = 0; + if(length_squared[1] > length_squared[which_node]) + which_node = 1; + if(length_squared[2] > length_squared[which_node]) + which_node = 2; + if(length_squared[3] > length_squared[which_node]) + which_node = 3; + + double length_product = std::sqrt( length_squared[which_node] ); + if(length_product < std::abs(jacobi)) + length_product = std::abs(jacobi); + + const double lengthMin = 1.0E-30; + if( length_product < lengthMin ) + return 0.0; + + static const double root_of_2 = std::sqrt(2.0); + return root_of_2 * jacobi / length_product; +} + +double ScaledJacobianQualityMetric::tri2d_scaled_jacobian(const std::vector &nodeLocations) +{ + const double absScaledJacobian = tri3d_scaled_jacobian(nodeLocations); + const double normalZ = + (nodeLocations[1][0]-nodeLocations[0][0])*(nodeLocations[2][1]-nodeLocations[0][1]) - + (nodeLocations[1][1]-nodeLocations[0][1])*(nodeLocations[2][0]-nodeLocations[0][0]); + return (normalZ > 0.) ? absScaledJacobian : -absScaledJacobian; +} + +double ScaledJacobianQualityMetric::tri3d_scaled_jacobian(const std::vector &nodeLocations) +{ + ThrowAssert(nodeLocations.size() == 3 || nodeLocations.size() == 6); + + const stk::math::Vector3d edge0 = nodeLocations[1] - nodeLocations[0]; + const stk::math::Vector3d edge1 = nodeLocations[2] - nodeLocations[0]; + const stk::math::Vector3d edge2 = nodeLocations[2] - nodeLocations[1]; + + const double lenSqr0 = edge0.length_squared(); + const double lenSqr1 = edge1.length_squared(); + const double lenSqr2 = edge2.length_squared(); + + const double maxEdgeLengthProduct = std::sqrt( std::max(lenSqr0*lenSqr1, std::max(lenSqr1*lenSqr2, lenSqr0*lenSqr2)) ); + + const double lengthMin = 1.0E-30; + if( maxEdgeLengthProduct < lengthMin ) + return 0.0; + + static const double two_over_root_of_3 = 2./sqrt(3.0); + const double jacobian = Cross(edge0, edge1).length(); + return jacobian*two_over_root_of_3/maxEdgeLengthProduct; +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_QualityMetric.hpp b/packages/krino/krino/krino_lib/Akri_QualityMetric.hpp new file mode 100644 index 000000000000..02599303f86f --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_QualityMetric.hpp @@ -0,0 +1,76 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_QUALITY_METRIC_H +#define AKRI_QUALITY_METRIC_H +#include +#include + +namespace krino +{ + +class QualityMetric +{ +public: + virtual ~QualityMetric() {} + + virtual bool is_first_quality_metric_better_than_second(const double firstValue, const double secondValue) const = 0; + virtual double get_element_quality_metric(const std::vector &nodeLocations) const = 0; + virtual double get_best_value_for_metric() const = 0; + virtual double get_acceptable_value_for_metric() const = 0; +}; + +class MeanRatioQualityMetric : public QualityMetric +{ +public: + virtual ~MeanRatioQualityMetric() {} + + virtual double get_best_value_for_metric() const override { return 1.0; } + virtual bool is_first_quality_metric_better_than_second(const double firstValue, const double secondValue) const override + { + return static_cast(firstValue) > static_cast(secondValue); + } + + double get_element_quality_metric(const std::vector &nodeLocations) const override + { + return tet_mean_ratio(nodeLocations); + } + + static double tet_mean_ratio(const std::vector &nodeLocations); + + virtual double get_acceptable_value_for_metric() const override { return 0.2; } +}; + +class ScaledJacobianQualityMetric : public QualityMetric +{ +public: + ScaledJacobianQualityMetric() = default; + virtual ~ScaledJacobianQualityMetric() {} + + virtual double get_best_value_for_metric() const override { return 1.0; } + virtual bool is_first_quality_metric_better_than_second( + const double firstValue, const double secondValue) const override + { + return static_cast(firstValue) > static_cast(secondValue); + } + + double get_element_quality_metric(const std::vector &nodeLocations) const override + { + return (nodeLocations.size() == 4 || nodeLocations.size() == 10) ? tet_scaled_jacobian(nodeLocations) : tri2d_scaled_jacobian(nodeLocations); + } + + static double tet_scaled_jacobian(const std::vector &nodeLocations); + static double tri2d_scaled_jacobian(const std::vector &nodeLocations); + static double tri3d_scaled_jacobian(const std::vector &nodeLocations); + + virtual double get_acceptable_value_for_metric() const override { return 0.1; } +}; + +} + +#endif diff --git a/packages/krino/krino/krino_lib/Akri_RegionInterface.cpp b/packages/krino/krino/krino_lib/Akri_RegionInterface.cpp new file mode 100644 index 000000000000..f04136783043 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_RegionInterface.cpp @@ -0,0 +1,15 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +namespace krino{ + +std::unique_ptr RegionInterface::the_currently_parsed_region; + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_RegionInterface.hpp b/packages/krino/krino/krino_lib/Akri_RegionInterface.hpp new file mode 100644 index 000000000000..09248bed11a0 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_RegionInterface.hpp @@ -0,0 +1,65 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_RegionInterface_h +#define Akri_RegionInterface_h + +#include +#include + +namespace Ioss { class Region; } +namespace stk { namespace diag { class Timer; } } +namespace stk { namespace mesh { class MetaData; } } + +namespace krino { + +class RegionInterface { + + public: + static RegionInterface & set_currently_parsed_region(stk::mesh::MetaData & meta, + const std::string & region_name, + stk::diag::Timer & region_timer, + const std::string & name_of_input_mesh, + Ioss::Region * ioss_region) + { + the_currently_parsed_region = std::make_unique(meta, region_name, region_timer, name_of_input_mesh, ioss_region); + return *the_currently_parsed_region; + } + static void clear_currently_parsed_region() { the_currently_parsed_region.reset(); } + static RegionInterface & get_currently_parsed_region() { ThrowAssert(nullptr != the_currently_parsed_region); return *the_currently_parsed_region; } + + stk::mesh::MetaData & get_stk_mesh_meta_data() { return my_meta; } + const std::string & name() { return my_region_name; } + stk::diag::Timer & getRegionTimer() const { return my_region_timer; } + const std::string & name_of_input_mesh() const { return my_name_of_input_mesh; } + Ioss::Region * get_input_io_region() { return my_ioss_region; } + + // must be public to be used by make_unique + RegionInterface(stk::mesh::MetaData & meta, + const std::string & region_name, + stk::diag::Timer & region_timer, + const std::string & input_mesh, + Ioss::Region * ioss_region) + : my_meta(meta), + my_region_name(region_name), + my_region_timer(region_timer), + my_name_of_input_mesh(input_mesh), + my_ioss_region(ioss_region) {} + +private: + static std::unique_ptr the_currently_parsed_region; + stk::mesh::MetaData & my_meta; + const std::string my_region_name; + stk::diag::Timer & my_region_timer; + const std::string my_name_of_input_mesh; + Ioss::Region * my_ioss_region; +}; + +} // end krino namespace + +#endif /* Akri_RegionInterface_h */ diff --git a/packages/krino/krino/krino_lib/Akri_ReportHandler.hpp b/packages/krino/krino/krino_lib/Akri_ReportHandler.hpp new file mode 100644 index 000000000000..9c3c57c4f4d9 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_ReportHandler.hpp @@ -0,0 +1,21 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_REPORTHANDLER_H_ +#define KRINO_INCLUDE_AKRI_REPORTHANDLER_H_ + +#include +#include + +#define ParallelThrowRequire(parallel,expr) ThrowRequire(stk::is_true_on_all_procs(parallel,expr)) +#define ParallelThrowRequireMsg(parallel,expr,message) ThrowRequireMsg(stk::is_true_on_all_procs(parallel,expr),message) +#define ParallelThrowAssert(parallel,expr) ThrowAssert(stk::is_true_on_all_procs(parallel,expr)) +#define ParallelThrowAssertMsg(parallel,expr,message) ThrowAssertMsg(stk::is_true_on_all_procs(parallel,expr),message) + + +#endif /* KRINO_INCLUDE_AKRI_REPORTHANDLER_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_SearchTree.cpp b/packages/krino/krino/krino_lib/Akri_SearchTree.cpp new file mode 100644 index 000000000000..ffd80f22bb40 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SearchTree.cpp @@ -0,0 +1,257 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include + +#include + +namespace krino{ + +template +SearchTree::SearchTree( std::vector & entities ) +{ /* %TRACE[ON]% */ Trace trace__("krino::SearchTree::SearchTree( std::vector & entities )"); /* %TRACE% */ + + if (!entities.empty()) + { + my_nodes.resize(2*entities.size() - 1); + const size_t tree_size = build( entities.begin(), entities.end() ); + ThrowRequire(tree_size == my_nodes.size()); + } +} + +template +SearchTree::SearchTree( const std::vector & entities, const std::function & get_bbox ) +{ /* %TRACE[ON]% */ Trace trace__("krino::SearchTree::SearchTree( EntityVec & entities, const std::function & get_bbox )"); /* %TRACE% */ + + if (!entities.empty()) + { + std::vector bbox_entities; + bbox_entities.reserve(entities.size()); + for (auto && entity : entities) + { + bbox_entities.emplace_back(get_bbox(entity), entity); + } + my_nodes.resize(2*entities.size() - 1); + const size_t tree_size = build( bbox_entities.begin(), bbox_entities.end() ); + ThrowRequire(tree_size == my_nodes.size()); + } +} + +template +void +SearchTree::find_closest_entities( const Vector3d & search_point, std::vector & return_vec, const Real max_search_radius ) +{ + return_vec.clear(); + + // Algorithm requires tol > std::numeric_limits::epsilon(), here just pick multiplier + const Real tol = 100.*std::numeric_limits::epsilon(); + + // if there are no entities at all return empty list + if ( empty() ) return; + + const VecType search_pt(search_point.data()); + + // first find upper bound for distance + // the very first estimate for this is the upper bound for the base of the tree + Real upperBnd2 = my_nodes[0].boxUpperBnd2( search_pt ); + Real max_search_radius2 = max_search_radius * max_search_radius; + + if ( upperBnd2 > max_search_radius2 && max_search_radius2 > 0.0 ) + { + upperBnd2 = max_search_radius2; + } + + // now descend relevant parts of tree updating the bounds as we go + Real lowerBnd2 = upperBnd2; + treeBounds(search_pt, lowerBnd2, upperBnd2); + + // exit now if no entities within narrow_band + if ( lowerBnd2 > max_search_radius2 && max_search_radius2 > 0.0 ) + { + return; + } + + // with the upper bound known, find all entities within the tolerance + find_closest_entities( search_pt, upperBnd2, tol, return_vec ); + + return; +} + +template +void +SearchTree::find_closest_entities( + const VecType & search_point, + const Real maxDist2, + const Real tol, + std::vector & nearest, + const size_t index) +{ /* %TRACE% */ /* %TRACE% */ + const SearchTreeNode & node = my_nodes[index]; + if ( node.have_children() ) + { + const size_t Lindex = index + 1; + const Real L_lowerBnd2 = my_nodes[Lindex].boxLowerBnd2( search_point ); + const Real L_eps = tol*(maxDist2+my_nodes[Lindex].boxSize2()); + if ( L_lowerBnd2 < maxDist2+L_eps ) + find_closest_entities( search_point, maxDist2, tol, nearest, Lindex ); + + const size_t Rindex = node.get_right_index(); + const Real R_lowerBnd2 = my_nodes[Rindex].boxLowerBnd2( search_point ); + const Real R_eps = tol*(maxDist2+my_nodes[Rindex].boxSize2()); + if ( R_lowerBnd2 < maxDist2+R_eps ) + find_closest_entities( search_point, maxDist2, tol, nearest, Rindex ); + } + else + { + EntityType entity = node.get_leaf_entity(); + nearest.push_back( entity ); + } +} + +template +void +SearchTree::treeBounds( const VecType & search_point, + Real & lowerBnd2, + Real & upperBnd2, + const size_t index) +{ /* %TRACE% */ /* %TRACE% */ + const SearchTreeNode & node = my_nodes[index]; + if (!node.have_children()) + { + if (0 == index) + { + // Special case of a single node. Otherwise, this node will already have been handled by parent. + compute_and_update_bounds(search_point, lowerBnd2, upperBnd2, index); + } + return; + } + + const size_t Lindex = index + 1; + const Real L_lowerBnd2 = compute_and_update_bounds(search_point, lowerBnd2, upperBnd2, Lindex); + + const size_t Rindex = node.get_right_index(); + const Real R_lowerBnd2 = compute_and_update_bounds(search_point, lowerBnd2, upperBnd2, Rindex); + + if ( L_lowerBnd2 < R_lowerBnd2 ) + { + if ( L_lowerBnd2 < upperBnd2 ) + treeBounds( search_point, lowerBnd2, upperBnd2, Lindex ); + if ( R_lowerBnd2 < upperBnd2 ) + treeBounds( search_point, lowerBnd2, upperBnd2, Rindex ); + } + else + { + if ( R_lowerBnd2 < upperBnd2 ) + treeBounds( search_point, lowerBnd2, upperBnd2, Rindex ); + if ( L_lowerBnd2 < upperBnd2 ) + treeBounds( search_point, lowerBnd2, upperBnd2, Lindex ); + } +} + +template +size_t +SearchTree::build( typename std::vector::iterator entity_begin, typename std::vector::iterator entity_end, const size_t index ) +{ + const size_t entity_size = std::distance(entity_begin, entity_end); + + // determine bounding box + BoundingBox & node_bbox = my_nodes[index].bounding_box(); + for ( auto entity_it = entity_begin; entity_it != entity_end; ++entity_it ) + { + const BoundingBox & entity_bbox = entity_it->first; + node_bbox.accommodate( entity_bbox ); + } + + if ( entity_size > 1 ) + { + // determine axis to split tree on + int max_spread_dim = node_bbox.max_span_direction(); + + auto compare = [max_spread_dim](const BoundingBoxEntity & lhs, const BoundingBoxEntity & rhs) { return lhs.first.center()[max_spread_dim] < rhs.first.center()[max_spread_dim]; }; + const size_t mid_pt = entity_size / 2; + + // This works around a bug in some versions of nth_element +#ifdef CRAY_LWK + std::partial_sort( entity_begin, entity_begin + mid_pt, entity_end, compare ); +#else + std::nth_element( entity_begin, entity_begin + mid_pt, entity_end, compare ); +#endif + + const size_t Lindex = index + 1; + const size_t Rindex = build( entity_begin, entity_begin + mid_pt, Lindex ); + my_nodes[index].set_right_index(Rindex); + const size_t next_index = build( entity_begin + mid_pt, entity_end, Rindex ); + return next_index; + } + else + { + ThrowAssert(1 == entity_size); + my_nodes[index].set_leaf_entity(entity_begin->second); + return index + 1; + } +} + +template +void +SearchTree::get_intersecting_entities( const BoundingBoxType& bbox, std::vector& return_vec, const size_t index ) +{ + const SearchTreeNode & node = my_nodes[index]; + if (!node.bounding_box().intersects(bbox)) return; + + if ( node.have_children() ) + { + if ( bbox.contains(node.bounding_box()) ) + { + add_descendants(return_vec, index); + } + else + { + const size_t Lindex = index + 1; + get_intersecting_entities( bbox, return_vec, Lindex ); + + const size_t Rindex = node.get_right_index(); + get_intersecting_entities( bbox, return_vec, Rindex ); + } + } + else + { + EntityType entity = node.get_leaf_entity(); + return_vec.push_back( entity ); + } +} + +template +void +SearchTree::add_descendants( std::vector& return_vec, const size_t index ) +{ + const SearchTreeNode & node = my_nodes[index]; + + if ( node.have_children() ) + { + const size_t Lindex = index + 1; + add_descendants( return_vec, Lindex ); + + const size_t Rindex = node.get_right_index(); + add_descendants( return_vec, Rindex ); + } + else + { + EntityType entity = node.get_leaf_entity(); + return_vec.push_back( entity ); + } +} + +// Explicit template instantiation +template class SearchTree; +template class SearchTree; + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_SearchTree.hpp b/packages/krino/krino/krino_lib/Akri_SearchTree.hpp new file mode 100644 index 000000000000..4e7b5cf3094f --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SearchTree.hpp @@ -0,0 +1,119 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_SearchTree_h +#define Akri_SearchTree_h + +#include + +#include +#include + +namespace krino { + +template +class SearchTreeNode { +public: + typedef ENTITY EntityType; + typedef BoundingBox BoundingBoxType; + typedef BoundingBoxType::Real Real; + typedef BoundingBoxType::VecType VecType; + + SearchTreeNode() : m_entity(0), m_right_index(0) {} + + const BoundingBoxType & bounding_box() const { return m_bbox; } + BoundingBoxType & bounding_box() { return m_bbox; } + Real boxLowerBnd2( const VecType & x ) const { return ( m_bbox.SqrDistLowerBnd(x) ); } + Real boxUpperBnd2( const VecType & x ) const { return ( m_bbox.SqrDistUpperBnd(x) ); } + Real boxSize2() const { return m_bbox.SqrSize(); } + + bool have_children() const { return (0 != m_right_index); } + void set_right_index(size_t right_index) { m_right_index = right_index; } + size_t get_right_index() const { return m_right_index; } + void set_leaf_entity(EntityType leaf_entity) { m_entity = leaf_entity; } + EntityType get_leaf_entity() const { return m_entity; } + +private: + BoundingBoxType m_bbox; + EntityType m_entity; + size_t m_right_index; +}; + +template +class SearchTree { +public: + typedef ENTITY EntityType; + typedef BoundingBox BoundingBoxType; + typedef std::pair BoundingBoxEntity; + typedef BoundingBoxType::Real Real; + typedef BoundingBoxType::VecType VecType; + + SearchTree( std::vector & entities ); // vector is modified by sorting + SearchTree( const std::vector & entities, const std::function & get_bbox ); + SearchTree() = delete; + + // All entities within the max_search_radius that may be the closest to the given point will be returned. + // If max_search_radius = 0, then all entities that may be the closest to the given point will be returned. + void find_closest_entities( const Vector3d& search_point, std::vector& return_vec, const Real max_search_radius = 0.0 ); + + void get_intersecting_entities( const BoundingBoxType& bbox, std::vector& return_vec) + { + return_vec.clear(); + if (!empty()) + { + get_intersecting_entities( bbox, return_vec, 0); + } + } + + bool empty() const { return my_nodes.empty(); } + size_t storage_size() const { return my_nodes.size() * sizeof(SearchTreeNode); } + +private: + size_t build( typename std::vector::iterator entity_begin, typename std::vector::iterator entity_end, const size_t index = 0 ); + double compute_and_update_bounds( const VecType & x, + Real & lowerBnd2, + Real & upperBnd2, + const size_t index = 0 ); + void treeBounds( const VecType & x, + Real & lowerBnd2, + Real & upperBnd2, + const size_t index = 0 ); + void find_closest_entities( const VecType & x, + const Real maxDist2, + const Real tol, + std::vector & nearest, + const size_t index = 0 ); + void get_intersecting_entities( const BoundingBoxType& bbox, std::vector& return_vec, const size_t index ); + void add_descendants( std::vector& return_vec, const size_t index ); +private: + std::vector> my_nodes; +}; + +template +inline double +SearchTree::compute_and_update_bounds( const VecType & search_point, + Real & lowerBnd2, + Real & upperBnd2, + const size_t index) +{ /* %TRACE% */ /* %TRACE% */ + const SearchTreeNode & node = my_nodes[index]; + const Real node_lowerBnd2 = node.boxLowerBnd2( search_point ); + if ( node_lowerBnd2 < upperBnd2 ) + { + const Real node_upperBnd2 = node.boxUpperBnd2( search_point ); + if ( node_upperBnd2 < upperBnd2 ) + upperBnd2 = node_upperBnd2; + if (!node.have_children() && node_lowerBnd2 < lowerBnd2) + lowerBnd2 = node_lowerBnd2; + } + return node_lowerBnd2; +} + +} // namespace krino + +#endif // Akri_SearchTree_h diff --git a/packages/krino/krino/krino_lib/Akri_Segment.hpp b/packages/krino/krino/krino_lib/Akri_Segment.hpp new file mode 100644 index 000000000000..df9a117e7c5d --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Segment.hpp @@ -0,0 +1,83 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Segment_h +#define Akri_Segment_h + +#include +#include +#include +#include + +namespace krino { + +template +class Segment3 { + public: + typedef REAL Real; + Segment3() : nodes{} {} ///< Default all coords zero + Segment3(const Vec& n0, const Vec& n1) : nodes{{n0, n1}} {} ///< Explicit set two end points + Segment3(MemberInit type) {/* type == NONE */} + + Real Length() const { return (nodes[1]-nodes[0]).length(); } + Real LengthSquared() const { return (nodes[1]-nodes[0]).length_squared(); } + + const Vec& GetNode(const int index) const { return nodes[index]; } + Vec& GetNode(const int index) { return nodes[index]; } + + const std::array,2>& GetNodes() const { return nodes; } + std::array,2>& GetNodes() { return nodes; } + + // + // Find the closest point projection between point and the face. + // + Real closest_projection(const Vec &point) const { + Vec edge_dir(nodes[1]-nodes[0]); + Real dotValA = Dot(edge_dir,(point-nodes[0])); + if(dotValA <= 0.0) { return 0.0; } + Real dotValB = Dot(edge_dir,(point-nodes[1])); + if(dotValB >= 0.0) { return 1.0; } + Real lenSquared = edge_dir.length_squared(); + if(lenSquared == 0.0) { return 0.0; } + return dotValA / lenSquared; + } + Real closest_projection(const Vec &point, Vec &proj_point) const { + const Real location = closest_projection(point); + proj_point = (1.0-location)*GetNode(0) + location*GetNode(1); + return location; + } + + Real DistanceSquared(const Vec& x, Real & parametric_coord) const { + Vector3d closest_pt(MemberInit::NONE); + parametric_coord = closest_projection(x, closest_pt); + return (x - closest_pt).length_squared(); + } + Real DistanceSquared(const Vec& x) const { + Vector3d closest_pt(MemberInit::NONE); + closest_projection(x, closest_pt); + return (x - closest_pt).length_squared(); + } + + friend std::ostream& operator<<( std::ostream& out, const Segment3& seg ) + { + out << "Segment3:" + << " Node0= " << seg.GetNode(0)[0] << ", " << seg.GetNode(0)[1] << ", " << seg.GetNode(0)[2] + << " Node1= " << seg.GetNode(1)[0] << ", " << seg.GetNode(1)[1] << ", " << seg.GetNode(1)[2] + << "\n"; + return out; + } + + private: + std::array,2> nodes; +}; + +typedef Segment3 Segment3d; + +} // namespace krino + +#endif // Akri_Segment_h diff --git a/packages/krino/krino/krino_lib/Akri_Snap.cpp b/packages/krino/krino/krino_lib/Akri_Snap.cpp new file mode 100644 index 000000000000..d182524bf69a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Snap.cpp @@ -0,0 +1,633 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" + +namespace krino +{ + +static stk::math::Vector3d compute_snap_location(const std::vector & nodeLocations, const std::vector & weights) +{ + stk::math::Vector3d snapLocation{stk::math::Vector3d::ZERO}; + for (size_t i=0; i & nodes, std::vector & nodeLocations) +{ + nodeLocations.clear(); + for (auto node : nodes) + nodeLocations.emplace_back(field_data(coordsField, node), dim); +} + +static void fill_global_ids_of_elements_using_node(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + stk::mesh::Entity node, + std::vector & globalIdsOfSnapNodeElems) +{ + globalIdsOfSnapNodeElems.clear(); + for (auto elem : StkMeshEntities{mesh.begin_elements(node), mesh.end_elements(node)}) + if (elementSelector(mesh.bucket(elem))) + globalIdsOfSnapNodeElems.push_back(mesh.identifier(elem)); +} + +static double compute_quality_if_node_is_snapped_terminating_early_if_below_threshold(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const FieldRef coordsField, + stk::mesh::Entity node, + const stk::math::Vector3d & snapLocation, + const QualityMetric &qualityMetric, + const double qualityThreshold) +{ + const int dim = mesh.mesh_meta_data().spatial_dimension(); + + double qualityAfterSnap = qualityMetric.get_best_value_for_metric(); + std::vector nodeLocations; + + for (auto elem : StkMeshEntities{mesh.begin_elements(node), mesh.end_elements(node)}) + { + if (elementSelector(mesh.bucket(elem))) + { + nodeLocations.clear(); + for (auto elemNode : StkMeshEntities{mesh.begin_nodes(elem), mesh.end_nodes(elem)}) + { + if (elemNode == node) + nodeLocations.push_back(snapLocation); + else + nodeLocations.emplace_back(field_data(coordsField, elemNode), dim); + } + + const double elemQualityAfterSnap = qualityMetric.get_element_quality_metric(nodeLocations); + + if (qualityMetric.is_first_quality_metric_better_than_second(qualityAfterSnap, elemQualityAfterSnap)) + { + qualityAfterSnap = elemQualityAfterSnap; + if (qualityMetric.is_first_quality_metric_better_than_second(qualityThreshold, qualityAfterSnap)) + return qualityAfterSnap; + } + } + } + return qualityAfterSnap; +} + +static stk::math::Vector3d compute_intersection_point_location(const int dim, const FieldRef coordsField, const IntersectionPoint & intersectionPoint) +{ + std::vector intPtNodeLocations; + fill_node_locations(dim, coordsField, intersectionPoint.get_nodes(), intPtNodeLocations); + return compute_snap_location(intPtNodeLocations, intersectionPoint.get_weights()); +} + +static bool element_has_all_nodes(const std::vector & elemNodes, const std::vector & nodesToFind) +{ + for (auto && nodeToFind : nodesToFind) + if (std::find(elemNodes.begin(), elemNodes.end(), nodeToFind) == elemNodes.end()) + return false; + return true; +} + +static double estimate_quality_of_cutting_intersection_points(const stk::mesh::BulkData & mesh, + const FieldRef coordsField, + const std::vector & elemNodes, + const std::vector & elemNodeCoords, + const std::vector intersectionPointIndices, + const std::vector & intersectionPoints, + const QualityMetric &qualityMetric) +{ + if (intersectionPointIndices.empty()) + return std::max(0., qualityMetric.get_element_quality_metric(elemNodeCoords)); + + // apply the front intersection point to element and recursively call with remaining intersection points + const IntersectionPoint & intPtToApply = intersectionPoints[*intersectionPointIndices.begin()]; + const std::vector remainingIntersectionPointIndices(intersectionPointIndices.begin()+1, intersectionPointIndices.end()); + + const stk::math::Vector3d intPtLocation = compute_intersection_point_location(mesh.mesh_meta_data().spatial_dimension(), coordsField, intPtToApply); + + const auto & intPtNodes = intPtToApply.get_nodes(); + double qualityAfterCut = qualityMetric.get_best_value_for_metric(); + if (element_has_all_nodes(elemNodes, intPtNodes)) + { + std::vector cutElemNodes; + std::vector cutElemNodeCoords; + + for (auto intPtNode : intPtNodes) + { + cutElemNodes.clear(); + cutElemNodeCoords.clear(); + for (size_t nodeIndex=0; nodeIndex & interpNodes) +{ + for (auto && interpNode : interpNodes) + if (interpNode != node && !parts_are_compatible_for_snapping_when_ignoring_phase(mesh, auxMeta, phaseSupport, node, interpNode)) + return false; + return true; +} + +static double get_node_intersection_point_weight(const IntersectionPoint & intersectionPoint, stk::mesh::Entity node) +{ + const std::vector & nodes = intersectionPoint.get_nodes(); + const auto iter = std::find(nodes.begin(), nodes.end(), node); + ThrowRequire(iter != nodes.end()); + const auto index = std::distance(nodes.begin(), iter); + return intersectionPoint.get_weights()[index]; +} + +std::vector get_sorted_node_ids(const stk::mesh::BulkData & mesh, const std::vector & nodes) +{ + std::vector nodeIds; + nodeIds.reserve(nodes.size()); + for (auto && node : nodes) nodeIds.push_back(mesh.identifier(node)); + std::sort(nodeIds.begin(), nodeIds.end()); + return nodeIds; +} + +static void sort_intersection_points_for_cutting(const stk::mesh::BulkData & mesh, + const FieldRef coordsField, + const std::vector & intersectionPoints, + const stk::mesh::Entity node, + const bool globalIDsAreParallelConsistent, + std::vector & sortedIntersectionPointIndices) +{ + // This sorter is designed to match the priority used by the cutting algorithm with CUT_QUADS_BY_MINIMIZING_ANGLES, especially in 3D + auto sorter = [&intersectionPoints, &mesh, &coordsField, node, globalIDsAreParallelConsistent](const size_t intPtIndex0, const size_t intPtIndex1) + { + const IntersectionPoint & intPt0 = intersectionPoints[intPtIndex0]; + const IntersectionPoint & intPt1 = intersectionPoints[intPtIndex1]; + const size_t numDomains0 = intPt0.get_sorted_domains().size(); + const size_t numDomains1 = intPt1.get_sorted_domains().size(); + if (numDomains0 != numDomains1) + return numDomains0 > numDomains1; + // reduce precision to float to handle "ties" + const float wt0 = get_node_intersection_point_weight(intPt0, node); + const float wt1 = get_node_intersection_point_weight(intPt1, node); + if (wt0 != wt1) + return wt0 < wt1; + + if (globalIDsAreParallelConsistent) + { + const std::vector sortedNodes0 = get_sorted_node_ids(mesh, intPt0.get_nodes()); + const std::vector sortedNodes1 = get_sorted_node_ids(mesh, intPt1.get_nodes()); + return sortedNodes1 < sortedNodes0; + } + + const stk::math::Vector3d x0 = compute_intersection_point_location(mesh.mesh_meta_data().spatial_dimension(), coordsField, intPt0); + const stk::math::Vector3d x1 = compute_intersection_point_location(mesh.mesh_meta_data().spatial_dimension(), coordsField, intPt1); + return is_less_than_in_x_then_y_then_z(x0, x1); + }; + std::sort(sortedIntersectionPointIndices.begin(), sortedIntersectionPointIndices.end(), sorter); +} + +static void fill_sorted_intersection_point_indices_for_node_for_domains(const stk::mesh::BulkData & mesh, + const FieldRef coordsField, + const std::vector & intersectionPoints, + const std::vector & candidatesIntersectionPointIndices, + const stk::mesh::Entity node, + const std::vector & domains, + const bool globalIDsAreParallelConsistent, + std::vector & sortedIntersectionPointIndices) +{ + sortedIntersectionPointIndices.clear(); + for (auto && intPtIndex : candidatesIntersectionPointIndices) + { + if (first_sorted_vector_of_domains_contains_all_domains_in_second_vector(domains, intersectionPoints[intPtIndex].get_sorted_domains())) + sortedIntersectionPointIndices.push_back(intPtIndex); + } + + sort_intersection_points_for_cutting(mesh, coordsField, intersectionPoints, node, globalIDsAreParallelConsistent, sortedIntersectionPointIndices); +} + +static std::set get_intersected_elements(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const std::vector & intersectionPoints, + const std::vector & intersectionPointIndices) +{ + std::vector cutElems; + std::set intersectedElements; + for (size_t intPtIndex : intersectionPointIndices) + { + stk::mesh::get_entities_through_relations(mesh, intersectionPoints[intPtIndex].get_nodes(), stk::topology::ELEMENT_RANK, cutElems); + for (auto && cutElem : cutElems) + if (elementSelector(mesh.bucket(cutElem))) + intersectedElements.insert(cutElem); + } + return intersectedElements; +} + +static std::map> get_node_to_intersection_point_indices(const stk::mesh::BulkData & mesh, + const std::vector & intersectionPoints) +{ + std::map> nodeToInsersectionPointIndices; + for (size_t intersectionPointIndex=0; intersectionPointIndex, std::map> determine_quality_per_node_per_domain(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const FieldRef coordsField, + const std::vector & intersectionPoints, + const QualityMetric &qualityMetric, + const bool globalIDsAreParallelConsistent) +{ + const int dim = mesh.mesh_meta_data().spatial_dimension(); + + const auto nodeToInsersectionPointIndices = get_node_to_intersection_point_indices(mesh, intersectionPoints); + + std::vector sortedIntersectionPointIndices; + std::vector elemNodes; + std::vector elemNodeCoords; + + std::map, std::map> domainsToNodesToQuality; + for (auto entry : nodeToInsersectionPointIndices) + { + stk::mesh::Entity node = entry.first; + const auto nodeIntersectionPointIndices = entry.second; + + std::set> nodeIntPtDomains; + for (auto && intPtIndex : nodeIntersectionPointIndices) + nodeIntPtDomains.insert(intersectionPoints[intPtIndex].get_sorted_domains()); + + for (auto && intPtDomains : nodeIntPtDomains) + { + fill_sorted_intersection_point_indices_for_node_for_domains(mesh, coordsField, intersectionPoints, nodeIntersectionPointIndices, node, intPtDomains, globalIDsAreParallelConsistent, sortedIntersectionPointIndices); + const std::set intersectedElements = get_intersected_elements(mesh, elementSelector, intersectionPoints, sortedIntersectionPointIndices); + + double qualityAfterCut = qualityMetric.get_best_value_for_metric(); + for (auto && elem : intersectedElements) + { + elemNodes.assign(mesh.begin_nodes(elem), mesh.end_nodes(elem)); + fill_node_locations(dim, coordsField, elemNodes, elemNodeCoords); + const double elemQualityAfterCuts = estimate_quality_of_cutting_intersection_points(mesh, coordsField, elemNodes, elemNodeCoords, sortedIntersectionPointIndices, intersectionPoints, qualityMetric); + + if (qualityMetric.is_first_quality_metric_better_than_second(qualityAfterCut, elemQualityAfterCuts)) + qualityAfterCut = elemQualityAfterCuts; + } + + domainsToNodesToQuality[intPtDomains][mesh.identifier(node)] = qualityAfterCut; + } + } + + return domainsToNodesToQuality; +} + +std::vector +build_snap_infos_from_intersection_points(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const NodeToCapturedDomainsMap & nodesToCapturedDomains, + const std::vector & intersectionPoints, + const QualityMetric &qualityMetric, + const bool globalIDsAreParallelConsistent) +{ + std::vector snapInfos; + + const AuxMetaData & auxMeta = AuxMetaData::get(mesh.mesh_meta_data()); + const Phase_Support phaseSupport = Phase_Support::get(mesh.mesh_meta_data()); + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + const int dim = mesh.mesh_meta_data().spatial_dimension(); + std::vector nodeLocations; + std::vector procsThatNeedToKnowAboutThisInfo; + std::vector globalIdsOfSnapNodeElems; + + int owner = mesh.parallel_rank(); + + const auto domainsToNodesToQuality = determine_quality_per_node_per_domain(mesh, elementSelector, coordsField, intersectionPoints, qualityMetric, globalIDsAreParallelConsistent); + + for (size_t intersectionPointIndex=0; intersectionPointIndexsecond; + const double cutQualityEstimate = nodesToQuality.at(mesh.identifier(node)); + + // For face and volume cuts, allow quality to go down to acceptable_value_for_metric because estimate is not that good + //const double minAcceptableQuality = (nodes.size() == 2) ? cutQualityEstimate : std::min(qualityMetric.get_acceptable_value_for_metric(), cutQualityEstimate); + const double minAcceptableQuality = cutQualityEstimate; + + const double postSnapQuality = compute_quality_if_node_is_snapped_terminating_early_if_below_threshold(mesh, elementSelector, coordsField, node, snapLocation, qualityMetric, minAcceptableQuality); + if (qualityMetric.is_first_quality_metric_better_than_second(postSnapQuality, minAcceptableQuality)) + { + const size_t nodeGlobalId = mesh.identifier(node); + + fill_global_ids_of_elements_using_node(mesh, elementSelector, node, globalIdsOfSnapNodeElems); + fill_procs_owning_or_sharing_or_ghosting_node(mesh, node, procsThatNeedToKnowAboutThisInfo); + + snapInfos.emplace_back(nodeGlobalId, intersectionPointIndex, nodeLocations[nodeIndex], owner, procsThatNeedToKnowAboutThisInfo, globalIdsOfSnapNodeElems, postSnapQuality, snapLocation, nodes.size()); + } + else if (krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Skipping snap of " << mesh.identifier(node) << " to " << snapLocation << " at " << debug_output(mesh, intersectionPoint) << " with snap quality at or below " << postSnapQuality << " and estimated cut quality " << cutQualityEstimate << stk::diag::dendl; + } + } + } + } + + return snapInfos; +} + +void interpolate_nodal_field(const stk::mesh::BulkData & mesh, + stk::mesh::Entity node, const FieldRef field, + const std::vector & interpNodes, + const std::vector & interpWeights, + std::vector & scratch) +{ + const unsigned fieldLength = field.length(); + + double * val = field_data(field, node); + if (nullptr == val) return; + + scratch.assign(fieldLength, 0.0); + + for (size_t iNode=0; iNode(field, interpNodes[iNode]); + if (nullptr == nodeVal) + { + krinolog << "When snapping node " << mesh.identifier(node) << ", the field " << field.name() << " is missing on interpolating node " << mesh.identifier(interpNodes[iNode]) << stk::diag::dendl; + krinolog << "Should the field " << field.name() << " be an interpolation field?" << stk::diag::dendl; + ThrowRequireMsg(false, "Interpolation field missing on interpolation node " << mesh.identifier(interpNodes[iNode])); + } + + for (unsigned i=0; i & intersectionPoints, + const std::vector & snapInfos, + NodeToCapturedDomainsMap & nodesToCapturedDomains ) +{ + ThrowRequire(mesh.parallel_size() == 1 || mesh.is_automatic_aura_on()); + + std::vector< const stk::mesh::FieldBase *> interpFieldVec; + for(auto && field : interpolationFieldSet) + interpFieldVec.push_back(&field.field()); + stk::mesh::communicate_field_data(mesh, interpFieldVec); + + std::vector scratch; + std::vector snapNodes; + snapNodes.reserve(snapInfos.size()); + + for (auto && snapInfo : snapInfos) + { + if (snapInfo.get_owner() == mesh.parallel_rank()) + { + const size_t intersectionPointIndex = snapInfo.get_intersection_point_index(); + stk::mesh::Entity snapNode = mesh.get_entity(stk::topology::NODE_RANK, snapInfo.get_node_global_id()); + snapNodes.push_back(snapNode); + const IntersectionPoint & intersectionPoint = intersectionPoints[intersectionPointIndex]; + + nodesToCapturedDomains[snapNode] = intersectionPoint.get_sorted_domains(); + + const auto & nodes = intersectionPoint.get_nodes(); + const auto & weights = intersectionPoint.get_weights(); + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Snapping node " << snapInfo.get_node_global_id() << " to " << debug_output(mesh, intersectionPoint) << stk::diag::dendl; + } + + for(auto && field : interpolationFieldSet) + interpolate_nodal_field(mesh, snapNode, field, nodes, weights, scratch); + } + } + + stk::mesh::communicate_field_data(mesh, interpFieldVec); + + communicate_node_captured_domains_for_given_nodes(mesh, snapNodes, nodesToCapturedDomains); +} + +template +size_t get_global_num_infos(const std::vector & infos, stk::ParallelMachine comm) +{ + size_t numInfos = 0; + int rank{stk::parallel_machine_rank(comm)}; + for (const auto &info : infos) + if (info.get_owner() == rank) + ++numInfos; + const size_t localNumInfos = numInfos; + stk::all_reduce_sum(comm, &localNumInfos, &numInfos, 1); + return numInfos; +} + +void pack_owned_snap_infos_that_other_procs_need_to_know_about(std::vector &snapInfos, stk::CommSparse &commSparse) +{ + stk::pack_and_communicate(commSparse,[&]() + { + for(const auto &snapInfo : snapInfos) + { + if ( snapInfo.get_owner() == commSparse.parallel_rank() ) + { + for ( const int procId : snapInfo.get_procs_that_need_to_know_about_this_info()) + { + if ( procId != commSparse.parallel_rank()) + { + commSparse.send_buffer(procId).pack(snapInfo.get_node_global_id()); + commSparse.send_buffer(procId).pack(snapInfo.get_intersection_point_index()); + stk::pack_vector_to_proc(commSparse, snapInfo.get_procs_that_need_to_know_about_this_info(), procId); + stk::pack_vector_to_proc(commSparse, snapInfo.get_conflicting_ids(), procId); + commSparse.send_buffer(procId).pack(snapInfo.get_post_worst_quality()); + commSparse.send_buffer(procId).pack(snapInfo.get_node_location()); + commSparse.send_buffer(procId).pack(snapInfo.get_snap_location()); + commSparse.send_buffer(procId).pack(snapInfo.get_snap_rank()); + } + } + } + } + }); +} + +void receive_snap_infos_that_this_proc_need_to_know_about_and_ghost(std::vector &snapInfos, stk::CommSparse &commSparse) +{ + stk::unpack_communications(commSparse, [&commSparse, &snapInfos](int procId) + { + size_t globalNodeId{0}; + commSparse.recv_buffer(procId).unpack(globalNodeId); + + size_t intersectionPointIndex{0}; + commSparse.recv_buffer(procId).unpack(intersectionPointIndex); + + std::vector procsThatNeedToKnowAboutThisInfo; + std::vector globalIdsOfSnapNodeElems; + stk::unpack_vector_from_proc(commSparse, procsThatNeedToKnowAboutThisInfo, procId); + stk::unpack_vector_from_proc(commSparse, globalIdsOfSnapNodeElems, procId); + + double postSnapQuality{0}; + commSparse.recv_buffer(procId).unpack(postSnapQuality); + + stk::math::Vector3d nodeLocation; + commSparse.recv_buffer(procId).unpack(nodeLocation); + + stk::math::Vector3d snapLocation; + commSparse.recv_buffer(procId).unpack(snapLocation); + + int snapRank; + commSparse.recv_buffer(procId).unpack(snapRank); + + snapInfos.emplace_back(globalNodeId, + intersectionPointIndex, + nodeLocation, + procId, + procsThatNeedToKnowAboutThisInfo, + globalIdsOfSnapNodeElems, + postSnapQuality, + snapLocation, + snapRank); + }); +} + +void communicate_snap_infos_that_other_procs_need_to_know_about(std::vector &snapInfos, stk::ParallelMachine comm) +{ + stk::CommSparse commSparse(comm); + + pack_owned_snap_infos_that_other_procs_need_to_know_about(snapInfos, commSparse); + receive_snap_infos_that_this_proc_need_to_know_about_and_ghost(snapInfos, commSparse); +} + +std::vector get_sorted_nodes_modified_in_current_snapping_iteration(const stk::mesh::BulkData & mesh, const std::vector & iterationSnapInfos) +{ + std::vector sortedSnappedNodes; + for (auto && snapInfo : iterationSnapInfos) + sortedSnappedNodes.push_back(mesh.get_entity(stk::topology::NODE_RANK, snapInfo.get_node_global_id())); + std::sort(sortedSnappedNodes.begin(), sortedSnappedNodes.end()); + return sortedSnappedNodes; +} + +double determine_quality(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const QualityMetric &qualityMetric) +{ + const FieldRef coordsField(mesh.mesh_meta_data().coordinate_field()); + std::vector nodeLocations; + + double quality = qualityMetric.get_best_value_for_metric(); + for (auto && bucket : mesh.get_buckets(stk::topology::ELEMENT_RANK, elementSelector)) + { + if (bucket->topology().base() == stk::topology::TETRAHEDRON_4 || bucket->topology().base() == stk::topology::TRIANGLE_3_2D) + { + for (auto && element : *bucket) + { + fill_element_node_coordinates(mesh, element, coordsField, nodeLocations); + const double elementQuality = qualityMetric.get_element_quality_metric(nodeLocations); + quality = std::min(quality, elementQuality); + } + } + } + + const double localQuality = quality; + stk::all_reduce_min(mesh.parallel(), &localQuality, &quality, 1); + + return quality; +} + +NodeToCapturedDomainsMap snap_as_much_as_possible_while_maintaining_quality(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const FieldSet & interpolationFields, + const InterfaceGeometry & geometry, + const bool globalIDsAreParallelConsistent) +{/* %TRACE[ON]% */ Trace trace__("krino::snap_as_much_as_possible_while_maintaining_quality()"); /* %TRACE% */ + + const ScaledJacobianQualityMetric qualityMetric; + size_t iteration{0}; + NodeToCapturedDomainsMap nodesToCapturedDomains; + stk::ParallelMachine comm = mesh.parallel(); + + std::vector intersectionPoints; + geometry.store_phase_for_uncut_elements(mesh); + intersectionPoints = build_all_intersection_points(mesh, geometry, nodesToCapturedDomains); + + while (true) + { + krinolog << "Snapping To Geometry Iteration " << std::to_string(++iteration) << stk::diag::dendl; + + std::vector snapInfos = build_snap_infos_from_intersection_points(mesh, elementSelector, nodesToCapturedDomains, intersectionPoints, qualityMetric, globalIDsAreParallelConsistent); + + bool done = stk::is_true_on_all_procs(comm, snapInfos.empty()); + if ( done ) + break; + + communicate_snap_infos_that_other_procs_need_to_know_about(snapInfos, comm); + + const std::vector independentSnapInfos = find_snap_info_independent_sets(snapInfos, qualityMetric, comm); + + krinolog << " Snapping " << get_global_num_infos(independentSnapInfos, comm) << " of " << get_global_num_infos(snapInfos, comm) << " snap candidates." << stk::diag::dendl; + + geometry.store_phase_for_elements_that_will_be_uncut_after_snapping(mesh, intersectionPoints, independentSnapInfos, nodesToCapturedDomains); + snap_nodes(mesh, interpolationFields, intersectionPoints, independentSnapInfos, nodesToCapturedDomains); + + const std::vector iterationSortedSnapNodes = get_sorted_nodes_modified_in_current_snapping_iteration(mesh, independentSnapInfos); + update_intersection_points_after_snap_iteration(mesh, geometry, iterationSortedSnapNodes, nodesToCapturedDomains, intersectionPoints); + } + + krinolog << "After snapping quality is " << determine_quality(mesh, elementSelector, qualityMetric) << stk::diag::dendl; + + return nodesToCapturedDomains; +} +} + + + diff --git a/packages/krino/krino/krino_lib/Akri_Snap.hpp b/packages/krino/krino/krino_lib/Akri_Snap.hpp new file mode 100644 index 000000000000..f56bc31ecd38 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Snap.hpp @@ -0,0 +1,35 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_SNAP_H_ +#define KRINO_INCLUDE_AKRI_SNAP_H_ +#include +#include +#include +#include + +namespace krino +{ +class InterfaceGeometry; +class QualityMetric; + +NodeToCapturedDomainsMap snap_as_much_as_possible_while_maintaining_quality(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const FieldSet & interpolationFields, + const InterfaceGeometry & geometry, + const bool globalIDsAreParallelConsistent); + +double determine_quality(const stk::mesh::BulkData & mesh, + const stk::mesh::Selector & elementSelector, + const QualityMetric &qualityMetric); + +} + + + +#endif /* KRINO_INCLUDE_AKRI_SNAP_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_SnapIndependentSetFinder.hpp b/packages/krino/krino/krino_lib/Akri_SnapIndependentSetFinder.hpp new file mode 100644 index 000000000000..4c2490eadee5 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SnapIndependentSetFinder.hpp @@ -0,0 +1,34 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_SNAPINDEPENDENTSETFINDER_H +#define AKRI_SNAPINDEPENDENTSETFINDER_H + +#include +#include +#include +#include + +namespace krino +{ + +typedef independent_set::IndependentSetFinder SnapInfoIndependentSetFinder; + +inline +std::vector find_snap_info_independent_sets(const std::vector &allSnapInfos, + const QualityMetric & qualityMetric, + stk::ParallelMachine comm) +{ + SnapInfo::Comparator comparator {qualityMetric}; + SnapInfo::ConflictFinder conflictFinder; + return SnapInfoIndependentSetFinder::find_independent_set(allSnapInfos, comparator, conflictFinder, false, comm); +} + +} + +#endif /* AKRI_SNAPINDEPENDENTSETFINDER_H */ diff --git a/packages/krino/krino/krino_lib/Akri_SnapInfo.cpp b/packages/krino/krino/krino_lib/Akri_SnapInfo.cpp new file mode 100644 index 000000000000..96d0abdefd56 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SnapInfo.cpp @@ -0,0 +1,71 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +namespace krino +{ + +template +std::string +to_string(const Container & container) +{ + std::ostringstream os; + os << " {"; + for(auto data : container) + os << " " << data; + os << " }"; + return os.str(); +} + +bool SnapInfo::Comparator::is_first_higher_priority_than_second(const SnapInfo& snapInfoA,const SnapInfo& snapInfoB) const +{ + if( snapInfoA.get_snap_rank() > snapInfoB.get_snap_rank() ) + return true; + else if ( snapInfoA.get_snap_rank() < snapInfoB.get_snap_rank() ) + return false; + + if(mQualityMetric.is_first_quality_metric_better_than_second(snapInfoA.get_post_worst_quality(),snapInfoB.get_post_worst_quality())) + return true; + else if(mQualityMetric.is_first_quality_metric_better_than_second(snapInfoB.get_post_worst_quality(),snapInfoA.get_post_worst_quality())) + return false; + + if (snapInfoA.get_node_global_id() != snapInfoB.get_node_global_id()) + { + if ( is_less_than_in_x_then_y_then_z(snapInfoA.get_node_location(), snapInfoB.get_node_location()) ) + return true; + else if ( is_less_than_in_x_then_y_then_z(snapInfoB.get_node_location(), snapInfoA.get_node_location()) ) + return false; + } + + if ( is_less_than_in_x_then_y_then_z(snapInfoA.get_snap_location(), snapInfoB.get_snap_location()) ) + return true; + else if ( is_less_than_in_x_then_y_then_z(snapInfoB.get_snap_location(), snapInfoA.get_snap_location()) ) + return false; + + return false; +} + +std::ostream & operator<<(std::ostream & os, const SnapInfo& snapInfo) +{ + os << " Owner: " << snapInfo.get_owner() << std::endl; + os << "Snap Node: " << snapInfo.get_node_global_id() << std::endl; + os << "ConflictingIds: " << to_string(snapInfo.get_conflicting_ids()) << std::endl; + os << "SnapLocation: " << snapInfo.get_snap_location().to_string(16) << std::endl; + os << "PostSnapWorstQuality: " << snapInfo.get_post_worst_quality() << std::endl; + os << "GetProcsThatNeedToKnow: " << to_string(snapInfo.get_procs_that_need_to_know_about_this_info()) << std::endl; + os << "Snap Rank: " << snapInfo.get_snap_rank() << std::endl; + return os; +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_SnapInfo.hpp b/packages/krino/krino/krino_lib/Akri_SnapInfo.hpp new file mode 100644 index 000000000000..889defbd38e9 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SnapInfo.hpp @@ -0,0 +1,87 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_SNAPINFO_H +#define AKRI_SNAPINFO_H + +#include +#include +#include + +namespace krino +{ + +class SnapInfo +{ +public: + typedef size_t ExclusionIdentifierType; + typedef std::array GlobalId; + + SnapInfo(const size_t nodeGlobalId, + const size_t intersectionPointIndex, + const stk::math::Vector3d & nodeLocation, + const int owner, + const std::vector & procsThatNeedToKnowAboutThisInfo, + const std::vector & globalIdsOfSnapNodeElems, + const double& postSnapQuality, + const stk::math::Vector3d & snapLocation, + const int snapRank) + : mUniqueId(make_unique_id(nodeGlobalId, intersectionPointIndex)), + mOwner{owner}, + mProcsThatNeedToKnowAboutThisInfo{procsThatNeedToKnowAboutThisInfo}, + mGlobalIdsOfSnapNodeElems{globalIdsOfSnapNodeElems}, + mPostWorstQuality(postSnapQuality), // () for intel 17 + mNodeLocation(nodeLocation), + mSnapLocation(snapLocation), + mSnapRank{snapRank} + { + } + + static GlobalId make_unique_id(const size_t globalNodeId, const size_t intersectionPointIndex) { return GlobalId{globalNodeId, intersectionPointIndex}; } + const GlobalId &get_unique_id() const { return mUniqueId; } + size_t get_node_global_id() const { return mUniqueId[0]; } + size_t get_intersection_point_index() const { return mUniqueId[1]; } + int get_owner() const { return mOwner; } + const std::vector &get_procs_that_need_to_know_about_this_info() const { return mProcsThatNeedToKnowAboutThisInfo; } + double get_post_worst_quality() const { return mPostWorstQuality; } + const stk::math::Vector3d & get_snap_location() const { return mSnapLocation; }; + const stk::math::Vector3d & get_node_location() const { return mNodeLocation; }; + int get_snap_rank() const { return mSnapRank; } + const std::vector &get_conflicting_ids() const { return mGlobalIdsOfSnapNodeElems; } + + class ConflictFinder + { + public: + std::vector get_other_conflicting_infos(const SnapInfo& info) const {return {};} + }; + class Comparator + { + public: + Comparator(const QualityMetric &qualityMetric) : mQualityMetric{qualityMetric}{} + bool is_first_higher_priority_than_second(const SnapInfo& tetSnapInfoA,const SnapInfo& tetSnapInfoB) const; + + private: + const QualityMetric &mQualityMetric; + }; + +private: + const GlobalId mUniqueId; + const int mOwner{0u}; + const std::vector mProcsThatNeedToKnowAboutThisInfo; + const std::vector mGlobalIdsOfSnapNodeElems; + const double mPostWorstQuality{0.0}; + const stk::math::Vector3d mNodeLocation{}; + const stk::math::Vector3d mSnapLocation{}; + const int mSnapRank; +}; + +std::ostream & operator<<(std::ostream & os, const SnapInfo& snapInfo); + +} + +#endif /* AKRI_SNAPINFO_H */ diff --git a/packages/krino/krino/krino_lib/Akri_SnapToNode.cpp b/packages/krino/krino/krino_lib/Akri_SnapToNode.cpp new file mode 100644 index 000000000000..84b437bdd092 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SnapToNode.cpp @@ -0,0 +1,59 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include "../interface_geometry_interface/Akri_InterfaceGeometry.hpp" + +namespace krino { + +void determine_node_snapping_from_intersection_points(const stk::mesh::BulkData & mesh, + const std::vector & intersectionPoints, + const CDFEM_Snapper & snapper, + NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + const double snapTol = snapper.get_edge_tolerance(); + for (auto && intersectionPoint : intersectionPoints) + { + const auto & nodes = intersectionPoint.get_nodes(); + const auto & weights = intersectionPoint.get_weights(); + for (size_t nodeIndex=0; nodeIndex 1.-snapTol) + { + if (krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Snapping node " << debug_output(mesh, intersectionPoint) << " to " << mesh.identifier(nodes[nodeIndex]) << stk::diag::dendl; + } + + const auto & intersectionPointDomains = intersectionPoint.get_sorted_domains(); + auto & nodeCapturedDomains = nodesToCapturedDomains[nodes[nodeIndex]]; + nodeCapturedDomains.insert(nodeCapturedDomains.end(), intersectionPointDomains.begin(), intersectionPointDomains.end()); + } + } + } + + for (auto && nodeToCapturedDomains : nodesToCapturedDomains) + stk::util::sort_and_unique(nodeToCapturedDomains.second); +} + +void snap_to_node(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & interfaceGeometry, + const CDFEM_Snapper & snapper, + NodeToCapturedDomainsMap & nodesToCapturedDomains) +{ + const std::vector intersectionPoints = build_uncaptured_intersection_points(mesh, interfaceGeometry, nodesToCapturedDomains); + + determine_node_snapping_from_intersection_points(mesh, intersectionPoints, snapper, nodesToCapturedDomains); + communicate_node_captured_domains_for_all_nodes(mesh, nodesToCapturedDomains); +} + +} + + diff --git a/packages/krino/krino/krino_lib/Akri_SnapToNode.hpp b/packages/krino/krino/krino_lib/Akri_SnapToNode.hpp new file mode 100644 index 000000000000..26fe17920793 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SnapToNode.hpp @@ -0,0 +1,26 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_INCLUDE_AKRI_SNAPTONODE_H_ +#define KRINO_INCLUDE_AKRI_SNAPTONODE_H_ + +#include +#include + +namespace krino { + +class CDFEM_Support; +class Phase_Support; + +void snap_to_node(const stk::mesh::BulkData & mesh, + const InterfaceGeometry & interfaceGeometry, + const CDFEM_Snapper & snapper, + NodeToCapturedDomainsMap & nodesToCapturedDomains); + +} +#endif /* KRINO_INCLUDE_AKRI_SNAPTONODE_H_ */ diff --git a/packages/krino/krino/krino_lib/Akri_SubElement.cpp b/packages/krino/krino/krino_lib/Akri_SubElement.cpp new file mode 100644 index 000000000000..6a531e1abab2 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SubElement.cpp @@ -0,0 +1,2833 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace krino{ + +bool +SubElementNode::on_common_edge(const SubElementNode * other) const +{ + NodeSet ancestors; + get_ancestors(ancestors); + if (ancestors.size() <= 2) + { + other->get_ancestors(ancestors); + } + if (ancestors.size() <= 2) + { + return true; + } + return false; +} + +SubElementMidSideNode::SubElementMidSideNode( const Mesh_Element * owner, + const SubElementNode *parent1, + const SubElementNode *parent2) + : SubElementNode(owner), + my_is_mesh_node(false), + my_parent1(parent1), + my_parent2(parent2) +{ + // fill base class data + my_cached_owner_coords = compute_owner_coords( owner ); + my_global_coords = 0.5*(my_parent1->coordinates()) + 0.5*(my_parent2->coordinates()); +} + +SubElementMidSideNode::SubElementMidSideNode( const Mesh_Element * owner, + const SubElementNode *parent1, + const SubElementNode *parent2, + stk::mesh::Entity meshNode, + stk::mesh::EntityId meshNodeId) + : SubElementMidSideNode(owner, parent1, parent2) +{ + my_is_mesh_node = true; + set_entity(meshNode, meshNodeId); +} + +SubElementChildNode::SubElementChildNode( const Mesh_Element * in_owner, + const NodeVec & parents, + const std::vector & weights ) + : SubElementNode(in_owner), + my_parents(parents), + my_weights(weights) +{ + // fill base class data + my_cached_owner_coords = compute_owner_coords( in_owner ); + my_global_coords = in_owner->coordinates( my_cached_owner_coords ); +} + +SubElementMeshNode::SubElementMeshNode( const Mesh_Element * in_owner, + stk::mesh::Entity nodeEntity, + stk::mesh::EntityId nodeEntityId, + const Vector3d & in_owner_coords, + const Vector3d & in_global_coords ) + : SubElementNode(in_owner) +{ + // fill base class data + set_entity(nodeEntity, nodeEntityId); + my_cached_owner_coords = in_owner_coords; + my_global_coords = in_global_coords; +} + +void +SubElementNode::get_parent_entities(std::vector & parent_entities) const +{ + NodeVec parents = get_parents(); + + const unsigned parent_size = parents.size(); + parent_entities.resize(parent_size); + + for (unsigned i=0; ientity(); + } +} + +static bool is_on_multiple_blocks(const stk::mesh::BulkData& mesh, stk::mesh::Entity node) +{ + bool foundVolumePart = false; + for (auto && part : mesh.bucket(node).supersets()) + { + if (part->primary_entity_rank() == stk::topology::ELEMENT_RANK && + !stk::mesh::is_auto_declared_part(*part) && + part->subsets().empty() && + part->name().compare(0,7,"refine_") != 0) + { + if (foundVolumePart) return true; + foundVolumePart = true; + } + } + return false; +} + +bool SubElementChildNode::needs_to_be_ale_prolonged(const CDMesh & mesh) const +{ + if (mesh.get_prolongation_model() == INTERPOLATION) + return false; + + const CDMesh* old_mesh = mesh.get_old_mesh(); + const bool is_initial_mesh = old_mesh->stash_step_count() < 0; + if (is_initial_mesh) + return false; + + if (is_on_multiple_blocks(mesh.stk_bulk(), entity())) + return true; + + // relatively unusual case of an edge node that is not on a block-block boundary. + // this is currently handled by using interpolation. This possibly needs further + // testing/development to treat these like mesh nodes where we see if they have + // changed phase. + return false; +} + +Vector3d SubElementChildNode::compute_owner_coords( const Mesh_Element * in_owner ) const +{ + Vector3d calcOwnerCoords{Vector3d::ZERO}; + ThrowAssert(my_parents.size() == my_weights.size()); + for (size_t i=0; iowner_coords(in_owner); + return calcOwnerCoords; +} + +void +SubElementChildNode::prolongate_fields(const CDMesh & mesh) const +{/* %TRACE[ON]% */ Trace trace__("SubElementEdgeNode::prolongate_fields() const"); /* %TRACE% */ + for (auto && parent : my_parents) + if (!parent->is_prolonged()) + parent->prolongate_fields(mesh); + + if (my_is_prolonged_flag) return; + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "SubElementEdgeNode::prolongate_fields for node#" << entityId() << "\n"; + my_is_prolonged_flag = true; + + const ProlongationPointData * prolong_node = needs_to_be_ale_prolonged(mesh) ? mesh.get_old_mesh()->find_prolongation_node(*this) : nullptr; + + prolong_cdfem_displacements(mesh, prolong_node); + + prolong_zeroed_fields(mesh, nullptr); + + prolong_ale_fields(mesh, prolong_node); + + prolong_interpolation_fields(mesh); +} + +bool SubElementMidSideNode::is_mesh_node_that_needs_to_be_prolonged(const CDMesh & mesh) const +{/* %TRACE[ON]% */ Trace trace__("SubElementMidSideNode::is_mesh_node_that_needs_to_be_prolonged() const"); /* %TRACE% */ + + ThrowRequire(my_is_mesh_node); + + const SubElementMeshNode * parent1 = dynamic_cast(my_parent1); + const SubElementMeshNode * parent2 = dynamic_cast(my_parent2); + const int num_ale_prolonged_parents = (parent1->needs_to_be_ale_prolonged(mesh) ? 1 : 0) + (parent2->needs_to_be_ale_prolonged(mesh) ? 1 : 0); + + if (num_ale_prolonged_parents == 0) return false; + if (num_ale_prolonged_parents == 2) return true; + + // 1 parent node needed to be ALE prolonged and this node is active (so the edge is not cut). + // This means the interface was cutting this edge, but now is not -> prolong OR + // the interface is passing through one of the parents of this uncut edge -> do not prolong. + + const bool have_or_did_have_interface = my_cached_owner->have_interface() || mesh.get_old_mesh()->find_mesh_element(my_cached_owner->entityId())->have_interface(); + + return have_or_did_have_interface && nullptr == mesh.get_old_mesh()->fetch_prolong_node(entityId()); +} + +void +SubElementMidSideNode::prolongate_fields(const CDMesh & mesh) const +{/* %TRACE[ON]% */ Trace trace__("SubElementMidSideNode::prolongate_fields() const"); /* %TRACE% */ + if (!my_parent1->is_prolonged()) + { + my_parent1->prolongate_fields(mesh); + } + if (!my_parent2->is_prolonged()) + { + my_parent2->prolongate_fields(mesh); + } + if (my_is_prolonged_flag) return; + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "SubElementMidSideNode::prolongate_fields for node#" << entityId() << "\n"; + my_is_prolonged_flag = true; + + const bool needs_to_be_prolonged = !my_is_mesh_node || is_mesh_node_that_needs_to_be_prolonged(mesh); + if (needs_to_be_prolonged) + { + // Note: CDFEM displacement is not present on midside nodes + prolong_zeroed_fields(mesh, nullptr); + + prolong_interpolation_fields(mesh); + + prolong_ale_fields(mesh); + } +} + +void +SubElementMidSideNode::prolong_interpolation_fields(const CDMesh & mesh) const +{ + const ElementObj * interp_elem = nullptr; + Vector3d interp_elem_p_coords; + const CDMesh* old_mesh = mesh.get_old_mesh(); + const Mesh_Element * old_owner = old_mesh->find_mesh_element(my_cached_owner->entityId()); + ThrowAssert(old_owner); + old_owner->find_child_coordinates_at_owner_coordinates(my_cached_owner_coords, interp_elem, interp_elem_p_coords); + + for(auto && field : mesh.get_interpolation_fields()) + { + const unsigned field_length = field.length(); + + double * val = field_data(field, my_entity); + if (NULL == val) continue; + + interp_elem->evaluate_prolongation_field(*old_mesh, field, field_length, interp_elem_p_coords, val); + } +} + +void +SubElementMidSideNode::prolong_ale_fields(const CDMesh & mesh) const +{ + // simply average parent nodes + for(auto && field : mesh.get_ale_prolongation_fields()) + { + const unsigned field_length = field.length(); + + double * val = field_data(field, my_entity); + if (nullptr != val) + { + double * val1 = field_data(field, my_parent1->entity()); + double * val2 = field_data(field, my_parent2->entity()); + ThrowRequire(val1 && val2); + for (unsigned i=0; iis_prolonged()) + { + parent->prolongate_fields(mesh); + } + } + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "SubElementInternalNode::prolongate_fields for node#" << entityId() << "\n"; + my_is_prolonged_flag = true; + + const stk::mesh::BulkData& stk_mesh = mesh.stk_bulk(); + const stk::mesh::FieldVector & all_fields = stk_mesh.mesh_meta_data().get_fields(); + for ( stk::mesh::FieldVector::const_iterator it = all_fields.begin(); it != all_fields.end() ; ++it ) + { + const FieldRef field = (const FieldRef)(**it); + + // Do not try to prolong non-real variables + if( field.entity_rank()!=stk::topology::NODE_RANK || !field.type_is() ) continue; + + const unsigned field_length = field.length(); + + double * val = field_data(field, my_entity); + if (NULL == val) continue; + + for (unsigned i=0; i(field, get_parents()[p]->entity()); + if (NULL == parent_val) + { + parent_error = true; + } + else + { + tot_wt += get_parent_weights()[p]; + for (unsigned i=0; ientity()) << stk::diag::dendl; + } + } + } +} + +bool on_interface_or_io_parts_have_changed(const stk::mesh::BulkData & mesh, const Phase_Support & phaseSupport, stk::mesh::Entity node, const ProlongationNodeData & oldProlongNode) +{ + const auto newParts = ProlongationNodeData::get_node_io_parts(mesh, node); + if (newParts != oldProlongNode.get_io_parts()) + return true; + for (auto && partOrdinal : newParts) + if (phaseSupport.is_interface(&mesh.mesh_meta_data().get_part(partOrdinal))) + return true; + return false; +} + +bool SubElementMeshNode::needs_to_be_ale_prolonged(const CDMesh & mesh) const +{/* %TRACE[ON]% */ Trace trace__("SubElementMeshNode::needs_to_be_ale_prolonged() const"); /* %TRACE% */ + const ProlongationNodeData * old_prolong_node = NULL; + const CDMesh* old_mesh = mesh.get_old_mesh(); + old_prolong_node = old_mesh->fetch_prolong_node(entityId()); + const bool is_initial_mesh = old_mesh->stash_step_count() < 0; + return !is_initial_mesh && nullptr != old_prolong_node && on_interface_or_io_parts_have_changed(mesh.stk_bulk(), mesh.get_phase_support(), entity(), *old_prolong_node); +} + +void +SubElementMeshNode::prolongate_fields(const CDMesh & mesh) const +{/* %TRACE[ON]% */ Trace trace__("SubElementMeshNode::prolongate_fields() const"); /* %TRACE% */ + + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "SubElementMeshNode::prolongate_fields for node#" << entityId() << "\n"; + my_is_prolonged_flag = true; + + const ProlongationPointData * prolong_data = NULL; + const ProlongationNodeData * old_prolong_node = NULL; + const CDMesh* old_mesh = mesh.get_old_mesh(); + old_prolong_node = old_mesh->fetch_prolong_node(entityId()); + + const bool needsToBeALEProlonged = needs_to_be_ale_prolonged(mesh); + if (mesh.get_prolongation_model() != INTERPOLATION && needsToBeALEProlonged) + { + prolong_data = old_mesh->find_prolongation_node(*this); + } + + if( !old_prolong_node && !prolong_data ) + { + return; + } + + prolong_cdfem_displacements(mesh, prolong_data, false); + + const ProlongationNodeData * nodeToExamineForPreExistingField = needsToBeALEProlonged ? nullptr : old_prolong_node; + prolong_zeroed_fields(mesh, nodeToExamineForPreExistingField); + + prolong_ale_fields(mesh, prolong_data, old_prolong_node); + + prolong_interpolation_fields(mesh, old_prolong_node); +} + +void +SubElementNode::prolong_zeroed_fields(const CDMesh & mesh, const ProlongationNodeData * nodeToExamineForPreExistingField) const +{ + const FieldSet & zeroed_fields = mesh.get_zeroed_fields(); + for(auto&& field : zeroed_fields) + { + if (!nodeToExamineForPreExistingField || !nodeToExamineForPreExistingField->get_field_data(field)) // If this node existed before and had this field, leave it alone + { + double * val = field_data(field, my_entity); + if (nullptr != val) std::fill(val, val+field.length(), 0.); + } + } +} + +void SubElementNode::prolong_cdfem_displacements(const CDMesh & mesh, + const ProlongationPointData * prolong_data, + const bool zero_if_no_prolong_data) const +{ + const FieldRef field = mesh.get_cdfem_displacements_field(); + if( !field.valid()) return; + + const unsigned field_length = field.length(); + for ( unsigned is = 0; is < field.number_of_states(); ++is ) + { + const stk::mesh::FieldState state = static_cast(is); + const FieldRef state_field = field.field_state(state); + + double * val = field_data(state_field, my_entity); + if(val == NULL) continue; + + if(!prolong_data) + { + if (zero_if_no_prolong_data) + { + std::fill(val, val+field_length, 0); + } + } + else + { + const double * prolong_field = prolong_data->get_field_data(state_field); + ThrowRequire(NULL != prolong_field); + std::copy(prolong_field, prolong_field+field_length, val); + + if (state == stk::mesh::StateNew) + { + const Vector3d & coords = coordinates(); + const Vector3d & old_coords = prolong_data->get_coordinates(); + for (unsigned i=0; i 0); + + for (auto && child : parents[0]->my_children) + { + if (child->get_num_parents() == numParents) + { + bool childOfAllParents = true; + for (size_t iParent=1; iParenthave_child(child)) + { + childOfAllParents = false; + break; + } + } + if (childOfAllParents) + return child; + } + } + return nullptr; +} + +bool SubElementNode::have_child() const +{ + return !my_children.empty(); +} + +bool SubElementNode::have_child(const SubElementNode* child) const +{ + return (std::find(my_children.begin(), my_children.end(), child) != my_children.end()); +} + +void +SubElementChildNode::prolong_ale_fields(const CDMesh & mesh, const ProlongationPointData * prolong_data) const +{ + const CDMesh* old_mesh = mesh.get_old_mesh(); + const ElementObj * interp_elem = nullptr; + Vector3d interp_elem_p_coords; + const FieldSet & ale_prolongation_fields = mesh.get_ale_prolongation_fields(); + for(FieldSet::const_iterator it = ale_prolongation_fields.begin(); it != ale_prolongation_fields.end(); ++it) + { + const FieldRef field = *it; + const unsigned field_length = field.length(); + + double * val = field_data(field, my_entity); + if (NULL == val) continue; + + if(prolong_data) + { + // this node has changed phase + // prolong based on prolong_node + const double * prolong_field = prolong_data->get_field_data(field); + // We cannot yet handle the case where a prolongation field is not defined on the prolongation node that + // was found. Throw here to avoid the possibility of not prolonging a prolongation field that then + // has its time derivative screwed up because it has a mesh velocity associated with it. + // We think this should only occur in problems with multiple different level sets. + ThrowRequire(prolong_field); + std::copy(prolong_field, prolong_field+field_length, val); + } + else + { + if (nullptr == interp_elem) + { + const Mesh_Element * old_owner = old_mesh->find_mesh_element(my_cached_owner->entityId()); + ThrowAssert(old_owner); + old_owner->find_child_coordinates_at_owner_coordinates(my_cached_owner_coords, interp_elem, interp_elem_p_coords); + } + + interp_elem->evaluate_prolongation_field(*old_mesh, field, field_length, interp_elem_p_coords, val); + } + } +} + +void +SubElementMeshNode::prolong_ale_fields(const CDMesh & mesh, + const ProlongationPointData * prolong_data, + const ProlongationNodeData * old_node) const +{ + const FieldSet & ale_prolongation_fields = mesh.get_ale_prolongation_fields(); + for(FieldSet::const_iterator it = ale_prolongation_fields.begin(); it != ale_prolongation_fields.end(); ++it) + { + const FieldRef field = *it; + const unsigned field_length = field.length(); + + double * val = field_data(field, my_entity); + if (NULL == val) continue; + + if(prolong_data) + { + // this node has changed phase + // prolong based on prolong_node + const double * prolong_field = prolong_data->get_field_data(field); + // We cannot yet handle the case where a prolongation field is not defined on the prolongation node that + // was found. Throw here to avoid the possibility of not prolonging a prolongation field that then + // has its time derivative screwed up because it has a mesh velocity associated with it. + // We think this should only occur in problems with multiple different level sets. + ThrowRequire(prolong_field); + std::copy(prolong_field, prolong_field+field_length, val); + } + else if(old_node) + { + const double * old_field = old_node->get_field_data(field); + if(!old_field) + { + const FieldRef initial_field = mesh.get_cdfem_support().get_initial_prolongation_field( field ); + if(initial_field.valid()) + { + const double * initial_data = old_node->get_field_data(initial_field); + std::copy(initial_data, initial_data+field_length, val); + } + else + { + std::fill(val, val+field_length, 0.); + } + } + } + } +} + +void +SubElementChildNode::prolong_interpolation_fields(const CDMesh & mesh) const +{ + const ElementObj * interp_elem = nullptr; + Vector3d interp_elem_p_coords; + const CDMesh* old_mesh = mesh.get_old_mesh(); + const Mesh_Element * old_owner = old_mesh->find_mesh_element(my_cached_owner->entityId()); + ThrowAssert(old_owner); + old_owner->find_child_coordinates_at_owner_coordinates(my_cached_owner_coords, interp_elem, interp_elem_p_coords); + + const FieldSet & interpolation_fields = mesh.get_interpolation_fields(); + for(FieldSet::const_iterator it = interpolation_fields.begin(); it != interpolation_fields.end(); ++it) + { + const FieldRef field = *it; + const unsigned field_length = field.length(); + + double * val = field_data(field, my_entity); + if (NULL == val) continue; + + interp_elem->evaluate_prolongation_field(*old_mesh, field, field_length, interp_elem_p_coords, val); + } +} + +void +SubElementMeshNode::prolong_interpolation_fields(const CDMesh & mesh, const ProlongationNodeData * old_node) const +{ + const FieldSet & interpolation_fields = mesh.get_interpolation_fields(); + for(FieldSet::const_iterator it = interpolation_fields.begin(); it != interpolation_fields.end(); ++it) + { + const FieldRef field = *it; + const unsigned field_length = field.length(); + + double * val = field_data(field, my_entity); + if (NULL == val) continue; + + if(old_node) + { + const double * old_field = old_node->get_field_data(field); + if(!old_field) + { + const FieldRef initial_field = mesh.get_cdfem_support().get_initial_prolongation_field( field ); + if(initial_field.valid()) + { + const double * initial_data = old_node->get_field_data(initial_field); + std::copy(initial_data, initial_data+field_length, val); + } + else + { + std::fill(val, val+field_length, 0.); + } + } + } + } +} + +void removeParts(stk::mesh::PartVector & parts, const stk::mesh::PartVector & parts_to_remove) +{ + stk::mesh::PartVector::iterator parts_begin = parts.begin(); + stk::mesh::PartVector::iterator begin_parts_to_erase = parts.end(); + for(stk::mesh::PartVector::const_iterator it = parts_to_remove.begin(); it != parts_to_remove.end(); ++it) + { + begin_parts_to_erase = std::remove(parts_begin, begin_parts_to_erase, *it); + } + parts.erase(begin_parts_to_erase, parts.end()); +} + +std::vector SubElementNode::prolongation_node_fields(const CDMesh & mesh) const +{ + const FieldSet & ale_prolongation_fields = mesh.get_ale_prolongation_fields(); + + std::vector ale_prolongation_fields_on_node; + ale_prolongation_fields_on_node.reserve(ale_prolongation_fields.size()); + for(auto && field : ale_prolongation_fields) + if (nullptr != field_data(field, my_entity)) + ale_prolongation_fields_on_node.push_back(field.field().mesh_meta_data_ordinal()); + + std::sort(ale_prolongation_fields_on_node.begin(), ale_prolongation_fields_on_node.end()); + return ale_prolongation_fields_on_node; +} + +static bool float_less(double a, double b) +{ + return static_cast(a) < static_cast(b); +} + +bool SubElementNode::higher_priority_by_score_then_ancestry(const SubElementNode & a, const SubElementNode & b, const bool globalIDsAreParallelConsistent) +{ + // higher score wins + if (float_less(b.get_node_score(), a.get_node_score())) return true; + if (float_less(a.get_node_score(), b.get_node_score())) return false; + + if (globalIDsAreParallelConsistent) + return SubElementNodeAncestry::compare(a.get_ancestry(), b.get_ancestry(), SubElementNode::less_by_entity_id); + return SubElementNodeAncestry::compare(a.get_ancestry(), b.get_ancestry(), SubElementNode::less_by_coordinates_then_by_entity_id); +} + +bool SubElementNode::less_by_entity_id(const SubElementNode & a, const SubElementNode & b) +{ + // lower id wins (this can be an issue because it is sensitive to the global ids provided by percept, which are dependent on the number of procs) + return a.entityId() < b.entityId(); +} + +bool SubElementNode::less_by_coordinates_then_by_entity_id(const SubElementNode & a, const SubElementNode & b) +{ + const Vector3d & aCoord = a.coordinates(); + const Vector3d & bCoord = b.coordinates(); + if (float_less(aCoord[0], bCoord[0])) return true; + if (float_less(bCoord[0], aCoord[0])) return false; + if (float_less(aCoord[1], bCoord[1])) return true; + if (float_less(bCoord[1], aCoord[1])) return false; + if (float_less(aCoord[2], bCoord[2])) return true; + if (float_less(bCoord[2], aCoord[2])) return false; + + // as a last resort, lower id wins (this can be an issue because it is sensitive to the global ids provided by percept, which are dependent on the number of procs) + return a.entityId() < b.entityId(); +} + +bool SubElementNode::captures_intersection_point_domains(const std::vector & intersectionPointDomains) const +{ + return first_sorted_vector_of_domains_contains_all_domains_in_second_vector(my_sorted_node_domains, intersectionPointDomains); +} + +bool SubElementNode::captures_interface(const InterfaceID & interface) const +{ + if (interface.first_ls() == interface.second_ls()) + return first_sorted_vector_of_domains_contains_all_domains_in_second_vector(my_sorted_node_domains, {interface.first_ls()}); + return first_sorted_vector_of_domains_contains_all_domains_in_second_vector(my_sorted_node_domains, {interface.first_ls(),interface.second_ls()}); +} + +void SubElementNode::insert_node_domains(const std::vector & domainsToAdd) const +{ + my_sorted_node_domains.insert(my_sorted_node_domains.end(), domainsToAdd.begin(), domainsToAdd.end()); + stk::util::sort_and_unique(my_sorted_node_domains); +} + + +const SubElementNode * +SubElementNode::find_node_with_common_ancestry(const CDMesh & search_mesh) const +{ + // This only works with a lineage of edge nodes (not internal nodes). + if (is_mesh_node()) + { + return search_mesh.get_mesh_node(entityId()); + } + + const NodeVec parents = get_parents(); + const unsigned num_parents = parents.size(); + + if (num_parents == 2) + { + const SubElementNode * search_parent1 = search_mesh.find_node_with_common_ancestry(parents[0]); + const SubElementNode * search_parent2 = search_mesh.find_node_with_common_ancestry(parents[1]); + if (nullptr != search_parent1 && nullptr != search_parent2) + { + return SubElementNode::common_child({search_parent1, search_parent2}); + } + } + return nullptr; +} + +void +SubElementNode::get_ancestors(NodeSet & ancestors) const +{ + if (is_mesh_node()) + { + ancestors.insert(this); + return; + } + const NodeVec parents = get_parents(); + for(auto&& parent : parents) + { + parent->get_ancestors(ancestors); + } +} + +SubElementNodeAncestry +SubElementNode::get_ancestry() const +{ + return SubElementNodeAncestry(this); +} + +void +SubElementNode::build_stencil(std::map & stencil, const double self_weight) const +{ + if (is_mesh_node()) + { + stencil[this] += self_weight; + return; + } + const NodeVec parents = get_parents(); + const std::vector parent_weights = get_parent_weights(); + for(unsigned i=0; ibuild_stencil(stencil, self_weight * parent_weights[i]); + } +} + +void +SubElementNode::build_constraint_stencil(const FieldRef field, std::vector & entities, std::vector & weights) const +{ + ThrowRequire(!is_mesh_node()); + static const double wt_min = 1.e-9; + typedef std::tuple EntityAndWeight; + std::vector entitiesAndWeights; + + const MasterElement* master_elem = my_cached_owner->get_evaluation_master_element(field); + + const unsigned npe = master_elem->get_topology().num_nodes(); + std::vector shapefcn (npe,0.); + master_elem->shape_fcn(1, my_cached_owner_coords.data(), shapefcn.data()); + + const auto & nodes = my_cached_owner->get_nodes(); + for (unsigned n=0; n wt_min) + { + entitiesAndWeights.push_back(std::make_tuple(nodes[n]->entityId(), nodes[n]->entity(), shapefcn[n])); + } + } + + std::sort(entitiesAndWeights.begin(), entitiesAndWeights.end(), [](const EntityAndWeight & a, const EntityAndWeight & b) { return std::get<0>(a) > std::get<0>(b); }); + + entities.clear(); + weights.clear(); + + entities.push_back(entity()); + weights.push_back(-1.0); + + for (auto && entityAndWeight : entitiesAndWeights) + { + entities.push_back(std::get<1>(entityAndWeight)); + weights.push_back(std::get<2>(entityAndWeight)); + } +} + +// Notes on coordinate systems. +// (1) real coordinates +// This can be obtained using Element::coordinates( Vector3d of owner coordinates ). +// (2) owner coordinates +// This is the parametric coordinates of the finite element. +// This can be obtained using SubElement::owner_coordinates( Vector3d of subelement coordinates ). +// This is the type of coordinates stored in SubElement::my_coords, no matter what level of subelement. +// In places where both owner coordinates and subelement coordinates are used, this is kept in the var owner_coords. +// (3) subelement coordinates +// This is the parametric coordinates within the subelement. +// In places where both owner coordinates and subelement coordinates are used, this is kept in the var local_coords. + +SubElement::SubElement( const stk::topology topo, + const NodeVec & nodes, + const std::vector & side_ids, + const Mesh_Element * owner) + : ElementObj( topo, nodes), + my_parent_side_ids( side_ids ), + my_owner( owner ) +{ /* %TRACE% */ /* %TRACE% */ + ThrowAssert( nodes.size() == topology().num_nodes() ); + ThrowAssert( my_parent_side_ids.size() == topology().num_sides() ); + + set_permutation(); +} + +void +SubElement::set_permutation() +{ + // For true subelements (not just coincident with the owning mesh element), permute the element + // nodes and sides in a consistent way. This will produce consistent node and side ordering + // from decomposition to decomposition. In this way, repeated decompositions will produce identical results. + + bool coincident_with_owner = true; + for (size_t n=0; nget_nodes()[n]) + { + coincident_with_owner = false; + break; + } + } + if (coincident_with_owner) return; + + std::vector node_ancestries; + node_ancestries.reserve(my_nodes.size()); + for (auto&& node : my_nodes) + { + node_ancestries.emplace_back(node); + } + const unsigned permutation_index = topology().lexicographical_smallest_permutation(node_ancestries.data(), true); // only consider positive permutations (true means this) + + if (permutation_index == stk::mesh::DEFAULT_PERMUTATION) return; + + // permute nodes + NodeVec permuted_nodes(my_nodes.size()); + topology().permutation_nodes(my_nodes.data(), permutation_index, permuted_nodes.data()); + my_nodes = permuted_nodes; + + // permute sides + std::vector side_permutation = get_side_permutation(topology(), static_cast(permutation_index)); + + std::vector permuted_parent_side_ids(my_parent_side_ids.size()); + for (unsigned iside=0; isideentity()) return false; + } + return true; +} + +void +SubElement::get_owner_coord_transform(double * dOwnerdSub) const +{ +// const NodeVec & owner_nodes = my_owner->get_nodes(); +// for ( int n = 0; n < my_num_nodes; n++ ) +// { +// krinolog << "node " << n << ", owner node phys_coords = " << owner_nodes[n]->coordinates()[0] << "," << owner_nodes[n]->coordinates()[1] << stk::diag::dendl; +// } + + std::vector owner_coords; + fill_node_owner_coords(my_owner, owner_coords); + + // Hard coded for linear simplex elements with constant transformations + const unsigned nnodes = num_nodes(); + ThrowAssert(my_master_elem.num_intg_pts() == nnodes); + const double * d_shape = my_master_elem.shape_fcn_deriv(); + + const int dim = spatial_dim(); + for ( int i = 0; i < dim; i++ ) + { + for ( int j = 0; j < dim; j++ ) + { + double & dOwnerdSub_ij = dOwnerdSub[i*dim + j]; + + dOwnerdSub_ij = 0.0; + for ( unsigned n = 0; n < nnodes; n++ ) + { + dOwnerdSub_ij += owner_coords[n][i] * d_shape[n*dim + j]; + } + + //krinolog << "dOwnerdSub[" << i << "][" << j << "] = " << dOwnerdSub_ij << stk::diag::dendl; + } + } +} + +void +SubElement::determine_decomposed_elem_phase(const CDMesh & mesh) +{ + if(have_subelements()) + { + for(auto && subelem : my_subelements) + { + subelem->determine_decomposed_elem_phase(mesh); + } + // Phase for SubElement with subelements is left empty + return; + } + + if(!my_owner->have_interface()) + { + set_phase(my_owner->get_phase()); + } + else + { + const PhaseTag startPhase = my_phase.empty() ? my_owner->get_phase() : my_phase; + my_phase = update_phase(mesh, startPhase, my_owner->get_sorted_cutting_interfaces(), myInterfaceSigns); + } + + if(krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "SubElement with nodes "; + for (auto && node : my_nodes) + krinolog << node->get_ancestry() << " "; + krinolog << "has phase " << my_phase << "\n"; + } +} + +std::vector SubElement::subelement_interface_signs(const InterfaceID interface, const int sign) const +{ + if (have_interface(interface) && sign != 0) + { + std::vector interfaceSigns = myInterfaceSigns; + interfaceSigns[my_owner->get_interface_index(interface)] = sign; + return interfaceSigns; + } + return myInterfaceSigns; +} + +void SubElement::initialize_interface_signs() +{ + myInterfaceSigns.assign(my_owner->get_num_interfaces(), 0); +} + +void SubElement::set_interface_signs(const std::vector & interfaceSigns) +{ + myInterfaceSigns = interfaceSigns; +} + +void SubElement::update_interface_signs(const InterfaceID interface, const int sign) +{ + set_interface_signs(subelement_interface_signs(interface, sign)); +} + +double +SubElement::relative_volume() const +{ /* %TRACE% */ /* %TRACE% */ + // This is a relative volume compared to the owner volume. + // Actually this is a relative volume if the "parametric" volume of the element is unity. + // Otherwise, it is off by a factor. + const int nelem = 1; + const int dim = spatial_dim(); + const int nint = my_master_elem.num_intg_pts(); + std::vector coords(my_nodes.size() * dim, 0.); + std::vector det_J(nint, 0.); + double error = 0.; + + // integration weights + const double * intg_weights = my_master_elem.intg_weights(); + + // load coords + int count = 0; + for ( auto && node : my_nodes ) + { + const Vector3d & owner_coords = node->owner_coords(my_owner); + for ( int j = 0; j < dim; j++ ) + { + coords[count++] = owner_coords[j]; + } + } + + // determinant at integration points + my_master_elem.determinant( dim, nelem, coords.data(), det_J.data(), &error ); + + double elem_volume = 0.; + for ( int ip = 0; ip < nint; ip++ ) + { + elem_volume += det_J[ip] * intg_weights[ip]; + } + + return elem_volume; +} + +double +SubElement::maximum_relative_angle() const +{ /* %TRACE% */ /* %TRACE% */ + // Find the maximum angle formed at the vertices in parametric coordinates. + // These are obviously differentt than that maximum angle in physical coordinates due to + // the shape of the owning element. + double max_angle = 0; + + const stk::topology topol = topology(); + const unsigned num_edges = topol.num_edges(); + for ( unsigned edge0 = 0; edge0 < num_edges; edge0++ ) + { + const unsigned * lnn0 = get_edge_node_ordinals(topol, edge0); + ThrowAssert( + 2 == topol.edge_topology(edge0).num_nodes() || 3 == topol.edge_topology(edge0).num_nodes()); + + for ( unsigned edge1 = edge0+1; edge1 < num_edges; edge1++ ) + { + const unsigned * lnn1 = get_edge_node_ordinals(topol, edge1); + ThrowAssert(2 == topol.edge_topology(edge1).num_nodes() || + 3 == topol.edge_topology(edge1).num_nodes()); + + int node0 = -1; + int node1 = -1; + if (lnn0[0] == lnn1[0]) + { + node0 = 0; node1 = 0; + } + else if (lnn0[0] == lnn1[1]) + { + node0 = 0; node1 = 1; + } + else if (lnn0[1] == lnn1[0]) + { + node0 = 1; node1 = 0; + } + else if (lnn0[1] == lnn1[1]) + { + node0 = 1; node1 = 1; + } + else + { + continue; + } + const Vector3d vec0 = my_nodes[lnn0[1-node0]]->owner_coords(my_owner) - my_nodes[lnn0[node0]]->owner_coords(my_owner); + const Vector3d vec1 = my_nodes[lnn1[1-node1]]->owner_coords(my_owner) - my_nodes[lnn1[node1]]->owner_coords(my_owner); + const double angle = std::acos( Dot(vec0.unit_vector(),vec1.unit_vector()) ); + + //if (angle > 2.4) + //{ + // krinolog << "DEBUG: bad angle = " << angle << " between edges=" << edge0 << "," << edge1 << " at node=" << lnn0[node0] << stk::diag::dendl; + //} + + if (angle > max_angle) + { + max_angle = angle; + } + } + } + + return max_angle; +} + +void +SubElement::decompose_edges(CDMesh & mesh, const InterfaceID interface_key) +{ /* %TRACE% */ /* %TRACE% */ + const std::string & owner_type = my_owner->topology().name(); + const std::string & sub_type = topology().name(); + ThrowRuntimeError("Subelement decomposition for subelement of type '" << sub_type + << "' which was generated from owning element of type '" << owner_type + << "' is missing the capability to generate conformal facets."); +} + +void +SubElement::find_refined_edges(std::vector & refined_edges) const +{ /* %TRACE% */ /* %TRACE% */ + + const stk::topology topol = topology(); + const unsigned num_edges = topol.num_edges(); + refined_edges.reserve(num_edges); + for ( unsigned edge = 0; edge < num_edges; edge++ ) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(topol, edge); + + const int num_edge_nodes = topol.edge_topology(edge).num_nodes(); + ThrowRequire(2 == num_edge_nodes || 3 == num_edge_nodes); + + if ((2 == num_edge_nodes && + NULL != SubElementNode::common_child({my_nodes[edge_node_ordinals[0]], my_nodes[edge_node_ordinals[1]]})) || + (3 == num_edge_nodes && + (NULL != SubElementNode::common_child({my_nodes[edge_node_ordinals[0]], my_nodes[edge_node_ordinals[2]]}) || + NULL != SubElementNode::common_child({my_nodes[edge_node_ordinals[1]], my_nodes[edge_node_ordinals[2]]})))) + { + refined_edges.push_back(edge); + } + } +} + +int +SubElement::find_longest_bad_edge(std::vector & bad_edges) const +{ /* %TRACE% */ /* %TRACE% */ + + const stk::topology topol = topology(); + + const unsigned num_bad_edges = bad_edges.size(); + PointVec edge_midpt(num_bad_edges,Vector3d::ZERO); + + if (0 == num_bad_edges) return -1; + + double max_length = 0; + unsigned longest_bad_edge_index = 0; + for ( unsigned index = 0; index < num_bad_edges; index++ ) + { + const unsigned edge = bad_edges[index]; + + const unsigned * const lnn = get_edge_node_ordinals(topol, edge); + const int num_edge_nodes = topol.edge_topology(edge).num_nodes(); + + const SubElementNode * const node0 = my_nodes[lnn[0]]; + const SubElementNode * const node1 = my_nodes[lnn[1]]; + + const Vector3d & coord0 = node0->coordinates(); + const Vector3d & coord1 = node1->coordinates(); + + const double edge_straight_length = (coord0 - coord1).length(); + ThrowRequire(edge_straight_length > 0.0); + + if (2 == num_edge_nodes) + { + edge_midpt[index] = 0.5*(coord0 + coord1); + } + else + { + ThrowAssert (3 == num_edge_nodes); + edge_midpt[index] = my_nodes[lnn[0]]->coordinates(); + } + + // we need an absolute mechanism for selecting the edge to bisect so that all elements that share + // common edges will make the same decisions + if (utility::is_more(edge_straight_length,max_length)) + { + longest_bad_edge_index = index; + max_length = edge_straight_length; + } + else if (!utility::is_less(edge_straight_length,max_length)) // tie breaker + { + const Vector3d & edge_midside_coords = edge_midpt[index]; + // note that it is safe to assume that longest_bad_edge is already assigned if edge_length == max_length + const Vector3d longest_edge_midside_coords = edge_midpt[longest_bad_edge_index]; + + ThrowAssert((utility::is_not_equal(edge_midside_coords[0],longest_edge_midside_coords[0]) || + utility::is_not_equal(edge_midside_coords[1],longest_edge_midside_coords[1]))); + + if (utility::is_more(edge_midside_coords[0],longest_edge_midside_coords[0]) || + (!utility::is_less(edge_midside_coords[0],longest_edge_midside_coords[0]) && + (utility::is_more(edge_midside_coords[1],longest_edge_midside_coords[1])))) + { + longest_bad_edge_index = index; + max_length = edge_straight_length; + } + } + } + return bad_edges[longest_bad_edge_index]; +} + +int +SubElement::parent_side_id(const int iside) const +{ + return my_parent_side_ids[iside]; +} + +void +SubElement::debug_subelements(const NodeVec & lnodes, const InterfaceID & interface, const int case_id) const +{ /* %TRACE% */ /* %TRACE% */ + krinolog << "owner_id=" << my_owner->entityId() << ", after cutting with interface " << interface << ", case_id=" << case_id << stk::diag::dendl; + + for (unsigned n=0; nget_ancestry() << "]\n"; + } + } + krinolog << " interface signs = "; + const std::vector interfaces = my_owner->get_sorted_cutting_interfaces(); + ThrowRequire(interfaces.size() == myInterfaceSigns.size()); + for (unsigned i=0; idebug(); + } + krinolog << stk::diag::dendl; +} + +void +SubElement::debug() const +{ /* %TRACE% */ /* %TRACE% */ + const double sub_vol = relative_volume(); + krinolog << " owner_id=" << my_owner->entityId() << ", relative_volume=" << sub_vol << ", interface signs = "; + const std::vector interfaces = my_owner->get_sorted_cutting_interfaces(); + ThrowRequire(interfaces.size() == myInterfaceSigns.size()); + for (unsigned i=0; iget_ancestry() << "] with domains { "; + for (int domain : node->get_sorted_node_domains()) krinolog << domain << " "; + krinolog << "}\n"; + } + } + for (unsigned n=0; nhave_interface(interface) && (myInterfaceSigns[my_owner->get_interface_index(interface)] == 0); +} + +SubElement_Tri_6::SubElement_Tri_6( + const NodeVec & nodes, + const std::vector & parent_side_ids, + const Mesh_Element * owner) + : SubElement( stk::topology::TRIANGLE_6_2D, + nodes, + parent_side_ids, + owner) +{ /* %TRACE% */ /* %TRACE% */ +} + +SubElement_Tet_10::SubElement_Tet_10( + const NodeVec & nodes, + const std::vector & parent_side_ids, + const Mesh_Element * owner) + : SubElement( stk::topology::TETRAHEDRON_10, + nodes, + parent_side_ids, + owner) +{ /* %TRACE% */ /* %TRACE% */ +} + +SubElement_Tri_3::SubElement_Tri_3( + const NodeVec & nodes, + const std::vector & parent_side_ids, + const Mesh_Element * owner) + : SubElement( stk::topology::TRIANGLE_3_2D, + nodes, + parent_side_ids, + owner) +{ /* %TRACE% */ /* %TRACE% */ + +} + +void +SubElement_Tri_3::build_quadratic_subelements(CDMesh & mesh) +{ /* %TRACE% */ /* %TRACE% */ + + if ( my_subelements.size() > 0 ) + { + for ( auto && subelem : my_subelements ) + { + subelem->build_quadratic_subelements(mesh); + } + return; + } + + // create 1, 6-noded tri + NodeVec sub_nodes = my_nodes; + sub_nodes.resize(6,(SubElementNode *)NULL); + + sub_nodes[3] = mesh.create_midside_node(my_owner, my_nodes[0], my_nodes[1]); + sub_nodes[4] = mesh.create_midside_node(my_owner, my_nodes[1], my_nodes[2]); + sub_nodes[5] = mesh.create_midside_node(my_owner, my_nodes[2], my_nodes[0]); + + std::unique_ptr sub = std::make_unique( sub_nodes, my_parent_side_ids, my_owner ); + sub->set_interface_signs(get_interface_signs()); + add_subelement( std::move(sub) ); +} + +void SubElement_Tri_3::cut_interior_intersection_point(CDMesh & mesh, const Vector3d & pCoords, const std::vector & sortedDomains) +{ + const std::vector weights{1.-pCoords[0]-pCoords[1], pCoords[0], pCoords[1]}; + + bool badCut = false; + for (auto && weight : weights) + { + if(weight < mesh.get_snapper().get_edge_tolerance()) + { + if (krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "Skipping cut of interior intersection point because of quality." << stk::diag::dendl; + badCut = true; + break; + } + } + + if (!badCut) + { + const SubElementNode * cutNode = mesh.create_child_internal_or_face_node( my_owner, my_nodes, weights ); + cutNode->set_node_domains(sortedDomains); + + NodeVec lnodes = my_nodes; + lnodes.push_back(cutNode); + + handle_tri(lnodes, get_interface_signs(), 0,1,3, 0,-1,-1, false,false,false); + handle_tri(lnodes, get_interface_signs(), 1,2,3, 1,-1,-1, false,false,false); + handle_tri(lnodes, get_interface_signs(), 2,0,3, 2,-1,-1, false,false,false); + } +} + +void +SubElement_Tri_3::determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key) +{ + determine_node_signs_on_edge( mesh, interface_key, 0,1 ); + determine_node_signs_on_edge( mesh, interface_key, 1,2 ); + determine_node_signs_on_edge( mesh, interface_key, 2,0 ); +} + +void +SubElement_Tri_3::determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key) +{ + // No-op, node scores are not used on tris +} + +void +SubElement_Tri_3::decompose_edges(CDMesh & mesh, const InterfaceID interface_key) +{ + process_edge( mesh, interface_key, 0,1 ); + process_edge( mesh, interface_key, 1,2 ); + process_edge( mesh, interface_key, 2,0 ); +} + +void +SubElement_Tri_3::fix_hanging_children(CDMesh & mesh, const InterfaceID & interface, const std::vector & edges_with_children) +{ + int edge_case_id = 0; + for (auto edge_with_children : edges_with_children) + { + edge_case_id += 1< node_signs = {{0, 0, 0}}; + + if (edge_case_id == 0) // uncut subelement + { + if (my_nodes[0]->node_sign_is_set()) node_signs[0] = my_nodes[0]->get_node_sign(); + if (my_nodes[1]->node_sign_is_set()) node_signs[1] = my_nodes[1]->get_node_sign(); + if (my_nodes[2]->node_sign_is_set()) node_signs[2] = my_nodes[2]->get_node_sign(); + + if (!(node_signs[0] >= 0 && node_signs[1] >= 0 && node_signs[2] >= 0) && + !(node_signs[0] <= 0 && node_signs[1] <= 0 && node_signs[2] <= 0)) + { + return; + } + } + else + { + for (auto && edge_with_child : edges_with_children) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(topology(), edge_with_child); + const int i0 = edge_node_ordinals[0]; + const int i1 = edge_node_ordinals[1]; + node_signs[i0] = my_nodes[i0]->get_node_sign(); + node_signs[i1] = my_nodes[i1]->get_node_sign(); + } + } + + perform_decomposition(mesh, interface, node_signs); +} + +void +SubElement_Tri_3::perform_decomposition(CDMesh & mesh, const InterfaceID interface_key, const std::array & node_signs) +{ /* %TRACE% */ /* %TRACE% */ + + const int case_id = + (node_signs[0]+1) + + (node_signs[1]+1)*3 + + (node_signs[2]+1)*9; + + NodeVec lnodes = my_nodes; + lnodes.resize(6,(SubElementNode *)NULL); + + static const unsigned case_permutations[] = + { 0, 0, 0, 2, 0, 0, 2, 1, 1, 1, // 0-9 + 1, 2, 2, 0, 2, 2, 1, 1, 1, 1, // 10-19 + 2, 0, 0, 2, 0, 0, 0 }; // 20-26 + static const unsigned permute_case_ids[] = + { 0, 1, 2, 1, 4, 5, 2,21,24, 1, // 0-9 + 4,21, 4,13,22, 5,22,25, 2, 5, // 10-19 + 24,21,22,25,24,25,26 }; // 20-26 + + stk::topology topo = stk::topology::TRIANGLE_6_2D; + std::vector permute(6); + topo.permutation_node_ordinals(case_permutations[case_id], permute.begin()); + + const unsigned i0 = permute[0]; + const unsigned i1 = permute[1]; + const unsigned i2 = permute[2]; + const unsigned i3 = permute[3]; + const unsigned i5 = permute[5]; + + // nodes and sides permute the same way + const unsigned s0 = permute[0]; + const unsigned s1 = permute[1]; + const unsigned s2 = permute[2]; + + const int permute_case_id = permute_case_ids[case_id]; + + // FIXME: Remove this diagnostic + std::vector node_val(3); + node_val[2] = case_id/9; + node_val[1] = (case_id-9*node_val[2])/3; + node_val[0] = case_id-9*node_val[2]-3*node_val[1]; + ThrowRequire(permute_case_id == (node_val[i0] + node_val[i1]*3 + node_val[i2]*9)); + + const Simplex_Generation_Method simplexMethod = mesh.get_cdfem_support().get_simplex_generation_method(); + +// krinolog << "case_id, permute_case_id = " << case_id << ", " << permute_case_id << stk::diag::dendl; + + switch (permute_case_id) + { + case 0: // ls[0]<0 && ls[1]<0 && ls[2]<0 + case 1: // ls[0]=0 && ls[1]<0 && ls[2]<0 + { + update_interface_signs(interface_key, -1); + } + break; + + case 13: // ls[0]=0 && ls[1]=0 && ls[2]=0 + case 25: // ls[0]=0 && ls[1]>0 && ls[2]>0 + case 26: // ls[0]>0 && ls[1]>0 && ls[2]>0 + { + update_interface_signs(interface_key, +1); + } + break; + + case 2: // ls[0]>0 && ls[1]<0 && ls[2]<0 + case 24: // ls[0]<0 && ls[1]>0 && ls[2]>0 + { + lnodes[i3] = SubElementNode::common_child({lnodes[i0], lnodes[i1]}); + lnodes[i5] = SubElementNode::common_child({lnodes[i2], lnodes[i0]}); + ThrowRequire(nullptr != lnodes[i3] && nullptr != lnodes[i5]); + + const bool diag = determine_diagonal_for_cut_triangle(simplexMethod, lnodes, i0, i1, i2, i3, i5); + + const int sign = (permute_case_id==2) ? 1 : -1; + handle_tri(lnodes, subelement_interface_signs(interface_key, sign), i0,i3,i5, s0,-1,s2, false,true,false); + handle_quad(mesh, lnodes, subelement_interface_signs(interface_key, -sign), i3,i1,i2,i5, s0,s1,s2,-1, false,false,false,true, diag); + } + break; + + case 4: // ls[0]=0 && ls[1]=0 && ls[2]<0 + case 22: // ls[0]=0 && ls[1]=0 && ls[2]>0 + { + const int sign = (permute_case_id==4) ? -1 : 1; + update_interface_signs(interface_key, sign); + } + break; + + case 5: // ls[0]>0 && ls[1]=0 && ls[2]<0 + case 21: // ls[0]<0 && ls[1]=0 && ls[2]>0 + { + lnodes[i5] = SubElementNode::common_child({lnodes[i2], lnodes[i0]}); + ThrowRequire(nullptr != lnodes[i5]); + + const int sign = (permute_case_id==5) ? 1 : -1; + handle_tri(lnodes, subelement_interface_signs(interface_key, sign), i1,i5,i0, -1,s2,s0, true,false,false); + handle_tri(lnodes, subelement_interface_signs(interface_key, -sign), i5,i1,i2, -1,s1,s2, true,false,false); + } + break; + + default: ThrowRuntimeError("Subelement decomposition error. case_id,permute_case_id=" << case_id << "," << permute_case_id); + } + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + debug_subelements(lnodes, interface_key, case_id); + } +} + +bool +SubElement_Tri_3::determine_diagonal_for_cut_triangle(const Simplex_Generation_Method & simplexMethod, const NodeVec & lnodes, const int i0, const int i1, const int i2, const int i3, const int i5) +{ + /* + * 2 o + * / \ + * 5 o \ + * / \ \ + * o---o---o + * 0 3 1 + */ + + // true: connect nodes 3 and 2 + // false: connect nodes 5 and 1 + + if (simplexMethod == CUT_QUADS_BY_GLOBAL_IDENTIFIER) + { + SubElementNodeAncestry ancestry1 = lnodes[i1]->get_ancestry(); + SubElementNodeAncestry ancestry2 = lnodes[i2]->get_ancestry(); + + return ancestry2 < ancestry1; + } + else + { + ThrowRequire(simplexMethod == CUT_QUADS_BY_LARGEST_ANGLE); + + // Angle-based scheme + // Select diagonal that cuts largest angle in quad. Since there isn't an issue with + // conforming, this is always possible (unlike tet). + const int config = ElementObj::evaluate_quad(lnodes[i3],lnodes[i1],lnodes[i2],lnodes[i5]); + return (config == -1); + } +} + +void +SubElement_Tri_3::handle_tri( NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, + const int s0, const int s1, const int s2, + const bool is_interface0, const bool is_interface1, const bool is_interface2) +{ + ThrowRequire(!is_degenerate(lnodes, i0,i1,i2)); + + NodeVec sub_nodes(3,(SubElementNode *)NULL); + std::vector sub_parent_ids(3); + + sub_nodes[0] = lnodes[i0]; + sub_nodes[1] = lnodes[i1]; + sub_nodes[2] = lnodes[i2]; + sub_parent_ids[0] = (s0>= 0) ? my_parent_side_ids[s0] : -1; + sub_parent_ids[1] = (s1>= 0) ? my_parent_side_ids[s1] : -1; + sub_parent_ids[2] = (s2>= 0) ? my_parent_side_ids[s2] : -1; + + std::unique_ptr sub = std::make_unique( sub_nodes, sub_parent_ids, my_owner); + sub->set_interface_signs(subInterfaceSigns); + add_subelement( std::move(sub) ); +} + +void +SubElement_Tri_3::handle_quad( CDMesh & mesh, + NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, + const int s0, const int s1, const int s2, const int s3, + const bool is_interface0, const bool is_interface1, const bool is_interface2, const bool is_interface3, + const bool face ) +{ +#if 1 + if (face) + { + handle_tri( lnodes, subInterfaceSigns, i0,i1,i2, s0,s1,-1, is_interface0,is_interface1,false ); + handle_tri( lnodes, subInterfaceSigns, i2,i3,i0, s2,s3,-1, is_interface2,is_interface3,false ); + } + else + { + handle_tri( lnodes, subInterfaceSigns, i0,i1,i3, s0,-1,s3, is_interface0,false,is_interface3 ); + handle_tri( lnodes, subInterfaceSigns, i2,i3,i1, s2,-1,s1, is_interface2,false,is_interface1 ); + } +#else + NodeVec quad_nodes; + std::vector weights; + const double x0 = 0; + const double y0 = 0; + + quad_nodes.push_back(lnodes[i0]); + weights.push_back(0.25*(1-x0)*(1-y0)); + quad_nodes.push_back(lnodes[i1]); + weights.push_back(0.25*(1+x0)*(1-y0)); + quad_nodes.push_back(lnodes[i2]); + weights.push_back(0.25*(1+x0)*(1+y0)); + quad_nodes.push_back(lnodes[i3]); + weights.push_back(0.25*(1-x0)*(1+y0)); + + lnodes[6] = mesh.create_internal_node( my_owner, quad_nodes, weights ); + + handle_tri(lnodes, ls_index, sign, i0,i1,6, s0,-1,-1); + handle_tri(lnodes, ls_index, sign, i1,i2,6, s1,-1,-1); + handle_tri(lnodes, ls_index, sign, i2,i3,6, s2,-1,-1); + handle_tri(lnodes, ls_index, sign, i3,i0,6, s3,-1,-1); +#endif +} + +bool +SubElement_Tri_3::is_degenerate( NodeVec & lnodes, + const int i0, const int i1, const int i2 ) +{ /* %TRACE% */ /* %TRACE% */ + + if ( lnodes[i0] == lnodes[i1] || + lnodes[i0] == lnodes[i2] || + lnodes[i1] == lnodes[i2] ) + { + // this tri is degenerate with two coincident nodes + return true; + } + + return false; +} + +SubElement_Tet_4::SubElement_Tet_4( + const NodeVec & nodes, + const std::vector & parent_side_ids, + const Mesh_Element * owner) + : SubElement( stk::topology::TETRAHEDRON_4, + nodes, + parent_side_ids, + owner) +{ /* %TRACE% */ /* %TRACE% */ +} + +void SubElement_Tet_4::cut_face_intersection_point_with_permutation(CDMesh & mesh, const std::array & permuteNodes, const std::array & permuteSides, const std::vector & faceNodeWeights, const std::vector & sortedDomains) +{ + const SubElementNode * cutNode = mesh.create_child_internal_or_face_node(my_owner, + {my_nodes[permuteNodes[0]], my_nodes[permuteNodes[1]], my_nodes[permuteNodes[2]]}, + {faceNodeWeights[0], faceNodeWeights[1], faceNodeWeights[2]}); + const auto & previousDomains = cutNode->get_sorted_node_domains(); + ThrowRequire(previousDomains.empty() || sortedDomains == previousDomains); + cutNode->set_node_domains(sortedDomains); + + NodeVec lnodes; + lnodes.reserve(5); + for (int i=0; i<4; ++i) + lnodes.push_back(my_nodes[permuteNodes[i]]); + lnodes.push_back(cutNode); + + handle_tet(lnodes, get_interface_signs(), 0,1,4,3, permuteSides[0],-1,-1,permuteSides[3]); + handle_tet(lnodes, get_interface_signs(), 1,2,4,3, permuteSides[1],-1,-1,permuteSides[3]); + handle_tet(lnodes, get_interface_signs(), 2,0,4,3, permuteSides[2],-1,-1,permuteSides[3]); +} + +void SubElement_Tet_4::cut_interior_intersection_point(CDMesh & mesh, const Vector3d & pCoords, const std::vector & sortedDomains) +{ + const std::vector weights{1.-pCoords[0]-pCoords[1]-pCoords[2], pCoords[0], pCoords[1], pCoords[2]}; + + bool badCut = false; + for (auto && weight : weights) + { + if(weight < mesh.get_snapper().get_edge_tolerance()) + { + if (krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "Skipping cut of interior intersection point because of quality." << stk::diag::dendl; + badCut = true; + break; + } + } + + if (!badCut) + { + const SubElementNode * cutNode = mesh.create_child_internal_or_face_node( my_owner, my_nodes, weights ); + cutNode->set_node_domains(sortedDomains); + + NodeVec lnodes = my_nodes; + lnodes.push_back(cutNode); + + handle_tet(lnodes, get_interface_signs(), 0,3,1,4, -1,-1,-1,0); + handle_tet(lnodes, get_interface_signs(), 1,3,2,4, -1,-1,-1,1); + handle_tet(lnodes, get_interface_signs(), 0,2,3,4, -1,-1,-1,2); + handle_tet(lnodes, get_interface_signs(), 0,1,2,4, -1,-1,-1,3); + } +} + +void +SubElement_Tet_4::build_quadratic_subelements(CDMesh & mesh) +{ /* %TRACE% */ /* %TRACE% */ + + if ( my_subelements.size() > 0 ) + { + for ( auto && subelem : my_subelements ) + { + subelem->build_quadratic_subelements(mesh); + } + return; + } + + // create 1, 10-noded tet + NodeVec sub_nodes = my_nodes; + sub_nodes.resize(10,(SubElementNode *)NULL); + + const stk::topology tet10_topology = stk::topology::TETRAHEDRON_10; + + for (unsigned edge_i=0; edge_i sub = std::make_unique( sub_nodes, my_parent_side_ids, my_owner ); + sub->set_interface_signs(get_interface_signs()); + add_subelement( std::move(sub) ); +} + +struct FaceIntersection +{ + FaceIntersection(const int iFace, const Vector3d & coords, const std::vector & domains) + : face(iFace), + parametricCoords(coords), + sortedDomains(domains) {} + + int face; + Vector3d parametricCoords; + std::vector sortedDomains; +}; + +void +SubElement_Tet_4::cut_face_interior_intersection_points(CDMesh & mesh, const InterfaceID & interface1, const InterfaceID & interface2, int level) +{ /* %TRACE% */ /* %TRACE% */ + + if ( my_subelements.size() > 0 ) + { + for ( auto && subelem : my_subelements ) + { + subelem->cut_face_interior_intersection_points(mesh, interface1, interface2, level); + } + return; + } + + static constexpr std::array,4> permuteNodes{{ {{0,3,1,2}}, {{1,3,2,0}}, {{0,2,3,1}}, {{0,1,2,3}} }}; + static constexpr std::array,4> permuteSides{{ {{2,1,3,0}}, {{0,2,3,1}}, {{3,1,0,2}}, {{0,1,2,3}} }}; + + std::vector faceNodes(3); + // Note: this recursively cuts the first identified face interior intersection point + for (unsigned iFace=0; iFace faceIntersections; + my_owner->fill_face_interior_intersections(faceNodes, interface1, interface2, faceIntersections); + + for (auto && faceIntersection : faceIntersections) + { + const Vector3d & faceCoords = faceIntersection.parametricCoords; + const std::vector faceNodeWeights{1.-faceCoords[0]-faceCoords[1], faceCoords[0], faceCoords[1]}; + bool badCut = false; + for (auto && weight : faceNodeWeights) + { + if (weight < mesh.get_snapper().get_edge_tolerance()) + { + if (krinolog.shouldPrint(LOG_DEBUG)) + krinolog << "Skipping cut of interior face intersection point because of quality." << stk::diag::dendl; + badCut = true; + break; + } + } + if (!badCut) + { + ThrowRequireMsg(level < 8, "Face cut recursion level exceeded."); + cut_face_intersection_point_with_permutation(mesh, permuteNodes[iFace], permuteSides[iFace], faceNodeWeights, faceIntersection.sortedDomains); + cut_face_interior_intersection_points(mesh, interface1, interface2, ++level); + return; + } + } + } +} + +const unsigned ** +SubElement_Tet_4::get_permutation_side_ordinals() +{ + static const unsigned permutation_side_ordinals0[] = { 0, 1, 2, 3 }; + static const unsigned permutation_side_ordinals1[] = { 1, 2, 0, 3 }; + static const unsigned permutation_side_ordinals2[] = { 2, 0, 1, 3 }; + static const unsigned permutation_side_ordinals3[] = { 2, 1, 3, 0 }; + static const unsigned permutation_side_ordinals4[] = { 1, 3, 2, 0 }; + static const unsigned permutation_side_ordinals5[] = { 3, 2, 1, 0 }; + static const unsigned permutation_side_ordinals6[] = { 3, 1, 0, 2 }; + static const unsigned permutation_side_ordinals7[] = { 1, 0, 3, 2 }; + static const unsigned permutation_side_ordinals8[] = { 0, 3, 1, 2 }; + static const unsigned permutation_side_ordinals9[] = { 0, 2, 3, 1 }; + static const unsigned permutation_side_ordinals10[] = { 2, 3, 0, 1 }; + static const unsigned permutation_side_ordinals11[] = { 3, 0, 2, 1 }; + static const unsigned * permutation_side_ordinals[] = + { permutation_side_ordinals0 , permutation_side_ordinals1 , permutation_side_ordinals2, permutation_side_ordinals3 , permutation_side_ordinals4 , permutation_side_ordinals5 , + permutation_side_ordinals6 , permutation_side_ordinals7 , permutation_side_ordinals8, permutation_side_ordinals9, permutation_side_ordinals10, permutation_side_ordinals11 }; + return permutation_side_ordinals; +} + +double SubElement_Tet_4::tet_volume(const std::array & nodes) +{ + return Dot(nodes[3]-nodes[0],Cross(nodes[1]-nodes[0], nodes[2]-nodes[0]))/6.0; +} + +void +SubElement_Tet_4::fix_hanging_children(CDMesh & mesh, const InterfaceID & interface_key, const std::vector & edges_with_children) +{ + int edge_case_id = 0; + for (unsigned i=0; i node_signs = {{0, 0, 0, 0}}; + + if (edge_case_id == 0) // uncut subelement + { + if (my_nodes[0]->node_sign_is_set()) node_signs[0] = my_nodes[0]->get_node_sign(); + if (my_nodes[1]->node_sign_is_set()) node_signs[1] = my_nodes[1]->get_node_sign(); + if (my_nodes[2]->node_sign_is_set()) node_signs[2] = my_nodes[2]->get_node_sign(); + if (my_nodes[3]->node_sign_is_set()) node_signs[3] = my_nodes[3]->get_node_sign(); + + if (!(node_signs[0] >= 0 && node_signs[1] >= 0 && node_signs[2] >= 0 && node_signs[3] >= 0) && + !(node_signs[0] <= 0 && node_signs[1] <= 0 && node_signs[2] <= 0 && node_signs[3] <= 0)) + { + return; + } + } + else + { + for (auto && edge_with_child : edges_with_children) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(topology(), edge_with_child); + const int i0 = edge_node_ordinals[0]; + const int i1 = edge_node_ordinals[1]; + node_signs[i0] = my_nodes[i0]->get_node_sign(); + node_signs[i1] = my_nodes[i1]->get_node_sign(); + } + } + + perform_decomposition(mesh, interface_key, node_signs); + } + else + { + ThrowAssert(case_id == 2 || case_id == 3 || case_id == 4); + + static const int edge_case_permutations [] = + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-9 + 2, 6, -1, -1, 5, -1, -1, -1, -1, -1, // 10-19 + 0, 8, 5, -1, -1, -1, 0, -1, 0, -1, // 20-29 + -1, -1, -1, 1, -1, 4, -1, 3, -1, -1, // 30-39 + -1, 2, 2, -1, -1, -1, -1, -1, -1, 1, // 40-49 + -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, // 50-59 + -1, -1, -1, -1 // 60-63 + }; + + const int permutation = edge_case_permutations[edge_case_id]; + ThrowRequire(permutation >= 0); + + stk::topology topo = stk::topology::TETRAHEDRON_10; + std::vector permute_nodes(10); + topo.permutation_node_ordinals(permutation, permute_nodes.begin()); + + const unsigned i0 = permute_nodes[0]; + const unsigned i1 = permute_nodes[1]; + const unsigned i2 = permute_nodes[2]; + const unsigned i3 = permute_nodes[3]; + const unsigned i5 = permute_nodes[5]; + const unsigned i6 = permute_nodes[6]; + const unsigned i7 = permute_nodes[7]; + const unsigned i8 = permute_nodes[8]; + + const unsigned ** permutation_side_ordinals = get_permutation_side_ordinals(); + const unsigned s0 = permutation_side_ordinals[permutation][0]; + const unsigned s1 = permutation_side_ordinals[permutation][1]; + const unsigned s2 = permutation_side_ordinals[permutation][2]; + const unsigned s3 = permutation_side_ordinals[permutation][3]; + + NodeVec lnodes = my_nodes; + lnodes.resize(10,(SubElementNode *)NULL); + + const int arbitrary_sign = 0; + const auto subInterfaceSigns = subelement_interface_signs(interface_key, arbitrary_sign); + const Simplex_Generation_Method simplexMethod = mesh.get_cdfem_support().get_simplex_generation_method(); + const bool globalIDsAreParallelConsistent = mesh.get_cdfem_support().get_global_ids_are_parallel_consistent(); + + if (case_id == 2) + { + lnodes[i6] = SubElementNode::common_child({lnodes[i0], lnodes[i2]}); + lnodes[i8] = SubElementNode::common_child({lnodes[i1], lnodes[i3]}); + ThrowRequire(nullptr != lnodes[i6] && nullptr != lnodes[i8]); + + + handle_tet( lnodes, subInterfaceSigns, i6,i2,i3,i8, -1,s1,-1,s2 ); + handle_tet( lnodes, subInterfaceSigns, i3,i0,i6,i8, s0,-1,-1,s2 ); + handle_tet( lnodes, subInterfaceSigns, i6,i0,i1,i8, -1,s0,-1,s3 ); + handle_tet( lnodes, subInterfaceSigns, i1,i2,i6,i8, s1,-1,-1,s3 ); + } + else if (case_id == 3) + { + lnodes[i6] = SubElementNode::common_child({lnodes[i0], lnodes[i2]}); + lnodes[i7] = SubElementNode::common_child({lnodes[i0], lnodes[i3]}); + lnodes[i8] = SubElementNode::common_child({lnodes[i1], lnodes[i3]}); + ThrowRequire(nullptr != lnodes[i6] && nullptr != lnodes[i7] && nullptr != lnodes[i8]); + + const bool face0 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i3, i0, i1, i7, i8); + const bool face2 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i0, i3, i2, i7, i6); + + // Connect 6-8 + handle_tet( lnodes, subInterfaceSigns, i1,i2,i6,i8, s1,-1,-1,s3 ); + handle_pyramid( lnodes, subInterfaceSigns, i1,i0,i7,i8,i6, s3,s2,-1,-1,s0, face0 ); + handle_pyramid( lnodes, subInterfaceSigns, i2,i3,i7,i6,i8, s1,s0,-1,-1,s2, face2 ); + } + else if (case_id == 4) + { + lnodes[i5] = SubElementNode::common_child({lnodes[i1], lnodes[i2]}); + lnodes[i7] = SubElementNode::common_child({lnodes[i0], lnodes[i3]}); + lnodes[i8] = SubElementNode::common_child({lnodes[i1], lnodes[i3]}); + ThrowRequire(nullptr != lnodes[i5] && nullptr != lnodes[i7] && nullptr != lnodes[i8]); + + const bool face0 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i3, i0, i1, i7, i8); + const bool face1 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i1, i2, i3, i5, i8); + + // Connect 5-7 + handle_tet( lnodes, subInterfaceSigns, i0,i5,i2,i7, -1,-1,s2,s3 ); + handle_pyramid( lnodes, subInterfaceSigns, i1,i0,i7,i8,i5, s3,-1,-1,s1,s0, face0 ); + handle_pyramid( lnodes, subInterfaceSigns, i5,i8,i3,i2,i7, -1,s0,s2,-1,s1, face1 ); + } + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + debug_subelements(lnodes, interface_key, case_id); + } + } +} + +void +SubElement_Tet_4::determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key) +{ + ThrowAssert(!have_subelements()); + determine_node_signs_on_edge( mesh, interface_key, 0,1 ); + determine_node_signs_on_edge( mesh, interface_key, 1,2 ); + determine_node_signs_on_edge( mesh, interface_key, 0,2 ); + determine_node_signs_on_edge( mesh, interface_key, 0,3 ); + determine_node_signs_on_edge( mesh, interface_key, 1,3 ); + determine_node_signs_on_edge( mesh, interface_key, 2,3 ); +} + +void +SubElement_Tet_4::determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key) +{ + ThrowAssert(!have_subelements()); + + const Simplex_Generation_Method simplexMethod = mesh.get_cdfem_support().get_simplex_generation_method(); + if (simplexMethod == CUT_QUADS_BY_NEAREST_EDGE_CUT) + { + // nodal edge cut length based criterion + // Use globally consistent comparison at nodes based on the shortest relative edge length for the cut edges that use the node. + + // The general idea is to prefer edges that emanate away from nodes that have nearby cuts. This, for perfectly shaped elements, + // will cut the largest angles. + + determine_node_scores_on_edge( mesh, interface_key, 0,1 ); + determine_node_scores_on_edge( mesh, interface_key, 1,2 ); + determine_node_scores_on_edge( mesh, interface_key, 0,2 ); + determine_node_scores_on_edge( mesh, interface_key, 0,3 ); + determine_node_scores_on_edge( mesh, interface_key, 1,3 ); + determine_node_scores_on_edge( mesh, interface_key, 2,3 ); + } + else if (simplexMethod == CUT_QUADS_BY_LARGEST_ANGLE) + { + // nodal face angle criterion + // Use globally consistent comparison at nodes based on the largest angle for the cut faces that use the node. + // This does not rely on perfectly shaped elements to cut the larges angles. + + determine_node_scores_on_face( mesh, interface_key, 0,1,3 ); + determine_node_scores_on_face( mesh, interface_key, 1,2,3 ); + determine_node_scores_on_face( mesh, interface_key, 2,0,3 ); + determine_node_scores_on_face( mesh, interface_key, 2,0,3 ); + } +} + + +static std::pair get_quad_angle_measures(const Vector3d & x0, const Vector3d & x1, const Vector3d & x2, const Vector3d & x3) +{ + std::array sides{x1-x0, x2-x1, x3-x2, x0-x3}; + for (auto && side : sides) side.unitize(); + + // return measure02,measure13 where measureAB=std::max(-cos(A),-cos(B)) + return {std::max(Dot(sides[3],sides[0]), Dot(sides[1],sides[2])), std::max(Dot(sides[0],sides[1]), Dot(sides[2],sides[3]))}; +} + +static void determine_node_scores_on_triangle_face( const std::array & faceNodes ) +{ + /* + * 2 o + * / \ + * / o 4 + * / / \ + * o---o---o + * 0 3 1 + */ + + const std::pair measure23Andmeasure04 = get_quad_angle_measures(faceNodes[2]->coordinates(), faceNodes[0]->coordinates(), faceNodes[3]->coordinates(), faceNodes[4]->coordinates()); + faceNodes[2]->set_node_score(measure23Andmeasure04.first); + faceNodes[0]->set_node_score(measure23Andmeasure04.second); +} + +void +SubElement_Tet_4::determine_node_scores_on_face( const CDMesh & mesh, const InterfaceID interface, const int i0, const int i1, const int i2 ) +{ + + const SubElementNode * parent0 = my_nodes[i0]; + const SubElementNode * parent1 = my_nodes[i1]; + const SubElementNode * parent2 = my_nodes[i2]; + + if (parent0->get_node_on_interface() || parent1->get_node_on_interface() || parent2->get_node_on_interface()) return; + + const SubElementNode * child0 = SubElementNode::common_child({parent0, parent1}); + const SubElementNode * child1 = SubElementNode::common_child({parent1, parent2}); + const SubElementNode * child2 = SubElementNode::common_child({parent2, parent0}); + + const int caseId = + ((child0 == nullptr) ? 0 : 1) + + ((child1 == nullptr) ? 0 : 2) + + ((child2 == nullptr) ? 0 : 4); + + if (caseId == 3) + determine_node_scores_on_triangle_face({parent0, parent1, parent2, child0, child1}); + else if (caseId == 5) + determine_node_scores_on_triangle_face({parent2, parent0, parent1, child2, child0}); + else if (caseId == 6) + determine_node_scores_on_triangle_face({parent1, parent2, parent0, child1, child2}); +} + +void +SubElement_Tet_4::decompose_edges(CDMesh & mesh, const InterfaceID interface_key) +{ /* %TRACE% */ /* %TRACE% */ + + process_edge( mesh, interface_key, 0,1 ); + process_edge( mesh, interface_key, 1,2 ); + process_edge( mesh, interface_key, 0,2 ); + process_edge( mesh, interface_key, 0,3 ); + process_edge( mesh, interface_key, 1,3 ); + process_edge( mesh, interface_key, 2,3 ); +} + +void +SubElement_Tet_4::perform_decomposition(CDMesh & mesh, const InterfaceID interface_key, const std::array & node_signs) +{ /* %TRACE% */ /* %TRACE% */ + + // create between 4 to 6 conforming tetrahedral subelements + + const int case_id = + (node_signs[0]+1) + + (node_signs[1]+1)*3 + + (node_signs[2]+1)*9 + + (node_signs[3]+1)*27; + + NodeVec lnodes = my_nodes; + lnodes.resize(10,(SubElementNode *)NULL); + + static const unsigned case_permutations[] = + { 0, 0, 0, 1, 0, 0, 1, 5, 0, 2, // 0-9 + 2, 6, 1, 0, 0, 1, 1,10, 2, 2, // 10-19 + 2,11, 2, 4, 1, 8, 4, 4, 3, 3, // 20-29 + 4, 3, 3, 9, 5, 7, 7, 6, 6, 9, // 30-39 + 0, 9, 9, 6, 7, 7, 7, 9,11, 3, // 40-49 + 4, 3, 3, 4, 4, 8, 3, 4, 4,11, // 50-59 + 4, 2, 2,10, 8, 1,10, 0, 1, 6, // 60-69 + 2, 2, 7, 5, 1, 0, 0, 1, 0, 0, // 70-79 + 0 }; // 80 + + stk::topology topo = stk::topology::TETRAHEDRON_10; + std::vector permute_nodes(10); + topo.permutation_node_ordinals(case_permutations[case_id], permute_nodes.begin()); + + const unsigned i0 = permute_nodes[0]; + const unsigned i1 = permute_nodes[1]; + const unsigned i2 = permute_nodes[2]; + const unsigned i3 = permute_nodes[3]; + const unsigned i4 = permute_nodes[4]; + const unsigned i5 = permute_nodes[5]; + const unsigned i6 = permute_nodes[6]; + const unsigned i7 = permute_nodes[7]; + const unsigned i8 = permute_nodes[8]; + + const unsigned ** permutation_side_ordinals = get_permutation_side_ordinals(); + const unsigned s0 = permutation_side_ordinals[case_permutations[case_id]][0]; + const unsigned s1 = permutation_side_ordinals[case_permutations[case_id]][1]; + const unsigned s2 = permutation_side_ordinals[case_permutations[case_id]][2]; + const unsigned s3 = permutation_side_ordinals[case_permutations[case_id]][3]; + + static const unsigned permute_case_ids[] = + { 0, 1, 2, 1, 4, 5, 2, 5, 8, 1, // 0-9 + 4, 5, 4,13,14, 5,14,75, 2, 5, // 10-19 + 8, 5,14,75, 8,75,78, 1, 4, 5, // 20-29 + 4,13,14, 5,14,75, 4,13,14,13, // 30-39 + 40,67,14,67,76, 5,14,75,14,67, // 40-49 + 76,75,76,79, 2, 5, 8, 5,14,75, // 50-59 + 8,75,78, 5,14,75,14,67,76,75, // 60-69 + 76,79, 8,75,78,75,76,79,78,79, // 70-79 + 80 }; // 80 + + const int permute_case_id = permute_case_ids[case_id]; + + // FIXME: Remove this diagnostic + std::vector node_val(4); + node_val[3] = case_id/27; + node_val[2] = (case_id-27*node_val[3])/9; + node_val[1] = (case_id-27*node_val[3]-9*node_val[2])/3; + node_val[0] = case_id-27*node_val[3]-9*node_val[2]-3*node_val[1]; + ThrowRequire(permute_case_id == (node_val[i0] + node_val[i1]*3 + node_val[i2]*9 + node_val[i3]*27)); + + const Simplex_Generation_Method simplexMethod = mesh.get_cdfem_support().get_simplex_generation_method(); + const bool globalIDsAreParallelConsistent = mesh.get_cdfem_support().get_global_ids_are_parallel_consistent(); + + //krinolog << "permute_case_id " << permute_case_id << stk::diag::dendl; + + switch (permute_case_id) + { + case 0: // ls[0]<0 && ls[1]<0 && ls[2]<0 && ls[3]<0 + case 1: // ls[0]=0 && ls[1]<0 && ls[2]<0 && ls[3]<0 + case 4: // ls[0]=0 && ls[1]=0 && ls[2]<0 && ls[3]<0 + { + update_interface_signs(interface_key, -1); + } + break; + + case 40: // ls[0]=0 && ls[1]=0 && ls[2]=0 && ls[3]=0 + case 76: // ls[0]=0 && ls[1]=0 && ls[2]>0 && ls[3]>0 + case 79: // ls[0]=0 && ls[1]>0 && ls[2]>0 && ls[3]>0 + case 80: // ls[0]>0 && ls[1]>0 && ls[2]>0 && ls[3]>0 + { + update_interface_signs(interface_key, +1); + } + break; + + case 2: // ls[0]>0 && ls[1]<0 && ls[2]<0 && ls[3]<0 + case 78: // ls[0]<0 && ls[1]>0 && ls[2]>0 && ls[3]>0 + { + const int sign = (permute_case_id==2) ? -1 : 1; + + lnodes[i4] = SubElementNode::common_child({lnodes[i0], lnodes[i1]}); + lnodes[i6] = SubElementNode::common_child({lnodes[i0], lnodes[i2]}); + lnodes[i7] = SubElementNode::common_child({lnodes[i0], lnodes[i3]}); + ThrowRequire(nullptr != lnodes[i4] && nullptr != lnodes[i6] && nullptr != lnodes[i7]); + + // face0: true: connect 4 and 3, false: connect 7 and 1 + // face2: true: connect 7 and 2, false: connect 6 and 3 + // face3: true: connect 6 and 1, false: connect 4 and 2 + const bool face0 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i0, i1, i3, i4, i7); + const bool face2 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i0, i3, i2, i7, i6); + const bool face3 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i0, i2, i1, i6, i4); + + handle_tet( lnodes, subelement_interface_signs(interface_key, -sign), i6,i4,i7,i0, s3,s0,s2,-1 ); + handle_wedge( mesh, lnodes, subelement_interface_signs(interface_key, sign), i4,i7,i6,i1,i3,i2, s0,s2,s3,-1,s1, face0,face2,!face3 ); + } + break; + + case 5: // ls[0]>0 && ls[1]=0 && ls[2]<0 && ls[3]<0 + case 75: // ls[0]<0 && ls[1]=0 && ls[2]>0 && ls[3]>0 + { + const int sign = (permute_case_id==5) ? -1 : 1; + + lnodes[i6] = SubElementNode::common_child({lnodes[i0], lnodes[i2]}); + lnodes[i7] = SubElementNode::common_child({lnodes[i0], lnodes[i3]}); + ThrowRequire(nullptr != lnodes[i6] && nullptr != lnodes[i7]); + + // face2: true: connect 7 and 2, false: connect 6 and 3 + const bool face2 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i0, i3, i2, i7, i6); + + handle_tet( lnodes, subelement_interface_signs(interface_key, -sign), i6,i1,i7,i0, s3,s0,s2,-1 ); + handle_pyramid( lnodes, subelement_interface_signs(interface_key, sign), i7,i6,i2,i3,i1, -1,s3,s1,s0,s2, face2 ); + } + break; + + case 8: // ls[0]>0 && ls[1]>0 && ls[2]<0 && ls[3]<0 + { + lnodes[i5] = SubElementNode::common_child({lnodes[i1], lnodes[i2]}); + lnodes[i6] = SubElementNode::common_child({lnodes[i0], lnodes[i2]}); + lnodes[i7] = SubElementNode::common_child({lnodes[i0], lnodes[i3]}); + lnodes[i8] = SubElementNode::common_child({lnodes[i1], lnodes[i3]}); + ThrowRequire(nullptr != lnodes[i5] && nullptr != lnodes[i6] && nullptr != lnodes[i7] && nullptr != lnodes[i8]); + + // face0: true: connect 7 and 1, false: connect 8 and 0 + // face1: true: connect 5 and 3, false: connect 8 and 2 + // face2: true: connect 7 and 2, false: connect 6 and 3 + // face3: true: connect 5 and 0, false: connect 6 and 1 + const bool face0 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i3, i0, i1, i7, i8); + const bool face1 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i1, i2, i3, i5, i8); + const bool face2 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i0, i3, i2, i7, i6); + const bool face3 = determine_diagonal_for_cut_triangular_face(simplexMethod, globalIDsAreParallelConsistent, lnodes, i2, i1, i0, i5, i6); + + // face 4: true: connect 6 and 8, false: connect 7 and 5 + const bool face4 = ElementObj::determine_diagonal_for_internal_quad_of_cut_tet_from_edge_nodes(simplexMethod, lnodes[i8], lnodes[i5], lnodes[i6], lnodes[i7], + face0, face1, face2, face3); + + ThrowAssert( mesh.num_ls_fields() > 1 || + simplexMethod != CUT_QUADS_BY_GLOBAL_IDENTIFIER || + face4 == ElementObj::determine_diagonal_for_internal_quad_of_cut_tet_from_owner_nodes(lnodes[i0], lnodes[i1], lnodes[i2], lnodes[i3]) ); + + handle_wedge( mesh, lnodes, subelement_interface_signs(interface_key, -1), i8,i3,i7,i5,i2,i6, s1,s2,-1,s0,s3, !face1,!face2,face4 ); + handle_wedge( mesh, lnodes, subelement_interface_signs(interface_key, 1), i8,i1,i5,i7,i0,i6, s0,s3,-1,s1,s2, !face0,!face3,face4 ); + } + break; + + case 13: // ls[0]=0 && ls[1]=0 && ls[2]=0 && ls[3]<0 + case 67: // ls[0]=0 && ls[1]=0 && ls[2]=0 && ls[3]>0 + { + const int sign = (permute_case_id==13) ? -1 : 1; + update_interface_signs(interface_key, sign); + } + break; + + case 14: // ls[0]>0 && ls[1]=0 && ls[2]=0 && ls[3]<0 + { + lnodes[i7] = SubElementNode::common_child({lnodes[i0], lnodes[i3]}); + ThrowRequire(nullptr != lnodes[i7]); + + handle_tet( lnodes, subelement_interface_signs(interface_key, 1), i1,i7,i2,i0, s0,s2,s3,-1 ); + handle_tet( lnodes, subelement_interface_signs(interface_key, -1), i2,i7,i1,i3, s2,s0,s1,-1 ); + } + break; + + default: ThrowRuntimeError("Subelement decomposition error."); + } + + if (krinolog.shouldPrint(LOG_DEBUG)) + { + debug_subelements(lnodes, interface_key, case_id); + } +} + +bool SubElement_Tet_4::determine_diagonal_for_cut_triangular_face(const Simplex_Generation_Method & simplexMethod, const bool globalIDsAreParallelConsistent, const NodeVec & lnodes, const int i0, const int i1, const int i2, const int i3, const int i5) +{ + /* + * 2 o + * / \ + * 5 o \ + * / \ \ + * o---o---o + * 0 3 1 + */ + + // true: connect nodes 3 and 2 + // false: connect nodes 5 and 1 + + if (simplexMethod == CUT_QUADS_BY_GLOBAL_IDENTIFIER) + { + return lnodes[i2]->get_ancestry() < lnodes[i1]->get_ancestry(); + } + else + { + ThrowRequire(simplexMethod == CUT_QUADS_BY_LARGEST_ANGLE || simplexMethod == CUT_QUADS_BY_NEAREST_EDGE_CUT); + return SubElementNode::higher_priority_by_score_then_ancestry(*lnodes[i2],*lnodes[i1], globalIDsAreParallelConsistent); + } +} + +void +SubElement::get_edge_position( + const SubElementNode * n0, + const SubElementNode * n1, + const SubElementNode * n2, + double & position ) +{ + const SubElementEdgeNode * edge_child = dynamic_cast( n2 ); + ThrowRequire(NULL != edge_child); + position = edge_child->get_position(n0,n1); +} + +void +SubElement_Tet_4::handle_pyramid( NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, const int i4, + const int s0, const int s1, const int s2, const int s3, const int s4, + const bool face4 ) +{ + if (face4) + { + handle_tet( lnodes, subInterfaceSigns, i0,i1,i2,i4, s0,s1,-1,s4 ); + handle_tet( lnodes, subInterfaceSigns, i2,i3,i0,i4, s2,s3,-1,s4 ); + } + else + { + handle_tet( lnodes, subInterfaceSigns, i0,i1,i3,i4, s0,-1,s3,s4 ); + handle_tet( lnodes, subInterfaceSigns, i2,i3,i1,i4, s2,-1,s1,s4 ); + } +} + +void +SubElement_Tet_4::handle_tet( NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, + const int s0, const int s1, const int s2, const int s3) +{ + ThrowRequire(!is_degenerate(lnodes, i0,i1,i2,i3)); + + NodeVec sub_nodes(4,(SubElementNode *)NULL); + std::vector sub_parent_ids(4); + + sub_nodes[0] = lnodes[i0]; + sub_nodes[1] = lnodes[i1]; + sub_nodes[2] = lnodes[i2]; + sub_nodes[3] = lnodes[i3]; + sub_parent_ids[0] = (s0>= 0) ? my_parent_side_ids[s0] : -1; + sub_parent_ids[1] = (s1>= 0) ? my_parent_side_ids[s1] : -1; + sub_parent_ids[2] = (s2>= 0) ? my_parent_side_ids[s2] : -1; + sub_parent_ids[3] = (s3>= 0) ? my_parent_side_ids[s3] : -1; + + std::unique_ptr sub = std::make_unique( sub_nodes, sub_parent_ids, my_owner); + sub->set_interface_signs(subInterfaceSigns); + add_subelement( std::move(sub) ); +} + +void +SubElement_Tet_4::handle_wedge( CDMesh & mesh, + NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, const int i4, const int i5, + const int s0, const int s1, const int s2, const int s3, const int s4, + const bool face0, const bool face1, const bool face2 ) +{ +/* + * PARENT Linear 6-Node Wedge Nodes + * 5 (SPACE_DIM=3!) + * . o + * . / \ + * . / \ Face_Quad_4_3D() 0-1-4-3 + * . / \ Face_Quad_4_3D() 1-2-5-4 + * . / \ Face_Quad_4_3D() 0-3-5-2 + * 2 . o---------o 4 Face_Tri_3_3D() 0-2-1 + * o . 3 . Face_Tri_3_3D() 3-4-5 + * /.\ . + * /. \ . + * /. \ . + * /. \ . + * o---------o + * 0 1 + * + */ + // face0: true: connect 0 and 4, false: connect 1 and 3 + // face1: true: connect 1 and 5, false: connect 2 and 4 + // face2: true: connect 0 and 5, false: connect 2 and 3 + + std::vector wedge_nodes(6); + wedge_nodes[0] = i0; + wedge_nodes[1] = i1; + wedge_nodes[2] = i2; + wedge_nodes[3] = i3; + wedge_nodes[4] = i4; + wedge_nodes[5] = i5; + + std::vector wedge_sides(5); + wedge_sides[0] = s0; + wedge_sides[1] = s1; + wedge_sides[2] = s2; + wedge_sides[3] = s3; + wedge_sides[4] = s4; + + // Each of the 3 quad faces can be subdivided in 2 ways, giving a total of 8 possible decompositions. + // 2 of these are illegal, however, since they don't result in tetrahedra. + + static const unsigned tet_nodes0[] = { 0,5,4,3, 0,2,1,5, 0,1,4,5 }; + static const unsigned tet_nodes1[] = { 1,3,5,4, 1,0,2,5, 1,0,5,3 }; + static const unsigned tet_nodes2[] = { 0,5,4,3, 0,2,1,4, 0,2,4,5 }; + static const unsigned tet_nodes3[] = { 0,0,0,0, 0,0,0,0, 0,0,0,0 }; // illegal + static const unsigned tet_nodes4[] = { 0,0,0,0, 0,0,0,0, 0,0,0,0 }; // illegal + static const unsigned tet_nodes5[] = { 1,3,5,4, 1,0,2,3, 1,2,5,3 }; + static const unsigned tet_nodes6[] = { 2,4,3,5, 2,1,0,4, 2,0,3,4 }; + static const unsigned tet_nodes7[] = { 2,4,3,5, 2,1,0,3, 2,1,3,4 }; + static const unsigned * tet_node_map[] = { tet_nodes0 , tet_nodes1 , tet_nodes2, tet_nodes3 , tet_nodes4 , tet_nodes5, tet_nodes6, tet_nodes7 }; + + static const int tet_sides0[] = { 2, 4, 0,-1, 2, 1,-1, 3, -1, 1,-1, 0 }; + static const int tet_sides1[] = { 0, 4, 1,-1, -1, 2, 1, 3, 0, 2,-1,-1 }; + static const int tet_sides2[] = { 2, 4, 0,-1, -1, 1, 0, 3, 2, 1,-1,-1 }; + static const int tet_sides3[] = {-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 }; // illegal + static const int tet_sides4[] = {-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 }; // illegal + static const int tet_sides5[] = { 0, 4, 1,-1, 0, 2,-1, 3, -1, 2,-1, 1 }; + static const int tet_sides6[] = { 1, 4, 2,-1, 1, 0,-1, 3, -1, 0,-1, 2 }; + static const int tet_sides7[] = { 1, 4, 2,-1, -1, 0, 2, 3, 1, 0,-1,-1 }; + static const int * tet_side_map[] = { tet_sides0 , tet_sides1 , tet_sides2 , tet_sides3 , tet_sides4 , tet_sides5, tet_sides6, tet_sides7 }; + + const unsigned case_id = + (face0 ? 0 : 1) + + (face1 ? 0 : 2) + + (face2 ? 0 : 4); + + //krinolog << "Wedge case_id = " << case_id << stk::diag::dendl; + + if (case_id < 3 || case_id > 4) + { + const unsigned * tet_nodes = tet_node_map[case_id]; + const int * tet_sides = tet_side_map[case_id]; + + unsigned lnn[12]; + int lsn[12]; + + for (int n=0; n<12; ++n) + { + lnn[n] = wedge_nodes[tet_nodes[n]]; + lsn[n] = (tet_sides[n]<0) ? -1 : wedge_sides[tet_sides[n]]; + } + + handle_tet(lnodes, subInterfaceSigns, lnn[0], lnn[1], lnn[2], lnn[3], lsn[0], lsn[1], lsn[2], lsn[3]); + handle_tet(lnodes, subInterfaceSigns, lnn[4], lnn[5], lnn[6], lnn[7], lsn[4], lsn[5], lsn[6], lsn[7]); + handle_tet(lnodes, subInterfaceSigns, lnn[8], lnn[9], lnn[10], lnn[11], lsn[8], lsn[9], lsn[10], lsn[11]); + } + else + { + ThrowRequire(case_id == 3 || case_id == 4); + if(krinolog.shouldPrint(LOG_DEBUG)) krinolog << "Schonhardt's polyhedron formed, Adding Steiner point." << "\n"; + + // Schonhardt's polyhedra should never be forced now that diagonals are cut using a globally consistent nodal criterion + ThrowRequireMsg(false, "Schonhardt polyhedron found. This should not happen."); + + NodeVec wedge_nodevec(6); + wedge_nodevec[0] = lnodes[i0]; + wedge_nodevec[1] = lnodes[i1]; + wedge_nodevec[2] = lnodes[i2]; + wedge_nodevec[3] = lnodes[i3]; + wedge_nodevec[4] = lnodes[i4]; + wedge_nodevec[5] = lnodes[i5]; + + const Vector3d & v0 = lnodes[i0]->owner_coords(my_owner); + const Vector3d & v1 = lnodes[i1]->owner_coords(my_owner); + const Vector3d & v2 = lnodes[i2]->owner_coords(my_owner); + const Vector3d & v3 = lnodes[i3]->owner_coords(my_owner); + const Vector3d & v4 = lnodes[i4]->owner_coords(my_owner); + const Vector3d & v5 = lnodes[i5]->owner_coords(my_owner); + + std::vector weights(6); + + // Use the centroid of 1 of the elements that will be within the volume of the wedge depending + // on which way that face2 is cut (which is on the internal quad, which may be non-planar). + // This is not guaranteed, however, to always produce tets that stay within the deformed wedge. + if (face2) + { + weights[0] = 1./4.; + weights[1] = 1./4.; + weights[4] = 1./4.; + weights[5] = 1./4.; + } + else + { + weights[1] = 1./4.; + weights[2] = 1./4.; + weights[3] = 1./4.; + weights[4] = 1./4.; + } + + const SubElementNode * centroid = mesh.create_steiner_node( my_owner, wedge_nodevec, weights ); + + const int i6 = lnodes.size(); + lnodes.push_back(centroid); + const Vector3d & v6 = lnodes[i6]->owner_coords(my_owner); + + // create 8 tets + handle_tet(lnodes, subInterfaceSigns, i0, i2, i1, i6, -1, -1, -1, s3); + handle_tet(lnodes, subInterfaceSigns, i3, i4, i5, i6, -1, -1, -1, s4); + + if (tet_volume({{v0, v2, v1, v6}}) < 0. || tet_volume({{v3, v4, v5, v6}}) < 0.) + { + krinolog << "Error: Schonhard polyhedron decomposition includes inverted tets." << stk::diag::dendl; + } + + if (face0) + { + handle_tet(lnodes, subInterfaceSigns, i0, i1, i4, i6, -1, -1, -1, s0); + handle_tet(lnodes, subInterfaceSigns, i0, i4, i3, i6, -1, -1, -1, s0); + + if (tet_volume({{v0, v1, v4, v6}}) < 0. || tet_volume({{v0, v4, v3, v6}}) < 0.) + { + krinolog << "Error: Schonhard polyhedron decomposition includes inverted tets." << stk::diag::dendl; + } + } + else + { + handle_tet(lnodes, subInterfaceSigns, i0, i1, i3, i6, -1, -1, -1, s0); + handle_tet(lnodes, subInterfaceSigns, i1, i4, i3, i6, -1, -1, -1, s0); + + if (tet_volume({{v0, v1, v3, v6}}) < 0. || tet_volume({{v1, v4, v3, v6}}) < 0.) + { + krinolog << "Error: Schonhard polyhedron decomposition includes inverted tets." << stk::diag::dendl; + } + } + + if (face1) + { + handle_tet(lnodes, subInterfaceSigns, i1, i2, i5, i6, -1, -1, -1, s1); + handle_tet(lnodes, subInterfaceSigns, i1, i5, i4, i6, -1, -1, -1, s1); + + if (tet_volume({{v1, v2, v5, v6}}) < 0. || tet_volume({{v1, v5, v4, v6}}) < 0.) + { + krinolog << "Error: Schonhard polyhedron decomposition includes inverted tets." << stk::diag::dendl; + } + } + else + { + handle_tet(lnodes, subInterfaceSigns, i1, i2, i4, i6, -1, -1, -1, s1); + handle_tet(lnodes, subInterfaceSigns, i2, i5, i4, i6, -1, -1, -1, s1); + + if (tet_volume({{v1, v2, v4, v6}}) < 0. || tet_volume({{v2, v5, v4, v6}}) < 0.) + { + krinolog << "Error: Schonhard polyhedron decomposition includes inverted tets." << stk::diag::dendl; + } + } + + if (face2) + { + handle_tet(lnodes, subInterfaceSigns, i0, i3, i5, i6, -1, -1, -1, s2); + handle_tet(lnodes, subInterfaceSigns, i0, i5, i2, i6, -1, -1, -1, s2); + + if (tet_volume({{v0, v3, v5, v6}}) < 0. || tet_volume({{v0, v5, v2, v6}}) < 0.) + { + krinolog << "Error: Schonhard polyhedron decomposition includes inverted tets." << stk::diag::dendl; + } + } + else + { + handle_tet(lnodes, subInterfaceSigns, i0, i3, i2, i6, -1, -1, -1, s2); + handle_tet(lnodes, subInterfaceSigns, i3, i5, i2, i6, -1, -1, -1, s2); + + if (tet_volume({{v0, v3, v2, v6}}) < 0. || tet_volume({{v3, v5, v2, v6}}) < 0.) + { + krinolog << "Error: Schonhard polyhedron decomposition includes inverted tets." << stk::diag::dendl; + } + } + } +} + +double +SubElement::parametric_distance( const SubElementNode * node0, const SubElementNode * node1 ) +{ + if (node0->is_mesh_node() && node1->is_mesh_node()) + return 1.0; + + std::map node0_stencil; + std::map node1_stencil; + node0->build_stencil(node0_stencil); + node1->build_stencil(node1_stencil); + + // parametric distance = sqrt(0.5*sum(dx_i^2)) + + double sum_sqr_dist = 0.; + for (auto && entry : node0_stencil) + { + const SubElementNode * parent = entry.first; + const double wt0 = entry.second; + + auto it = node1_stencil.find(parent); + const double wt1 = (it == node1_stencil.end()) ? 0.0 : (it->second); + + sum_sqr_dist += (wt0 - wt1)*(wt0 - wt1); + } + for (auto && entry : node1_stencil) + { + const SubElementNode * parent = entry.first; + const double wt1 = entry.second; + + auto it = node0_stencil.find(parent); + if (it == node0_stencil.end()) + { + sum_sqr_dist += wt1*wt1; + } + } + + return std::sqrt(0.5*sum_sqr_dist); +} + +void set_node_signs_for_edge(const InterfaceID interface, const SubElementNode * node1, const SubElementNode * node2, const int node1Sign, const int node2Sign, const double crossingLocation, const CDFEM_Snapper & snapper) +{ + if (node1Sign != node2Sign) + { + // determine tolerance for this edge that may have already been cut + const bool always_snap = snapper.always_snap(); + const double edge_tol = always_snap ? 0.0 : snapper.get_edge_tolerance()/SubElement::parametric_distance(node1, node2); + + if (always_snap || edge_tol > 0.5) + { + if (crossingLocation < 0.5) + { + node1->set_node_sign(0); + } + else + { + node2->set_node_sign(0); + } + } + else + { + if (crossingLocation < edge_tol) + { + node1->set_node_sign(0); + } + else if (crossingLocation > 1.-edge_tol) + { + node2->set_node_sign(0); + } + } + } + + node1->set_node_sign(node1Sign); + node2->set_node_sign(node2Sign); +} + +void +SubElement::determine_node_signs_on_edge( const CDMesh & mesh, const InterfaceID interface, const int i0, const int i1 ) +{ /* %TRACE% */ /* %TRACE% */ + + if (my_owner->have_interface(interface)) + { + const SubElementNode * parent1 = my_nodes[i0]; + const SubElementNode * parent2 = my_nodes[i1]; + + if (parent1->captures_interface(interface)) + parent1->set_node_sign(0); + if (parent2->captures_interface(interface)) + parent2->set_node_sign(0); + + const int sign = myInterfaceSigns[my_owner->get_interface_index(interface)]; + if (sign == 0) + { + const int sign1 = my_owner->interface_node_sign(interface, parent1); + const int sign2 = my_owner->interface_node_sign(interface, parent2); + + const double position = (sign1 == sign2) ? (-1.0) : my_owner->interface_crossing_position(interface, parent1, parent2); + set_node_signs_for_edge(interface, parent1, parent2, sign1, sign2, position, mesh.get_snapper()); + } + } +} + +void +SubElement::determine_node_scores_on_edge( const CDMesh & mesh, const InterfaceID interface, const int i0, const int i1 ) +{ /* %TRACE% */ /* %TRACE% */ + + const SubElementNode * parent1 = my_nodes[i0]; + const SubElementNode * parent2 = my_nodes[i1]; + + if (parent1->get_node_on_interface() || parent2->get_node_on_interface()) return; + + const SubElementNode * child = SubElementNode::common_child({parent1, parent2}); + + if (child) + { + const NodeVec & parents = child->get_parents(); + const std::vector parent_weights = child->get_parent_weights(); + for (size_t i=0; iset_node_score(1.-parent_weights[i]); + } +} + +void +SubElement::process_edge( CDMesh & mesh, const InterfaceID interface, const int i0, const int i1 ) +{ /* %TRACE% */ /* %TRACE% */ + const SubElementNode * parent1 = my_nodes[i0]; + const SubElementNode * parent2 = my_nodes[i1]; + + if (parent1->get_node_on_interface() || parent2->get_node_on_interface()) return; + + const SubElementNode * subnode = SubElementNode::common_child({parent1, parent2}); + + if (subnode == nullptr && + parent1->node_sign_is_set() && + parent2->node_sign_is_set() && + parent1->get_node_sign() == -parent2->get_node_sign() && + have_interface(interface)) + { + const double position = my_owner->interface_crossing_position(interface, parent1, parent2); + ThrowRequireMsg(position > 0. && position < 1., "Error process_edge " << position << " " << parent1->get_node_sign() << " " << parent2->get_node_sign()); + + std::unique_ptr newNode = std::make_unique(my_owner, position, parent1, parent2); + subnode = mesh.add_managed_node(std::move(newNode)); + + // add subnode as child to parents + parent1->add_child(subnode); + parent2->add_child(subnode); + } +} + +void +SubElement::determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key) +{ + if(have_subelements()) + { + for ( auto && subelem : my_subelements ) + { + subelem->SubElement::determine_node_signs(mesh, interface_key); + } + return; + } + + determine_node_signs(mesh, interface_key); +} + +void +SubElement::determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key) +{ + if(have_subelements()) + { + for ( auto && subelem : my_subelements ) + { + subelem->SubElement::determine_node_scores(mesh, interface_key); + } + return; + } + + determine_node_scores(mesh, interface_key); +} + +void +SubElement::decompose(CDMesh & mesh, const InterfaceID interface_key) +{ + if(have_subelements()) + { + for ( auto && subelem : my_subelements ) + { + subelem->decompose(mesh, interface_key); + } + return; + } + + decompose_edges(mesh, interface_key); +} + +std::vector +SubElement::get_edges_with_children(const InterfaceID & interface) const +{ + // Iterate edges looking for any common children of the edge nodes + const stk::topology Top = topology(); + const int num_edges = Top.num_edges(); + + std::vector edgesWithChildren; + for ( int edge = 0; edge < num_edges; ++edge ) + { + const unsigned * edge_node_ordinals = get_edge_node_ordinals(Top, edge); + + const SubElementNode * node0 = my_nodes[edge_node_ordinals[0]]; + const SubElementNode * node1 = my_nodes[edge_node_ordinals[1]]; + const SubElementNode * child = SubElementNode::common_child({node0, node1}); + if( child ) + { + if(krinolog.shouldPrint(LOG_DEBUG)) + { + krinolog << "Found hanging node on edge " << edge << " of element id=" << entityId() << "\n"; + krinolog << " Ancestry: " << child->get_ancestry() << "\n"; + } + edgesWithChildren.push_back(edge); + } + } + return edgesWithChildren; +} + +void +SubElement::handle_hanging_children(CDMesh & mesh, const InterfaceID & interface) +{ + if(have_subelements()) + { + for ( auto && subelem : my_subelements ) + { + subelem->handle_hanging_children(mesh, interface); + } + return; + } + + const std::vector edgesWithChildren = get_edges_with_children(interface); + + fix_hanging_children(mesh, interface, edgesWithChildren); + + for ( auto && subelem : my_subelements ) + { + ThrowRequire(!subelem->have_edges_with_children()); + } +} + +void +SubElement::build_quadratic_subelements(CDMesh & mesh) +{ + ThrowRequireMsg(have_subelements(), "This subelement type does not support quadratic subelements."); + for ( auto && subelem : my_subelements ) + { + subelem->build_quadratic_subelements(mesh); + } +} + +void +SubElement::cut_face_interior_intersection_points(CDMesh & mesh, const InterfaceID & interface1, const InterfaceID & interface2, int level) +{ /* %TRACE% */ /* %TRACE% */ + + if (topology().num_faces() == 0) + return; + + ThrowRequireMsg(false, "This subelement type does not support cut_face_interior_intersection_points: " << topology()); +} + +bool +SubElement_Tet_4::is_degenerate( NodeVec & lnodes, + const int i0, const int i1, const int i2, const int i3 ) +{ /* %TRACE% */ /* %TRACE% */ + + if ( lnodes[i0] == lnodes[i1] || + lnodes[i0] == lnodes[i2] || + lnodes[i0] == lnodes[i3] || + lnodes[i1] == lnodes[i2] || + lnodes[i1] == lnodes[i3] || + lnodes[i2] == lnodes[i3] ) + { + // this tet is degenerate with two coincident nodes + return true; + } + + return false; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_SubElement.hpp b/packages/krino/krino/krino_lib/Akri_SubElement.hpp new file mode 100644 index 000000000000..cb6e086b947c --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SubElement.hpp @@ -0,0 +1,431 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_SubElement_h +#define Akri_SubElement_h + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace krino { + +class ProlongationNodeData; +class ProlongationPointData; +class SubElementNodeAncestry; + +bool node_on_negative_side_of_interface(const SubElementNode * node, const InterfaceID key); + +class SubElement : public ElementObj { +public: + SubElement( const stk::topology topo, + const NodeVec & nodes, + const std::vector & side_ids, + const Mesh_Element * owner); + virtual ~SubElement() {} + + static double coordinate_relative_tol() { return 1.e-6; } + static double parametric_distance( const SubElementNode * node0, const SubElementNode * node1 ); + + void set_permutation(); + double relative_volume() const; + double maximum_relative_angle() const; + + void decompose(CDMesh & mesh, const InterfaceID interface_key); + virtual void decompose_edges(CDMesh & mesh, const InterfaceID interface_key); + virtual void determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key); + virtual void determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key); + + void handle_hanging_children(CDMesh & mesh, const InterfaceID & interface); + virtual void fix_hanging_children(CDMesh & mesh, const InterfaceID & interface, const std::vector & edges_with_children) {} + + virtual void build_quadratic_subelements(CDMesh & mesh); + virtual void cut_face_interior_intersection_points(CDMesh & mesh, const InterfaceID & interface1, const InterfaceID & interface2, int level = 0); + + static void get_edge_position(const SubElementNode * n0, const SubElementNode * n1, const SubElementNode * n2, double & position ); + + bool check_entity_nodes(const stk::mesh::BulkData & stkMesh) const; + void get_owner_coord_transform(double * dOwnerdSub) const; + const Mesh_Element & get_owner() const { return *my_owner; } + + int parent_side_id(const int iside) const; + virtual void determine_decomposed_elem_phase(const CDMesh & mesh) override final; + std::vector subelement_interface_signs(const InterfaceID interface, const int sign) const; + const std::vector & get_interface_signs() const { return myInterfaceSigns; } + void initialize_interface_signs(); + void set_interface_signs(const std::vector & interfaceSigns); + void update_interface_signs(const InterfaceID interface_key, const int sign); + unsigned num_sides() const { return my_parent_side_ids.size(); } + void debug_subelements(const NodeVec & lnodes, const InterfaceID & interface, const int case_id) const; + void debug() const; + bool have_interface(const InterfaceID& interface) const; + void determine_if_cut_by_interface(const InterfaceID & interface, bool & haveAnyCrossing, bool & haveRealCrossing) const; + +protected: + std::vector get_edges_with_children(const InterfaceID & interface) const; + void determine_node_signs_on_edge( const CDMesh & mesh, const InterfaceID interface_key, const int i0, const int i1 ); + void determine_node_scores_on_edge( const CDMesh & mesh, const InterfaceID interface_key, const int i0, const int i1 ); + void process_edge( CDMesh & mesh, const InterfaceID interface_key, const int i0, const int i1 ); + void find_refined_edges(std::vector & refined_edges) const; + int find_longest_bad_edge(std::vector & bad_edges) const; + +protected: + std::vector my_parent_side_ids; + const Mesh_Element * my_owner; + std::vector myInterfaceSigns; + +private: + //: Default constructor not allowed + SubElement(); +}; + +class SubElement_Tri_3 : public SubElement { +public: + SubElement_Tri_3( const NodeVec & nodes, + const std::vector & parent_side_ids, + const Mesh_Element * owner); + virtual ~SubElement_Tri_3() {} + + virtual void decompose_edges(CDMesh & mesh, const InterfaceID interface_key) override; + virtual void fix_hanging_children(CDMesh & mesh, const InterfaceID & interface, const std::vector & edges_with_children) override; + virtual void build_quadratic_subelements(CDMesh & mesh) override; + + virtual void determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key) override; + virtual void determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key) override; + virtual void cut_interior_intersection_point(CDMesh & mesh, const Vector3d & pCoords, const std::vector & sortedDomains) override; + +protected: + void perform_decomposition(CDMesh & mesh, const InterfaceID interface_key, const std::array & node_signs); + bool determine_diagonal_for_cut_triangle(const Simplex_Generation_Method & simplexMethod, const NodeVec & lnodes, const int i0, const int i1, const int i2, const int i3, const int i5); + bool is_degenerate( NodeVec & lnodes, + const int i0, const int i1, const int i2 ); + void handle_tri( NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, + const int s0, const int s1, const int s2, + const bool is_interface0, const bool is_interface1, const bool is_interface2); + void handle_quad( CDMesh & mesh, + NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, + const int s0, const int s1, const int s2, const int s3, + const bool is_interface0, const bool is_interface1, const bool is_interface2, const bool is_interface3, + const bool face ); +private: + //: Default constructor not allowed + SubElement_Tri_3(); + +}; + +class SubElement_Tri_6 : public SubElement { +public: + SubElement_Tri_6( const NodeVec & nodes, + const std::vector & side_ids, + const Mesh_Element * owner); + virtual ~SubElement_Tri_6() {} + +private: + //: Default constructor not allowed + SubElement_Tri_6(); + +}; + +class SubElement_Tet_4 : public SubElement { +public: + SubElement_Tet_4( const NodeVec & nodes, + const std::vector & side_ids, + const Mesh_Element * owner); + virtual ~SubElement_Tet_4() {} + + virtual void decompose_edges(CDMesh & mesh, const InterfaceID interface_key) override; + virtual void fix_hanging_children(CDMesh & mesh, const InterfaceID & interface, const std::vector & edges_with_children) override; + virtual void build_quadratic_subelements(CDMesh & mesh) override; + virtual void cut_face_interior_intersection_points(CDMesh & mesh, const InterfaceID & interface1, const InterfaceID & interface2, int level = 0) override; + + virtual void determine_node_signs(const CDMesh & mesh, const InterfaceID interface_key) override; + virtual void determine_node_scores(const CDMesh & mesh, const InterfaceID interface_key) override; + virtual void cut_interior_intersection_point(CDMesh & mesh, const Vector3d & pCoords, const std::vector & sortedDomains) override; + +protected: + void perform_decomposition(CDMesh & mesh, const InterfaceID interface_key, const std::array & node_signs); + void determine_node_scores_on_face( const CDMesh & mesh, const InterfaceID interface, const int i0, const int i1, const int i2 ); + static const unsigned ** get_permutation_side_ordinals(); + static double tet_volume(const std::array & nodes); + + bool is_degenerate( NodeVec & lnodes, + const int i0, const int i1, const int i2, const int i3 ); + void handle_tet( NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, + const int s0, const int s1, const int s2, const int s3); + void handle_pyramid( NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, const int i4, + const int s0, const int s1, const int s2, const int s3, const int s4, + const bool face4 ); + void handle_wedge( CDMesh & mesh, + NodeVec & lnodes, + const std::vector & subInterfaceSigns, + const int i0, const int i1, const int i2, const int i3, const int i4, const int i5, + const int s0, const int s1, const int s2, const int s3, const int s4, + const bool face0, const bool face1, const bool face2 ); +private: + //: Default constructor not allowed + SubElement_Tet_4(); + + bool determine_diagonal_for_cut_triangular_face(const Simplex_Generation_Method & simplexMethod, const bool globalIDsAreParallelConsistent, const NodeVec & lnodes, const int i0, const int i1, const int i2, const int i3, const int i5); + void cut_face_intersection_point_with_permutation(CDMesh & mesh, const std::array & permuteNodes, const std::array & permuteSides, const std::vector & faceNodeWeights, const std::vector & sortedDomains); +}; + +class SubElement_Tet_10 : public SubElement { +public: + SubElement_Tet_10( const NodeVec & nodes, + const std::vector & side_ids, + const Mesh_Element * owner); + virtual ~SubElement_Tet_10() {} + +private: + //: Default constructor not allowed + SubElement_Tet_10(); + +}; + +class SubElementNode { +public: + SubElementNode(const Mesh_Element * in_owner) + : my_entity(), + my_entityId(0), + my_is_prolonged_flag(false), + my_cached_owner(in_owner) + {} + + virtual ~SubElementNode() {} + virtual bool is_mesh_node() const { return false; } + virtual NodeVec get_parents() const { throw std::runtime_error("Incorrect usage of SubElementNode. The type of node does not have parents."); } + virtual size_t get_num_parents() const { throw std::runtime_error("Incorrect usage of SubElementNode. The type of node does not have parents."); } + virtual std::vector get_parent_weights() const { throw std::runtime_error("Incorrect usage of SubElementNode. The type of node does not have parents."); } + virtual void prolongate_fields(const CDMesh & mesh) const = 0; + + void get_parent_entities(std::vector & parent_entities) const; + + void set_entity(const stk::mesh::BulkData & mesh, stk::mesh::Entity in_node_obj) const { my_entity = in_node_obj; my_entityId = mesh.identifier(my_entity); } + void set_entity(stk::mesh::Entity nodeEntity, stk::mesh::EntityId nodeEntityId) const { my_entity = nodeEntity; my_entityId = nodeEntityId; } + const Mesh_Element * get_active_element() const { return my_cached_owner; } + bool is_prolonged() const { return my_is_prolonged_flag; } + void set_prolonged_flag(const bool val) const { my_is_prolonged_flag = val; } // this is dangerous, know what you are doing + bool on_common_edge(const SubElementNode * other) const; + + bool entity_is_valid(const stk::mesh::BulkData & mesh) const { return mesh.is_valid(my_entity); } + bool check_entity(const stk::mesh::BulkData & mesh) const { return (my_entityId == 0) ? (my_entity == stk::mesh::Entity()) : (my_entityId == mesh.identifier(my_entity)); } + stk::mesh::Entity & entity() const { return my_entity; } + stk::mesh::EntityId entityId() const { return my_entityId; } + void set_entityId_from_entity(const stk::mesh::BulkData & mesh) const { ThrowAssert(mesh.is_valid(my_entity)); my_entityId = mesh.identifier(my_entity); } + + const Vector3d & owner_coords( const Mesh_Element * in_owner ) const { + if ( in_owner != my_cached_owner ) { + // call derived types function for recomputing owner_coords + my_cached_owner = in_owner; + my_cached_owner_coords = compute_owner_coords( in_owner ); + } + return my_cached_owner_coords; + } + bool get_node_on_interface() const { return my_node_sign == 0; } + + void set_node_domains(const std::vector & nodeDomains) const { my_sorted_node_domains = nodeDomains; } + const std::vector & get_sorted_node_domains() const { return my_sorted_node_domains; } + void insert_node_domains(const std::vector & domainsToAdd) const; + bool captures_intersection_point_domains(const std::vector & intersectionPointDomains) const; + bool captures_interface(const InterfaceID & interface) const; + bool captures_intersection_point_domains(const InterfaceID & interface) const; + static bool higher_priority_by_score_then_ancestry(const SubElementNode & a, const SubElementNode & b, const bool globalIDsAreParallelConsistent); + static bool less_by_entity_id(const SubElementNode & a, const SubElementNode & b); + static bool less_by_coordinates_then_by_entity_id(const SubElementNode & a, const SubElementNode & b); + bool node_sign_is_set() const { return my_node_sign != -2; } + void set_node_sign(const int sign) const { my_node_sign = (my_node_sign == 0 || my_node_sign == -sign) ? 0 : sign; } + int get_node_sign() const { ThrowRequire(node_sign_is_set()); return my_node_sign; } + void clear_node_sign() const { my_node_sign = -2; } + void clear_node_score() const { my_node_score = 1.; } + bool node_score_is_set() const { return my_node_score != 1.; } + void set_node_score(const double score) const { my_node_score = std::min(my_node_score, score); } + double get_node_score() const { ThrowRequire(node_sign_is_set()); return my_node_score; } + + // pure virtual function for recomputing local coordinates + virtual Vector3d compute_owner_coords( const Mesh_Element * in_owner ) const = 0; + + const Vector3d & coordinates() const { return my_global_coords; } + + void add_child(const SubElementNode* child) const { my_children.push_back(child); } + static const SubElementNode * common_child( const NodeVec & parents ); + bool have_child() const; + bool have_child(const SubElementNode* child) const; + + // This is called from Mesh::find_prolongation_node to determine the set of candidate prolongation nodes. + std::vector prolongation_node_fields(const CDMesh & mesh) const; + + const SubElementNode * find_node_with_common_ancestry(const CDMesh & search_mesh) const; + + void get_ancestors(NodeSet & ancestors) const; + SubElementNodeAncestry get_ancestry() const; + void build_stencil(std::map & stencil, const double self_weight = 1.0) const; + void build_constraint_stencil(const FieldRef field, std::vector & entities, std::vector & weights) const; + +protected: + void prolong_cdfem_displacements(const CDMesh & mesh, + const ProlongationPointData * prolong_data, + const bool zero_if_no_prolong_data = true) const; + void prolong_zeroed_fields(const CDMesh & mesh, const ProlongationNodeData * nodeToExamineForExistingField) const; + + mutable stk::mesh::Entity my_entity; + mutable stk::mesh::EntityId my_entityId; + mutable bool my_is_prolonged_flag; + Vector3d my_global_coords; + mutable signed char my_node_sign; + mutable double my_node_score; + + mutable const Mesh_Element * my_cached_owner; + mutable Vector3d my_cached_owner_coords; + + mutable std::vector my_children; + mutable std::vector my_sorted_node_domains; + +private: + //: copy constructor not allowed + SubElementNode(SubElementNode const & copy); +}; + +class SubElementChildNode : public SubElementNode { +public: + SubElementChildNode( const Mesh_Element * owner, + const NodeVec & parents, + const std::vector & weights ); + + virtual ~SubElementChildNode() {} + virtual void prolongate_fields(const CDMesh & mesh) const override; + virtual Vector3d compute_owner_coords( const Mesh_Element * in_owner ) const override; + + virtual NodeVec get_parents() const override { return my_parents; } + virtual size_t get_num_parents() const override { return my_parents.size(); } + virtual std::vector get_parent_weights() const override { return my_weights; } + bool needs_to_be_ale_prolonged(const CDMesh & mesh) const; + +private: + void prolong_ale_fields(const CDMesh & mesh, const ProlongationPointData * prolong_data) const; + void prolong_interpolation_fields(const CDMesh & mesh) const; + + NodeVec my_parents; + std::vector my_weights; +}; + +class SubElementSteinerNode : public SubElementChildNode { +public: + + SubElementSteinerNode( const Mesh_Element * in_owner, + const NodeVec & parents, + const std::vector & weights ) + : SubElementChildNode(in_owner, parents, weights) {} + + virtual ~SubElementSteinerNode() {} + virtual void prolongate_fields(const CDMesh & mesh) const override; + virtual Vector3d compute_owner_coords( const Mesh_Element * owner ) const override { + throw std::runtime_error("Incorrect usage of SubElementSteinerNode. The type of node only has one owner."); + } +}; + +class SubElementEdgeNode : public SubElementChildNode { +public: + SubElementEdgeNode( const Mesh_Element * owner, + const double & position, + const SubElementNode *parent1, + const SubElementNode *parent2) + : SubElementChildNode( owner, {parent1, parent2}, {1.-position, position}) {} + + virtual ~SubElementEdgeNode() {} + + double get_position() const { ThrowAssert(get_num_parents() == 2); return get_parent_weights()[1]; } + double get_position(const SubElementNode *parent1, const SubElementNode *parent2) const { ThrowAssert(check_parents(parent1,parent2)); return ((parent1==get_parents()[0]) ? get_parent_weights()[1] : get_parent_weights()[0]); } + +private: + bool check_parents(const SubElementNode *parent1, const SubElementNode *parent2) const { + return (get_num_parents() == 2 && ((parent1==get_parents()[0] && parent2==get_parents()[1]) || (parent2==get_parents()[0] && parent1==get_parents()[1]))); + } +}; + + class SubElementMidSideNode : public SubElementNode { +public: + SubElementMidSideNode() = delete; + SubElementMidSideNode( const Mesh_Element * owner, + const SubElementNode *parent1, + const SubElementNode *parent2); + SubElementMidSideNode( const Mesh_Element * owner, + const SubElementNode *parent1, + const SubElementNode *parent2, + stk::mesh::Entity meshNode, + stk::mesh::EntityId meshNodeId); + + virtual ~SubElementMidSideNode() {} + virtual void prolongate_fields(const CDMesh & mesh) const override; + virtual Vector3d compute_owner_coords( const Mesh_Element * in_owner ) const override { + return 0.5 * my_parent1->owner_coords(in_owner) + 0.5 * my_parent2->owner_coords(in_owner); + } + virtual bool is_mesh_node() const override { return my_is_mesh_node; } + + virtual NodeVec get_parents() const override { ThrowRequire(!my_is_mesh_node); return NodeVec{my_parent1,my_parent2}; } + virtual size_t get_num_parents() const override { return 2; } + virtual std::vector get_parent_weights() const override { ThrowRequire(!my_is_mesh_node); return std::vector{0.5,0.5}; } + +protected: + bool my_is_mesh_node; + const SubElementNode *my_parent1; + const SubElementNode *my_parent2; + +private: + void prolong_ale_fields(const CDMesh & mesh) const; + void prolong_interpolation_fields(const CDMesh & mesh) const; + bool is_mesh_node_that_needs_to_be_prolonged(const CDMesh & mesh) const; +}; + + +class SubElementMeshNode : public SubElementNode { +public: + + SubElementMeshNode( const Mesh_Element * owner, + stk::mesh::Entity nodeEntity, + stk::mesh::EntityId nodeEntityId, + const Vector3d & owner_coords, + const Vector3d & global_coords ); + + virtual ~SubElementMeshNode() {} + + virtual void prolongate_fields(const CDMesh & mesh) const override; + virtual Vector3d compute_owner_coords( const Mesh_Element * owner ) const override { return owner->get_node_parametric_coords(this); } + virtual bool is_mesh_node() const override { return true; } + bool needs_to_be_ale_prolonged(const CDMesh & mesh) const; +protected: + +private: + void prolong_ale_fields(const CDMesh & mesh, + const ProlongationPointData * prolong_data, + const ProlongationNodeData * old_node) const; + void prolong_interpolation_fields(const CDMesh & mesh, const ProlongationNodeData * old_node) const; + //: Default constructor not allowed + SubElementMeshNode(); +}; + +} // namespace krino + +#endif // Akri_SubElement_h diff --git a/packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.cpp b/packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.cpp new file mode 100644 index 000000000000..b592e297f5a9 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.cpp @@ -0,0 +1,246 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +namespace krino { + +SubElementChildNodeAncestry::SubElementChildNodeAncestry( stk::CommBuffer & b ) +{ + size_t ancestrySize = 0; + b.unpack(ancestrySize); + ThrowAssert(ancestrySize > 0); + + myAncestry.reserve(ancestrySize); + + size_t numParents = 0; + std::vector parentIDs; + std::vector parentWeights; + + for(unsigned index=0; indexis_mesh_node()) + { + myAncestry.emplace_back(std::vector{node->entityId()}, std::vector{0.}); + return; + } + + const NodeVec & parents = node->get_parents(); + const auto & parentWeights = node->get_parent_weights(); + std::vector parentIDs; + parentIDs.reserve(parents.size()); + + for (auto && parent : node->get_parents()) + { + if (parent->is_mesh_node()) + parentIDs.push_back(parent->entityId()); + else + parentIDs.push_back(0); + } + + myAncestry.emplace_back(parentIDs, parentWeights); + + for (auto && parent : node->get_parents()) + if (!parent->is_mesh_node()) + build_ancestry(parent); +} + +void +SubElementChildNodeAncestry::get_parent_node_keys(std::vector & parentNodeKeys) const +{ + parentNodeKeys.clear(); + for (auto&& cut : myAncestry) + for (auto && parentID : cut.myParentIDs) + if (parentID != 0) + parentNodeKeys.emplace_back(stk::topology::NODE_RANK, parentID); +} + +bool +SubElementChildNodeAncestry::is_shared(const stk::mesh::BulkData & mesh, const SubElementNode * node) +{ + if (node->is_mesh_node()) + { + return mesh.bucket(node->entity()).shared(); + } + for (auto && parent : node->get_parents()) + { + if (!is_shared(mesh, parent)) + { + return false; + } + } + return true; +} + +void +SubElementChildNodeAncestry::pack_into_buffer(stk::CommBuffer & b) const +{ + ThrowAssert(!myAncestry.empty()); + const size_t ancestrySize = myAncestry.size(); + b.pack(ancestrySize); + for (auto&& cut : myAncestry) + { + const size_t numParents = cut.myParentIDs.size(); + b.pack(numParents); + for (size_t i=0; i parents(numParents, nullptr); + + for (size_t i=0; i +SubElementChildNodeAncestry::get_constrained_node_ancestries(const std::unordered_map > & constrainedNodeMap) const +{ + std::set parentNodeIDs; + for (auto&& cut : myAncestry) + for (auto && parentID : cut.myParentIDs) + if (parentID != 0) + parentNodeIDs.insert(parentID); + + std::vector constrainedNodeAncestries; + constrainedNodeAncestries.push_back(*this); + for (auto&& parentNodeID : parentNodeIDs) + { + std::vector toBeConverted; + toBeConverted.swap(constrainedNodeAncestries); + auto it = constrainedNodeMap.find(parentNodeID); + if (it == constrainedNodeMap.end()) break; + constrainedNodeAncestries.reserve(it->second.size() * toBeConverted.size()); + for (auto&& constrainedParentNodeID : it->second) + { + for (auto&& unconverted : toBeConverted) + { + SubElementChildNodeAncestry converted = unconverted; + for (auto&& cut : converted.myAncestry) + { + for (auto && cutParentNodeID : cut.myParentIDs) + if (cutParentNodeID == parentNodeID) + cutParentNodeID = constrainedParentNodeID; + } + constrainedNodeAncestries.push_back(converted); + } + } + } + return constrainedNodeAncestries; +} + +const SubElementNode * +SubElementChildNodeAncestry::build_missing_child_nodes(CDMesh & mesh, unsigned & ancestryIndex) const +{ + ThrowAssert(ancestryIndex < myAncestry.size()); + const Cut & cut = myAncestry[ancestryIndex]; + const size_t numParents = cut.myParentIDs.size(); + std::vector parents(numParents, nullptr); + + for (size_t i=0; iget_ancestors(ancestors); + + std::vector ancestorNodes; + ancestorNodes.reserve(ancestors.size()); + for (auto&& ancestor : ancestors) + { + ThrowAssert(ancestor->entity_is_valid(mesh.stk_bulk())); + ancestorNodes.push_back(ancestor->entity()); + } + + std::vector elems; + stk::mesh::get_entities_through_relations(mesh.stk_bulk(), ancestorNodes, stk::topology::ELEMENT_RANK, elems); + + const Mesh_Element * owner = nullptr; + for(auto && elem : elems) + { + owner = mesh.find_mesh_element(mesh.stk_bulk().identifier(elem)); + if(owner != nullptr) break; + } + if(!owner) + { + // It is possible in parallel for both nodes of a parent edge to be shared with a processor, + // but that processor does not own any of the elements connected to that edge. In that case + // we do not need to build the missing edge node. + return nullptr; + } + + if (parents.size() == 2) + return mesh.create_edge_node(owner, parents[0], parents[1], cut.myParentWeights[1]); + + return mesh.create_child_internal_or_face_node(owner, parents, cut.myParentWeights); +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.hpp b/packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.hpp new file mode 100644 index 000000000000..55c73d64a243 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SubElementChildNodeAncestry.hpp @@ -0,0 +1,49 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_SubElementChildNodeAncestry_h +#define Akri_SubElementChildNodeAncestry_h + +#include +#include +#include +#include + +namespace krino +{ +class CDMesh; +class SubElementNode; + +class SubElementChildNodeAncestry { +public: + SubElementChildNodeAncestry(const SubElementNode * node) { build_ancestry(node); } + SubElementChildNodeAncestry( stk::CommBuffer & b ); + + static bool is_shared(const stk::mesh::BulkData & mesh, const SubElementNode * node); + void pack_into_buffer(stk::CommBuffer & b) const; + const SubElementNode * find_subelement_node(CDMesh & mesh) const; + const SubElementNode * find_subelement_node(CDMesh & mesh, unsigned & ancestry_index) const; + void get_parent_node_keys(std::vector & parent_node_keys) const; + void build_missing_child_nodes(CDMesh & mesh) const; + std::vector get_constrained_node_ancestries(const std::unordered_map > & constrained_node_map) const; + +private: + struct Cut { + Cut(const std::vector & parentIDs, const std::vector & parentWeights) : myParentIDs(parentIDs), myParentWeights(parentWeights) {} + std::vector myParentIDs; + std::vector myParentWeights; + }; + void build_ancestry(const SubElementNode * in_node); + const SubElementNode * build_missing_child_nodes(CDMesh & mesh, unsigned & ancestry_index) const; + + std::vector myAncestry; +}; + +} + +#endif // Akri_SubElementChildNodeAncestry_h diff --git a/packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.cpp b/packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.cpp new file mode 100644 index 000000000000..9dad99d5cf55 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.cpp @@ -0,0 +1,32 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +namespace krino { + +void SubElementNodeAncestry::print(std::ostream & os) const +{ + if (my_node->is_mesh_node()) + { + os << my_node->entityId(); + } + else + { + os << "{ "; + for (auto&& parent : get_parents()) + { + parent.print(os); + os << " "; + } + os << "}"; + } +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.hpp b/packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.hpp new file mode 100644 index 000000000000..b36fc35423e0 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_SubElementNodeAncestry.hpp @@ -0,0 +1,65 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_SubElementNodeAncestry_h +#define Akri_SubElementNodeAncestry_h + +#include + +namespace krino { + +class SubElementNodeAncestry { +public: + SubElementNodeAncestry() = default; + SubElementNodeAncestry(const SubElementNodeAncestry & rhs) = default; + SubElementNodeAncestry(const SubElementNode * node) : my_node(node) {} + + template + static bool compare(const SubElementNodeAncestry & x, const SubElementNodeAncestry & y, const LESS & compare) + { + const std::vector xParents = x.get_parents(); + const std::vector yParents = y.get_parents(); + if (xParents.empty()) + { + if (yParents.empty()) return compare(*(x.my_node), *(y.my_node)); + else return true; + } + else if (yParents.empty()) return false; + else return xParents < yParents; + } + + void print(std::ostream & os) const; + +private: + std::vector get_parents() const + { + std::vector parents; + if (!my_node->is_mesh_node()) + { + const NodeVec nodeParents = my_node->get_parents(); + parents.reserve(nodeParents.size()); + for (auto && nodeParent : nodeParents) + parents.emplace_back(nodeParent); + std::sort(parents.begin(), parents.end()); + } + return parents; + } + + const SubElementNode * my_node = nullptr; +}; + +inline bool operator<(const SubElementNodeAncestry & x, const SubElementNodeAncestry & y) +{ + return SubElementNodeAncestry::compare(x,y,SubElementNode::less_by_entity_id); +} + +inline std::ostream & operator << (std::ostream & os, const SubElementNodeAncestry & ancestry) { ancestry.print(os); return os; } + +} + +#endif // Akri_SubElementNodeAncestry_h diff --git a/packages/krino/krino/krino_lib/Akri_Surface.cpp b/packages/krino/krino/krino_lib/Akri_Surface.cpp new file mode 100644 index 000000000000..6fc43d1f8dd7 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Surface.cpp @@ -0,0 +1,28 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +namespace krino{ + +void +Surface::prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length) +{ + ThrowErrorMsgIf(NULL != my_transformation, + "This surface with type (" << type() + << ") has motion specified, but the prepare_to_compute() method has not been implemented yet to support motion."); +} + +BoundingBox +Surface::get_bounding_box() +{ + ThrowRuntimeError("This surface with type (" << type() << ") has not implemented get_bounding_box()."); +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Surface.hpp b/packages/krino/krino/krino_lib/Akri_Surface.hpp new file mode 100644 index 000000000000..78019bafe845 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Surface.hpp @@ -0,0 +1,98 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Surface_h +#define Akri_Surface_h + +#include +#include +#include +#include + +namespace krino { + +class Transformation; + +enum Surface_Type +{ + POINT=0, + SPHERE, + ELLIPSOID, + CYLINDER, + COMPOSITE_SURFACE, + PLANE, + RANDOM, + FACETED_SURFACE, + // Never, ever, ever add an entry after MAX_SURFACE_TYPE. Never. + MAX_SURFACE_TYPE +}; + +class Surface; +typedef std::vector SurfaceVec; +typedef std::vector< std::unique_ptr > SurfaceAutoVec; + +// abstract class used to define a surface. + +class Surface { + +public: + Surface() { Surface::set_transformation(nullptr); } + virtual ~Surface() {} + + virtual BoundingBox get_bounding_box(); + + // pre-calculations needed to compute distance + virtual void prepare_to_compute(const double time, const BoundingBox & point_bbox, const double truncation_length); + + // compute signed distance from specific point to surface + virtual double point_signed_distance(const Vector3d &x) const = 0; + + // compute signed distance from specific point to surface + // For distances larger than truncation_length (if truncation_length > 0.), the surface is allowed to return + // far_field_value instead of the actual signed distance. + virtual double truncated_point_signed_distance(const Vector3d &x, const double truncation_length, const double far_field_value) const = 0; + // If surface does return far_field_value instead of actual signed distance, the sign may be wrong. + virtual bool truncated_distance_may_have_wrong_sign() const = 0; + + // for debugging memory usage + virtual size_t storage_size() const = 0; + + // methods related to moving surfaces (transformations) + virtual void set_transformation(Transformation * trans) { my_transformation = trans; } + Transformation * get_transformation() { return my_transformation; } + const Transformation * get_transformation() const { return my_transformation; } + + // queries + virtual Surface_Type type() const = 0; + +protected: + Transformation * my_transformation; +}; + +class SurfaceThatTakesAdvantageOfNarrowBandAndThereforeMightHaveWrongSign : public Surface { +public: + virtual bool truncated_distance_may_have_wrong_sign() const override { return true; } + virtual double point_signed_distance(const Vector3d &x) const override + { + return truncated_point_signed_distance(x, 0., 0.); + } + virtual double truncated_point_signed_distance(const Vector3d &x, const double truncation_length, const double far_field_value) const override = 0; +}; + +class SurfaceThatDoesntTakeAdvantageOfNarrowBandAndThereforeHasCorrectSign : public Surface { +public: + virtual bool truncated_distance_may_have_wrong_sign() const override { return false; } + virtual double point_signed_distance(const Vector3d &x) const override = 0; + virtual double truncated_point_signed_distance(const Vector3d &x, const double truncation_length, const double far_field_value) const override + { + return point_signed_distance(x); + } +}; +} // namespace krino + +#endif // Akri_Surface_h diff --git a/packages/krino/krino/krino_lib/Akri_Transformation.cpp b/packages/krino/krino/krino_lib/Akri_Transformation.cpp new file mode 100644 index 000000000000..d7c563f2a588 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Transformation.cpp @@ -0,0 +1,112 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include + +namespace krino{ + +void +Quaternion::set_from_rotation_vector(const Vector3d & v) +{ + // the angle theta is the length of the rotation vector omega + const double theta = v.length(); + const double real_min = 10.0*std::numeric_limits::min(); + + double coef; + if ( theta > real_min ) { + coef = std::sin(0.5*theta)/theta; + } else { + coef = 0.5; + } + + q[0] = std::cos( 0.5*theta ); + q[1] = coef*v[0]; + q[2] = coef*v[1]; + q[3] = coef*v[2]; + + const double inv_length = 1.0/std::sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]); + for (auto && val : q) val *= inv_length; +} + +Vector3d use_quaternion_to_rotate_3d_vector(const std::array & q, const Vector3d & v) +{ + return Vector3d( + ((2. * ( q[0] * q[0] + q[1] * q[1] ) - 1.) * v[0] + + (2. * ( q[1] * q[2] - q[0] * q[3] ) ) * v[1] + + (2. * ( q[1] * q[3] + q[0] * q[2] ) ) * v[2]), + ((2. * ( q[1] * q[2] + q[0] * q[3] ) ) * v[0] + + (2. * ( q[0] * q[0] + q[2] * q[2] ) - 1.) * v[1] + + (2. * ( q[2] * q[3] - q[0] * q[1] ) ) * v[2]), + ((2. * ( q[1] * q[3] - q[0] * q[2] ) ) * v[0] + + (2. * ( q[2] * q[3] + q[0] * q[1] ) ) * v[1] + + (2. * ( q[0] * q[0] + q[3] * q[3] ) - 1.) * v[2])); +} + + +Vector3d +Quaternion::rotate_3d_vector(const Vector3d & v) const +{ + return use_quaternion_to_rotate_3d_vector(q, v); +} + +Vector3d +Quaternion::reverse_rotate_3d_vector(const Vector3d & v) const +{ + return use_quaternion_to_rotate_3d_vector(std::array{q[0],-q[1],-q[2],-q[3]}, v); +} + +void +Transformation::initialize( const Vector3d & initial_displacement, const Vector3d & initial_rotation, const Vector3d & reference_point ) +{ + my_reference_point = reference_point; + my_update_orientation.set_from_rotation_vector(initial_rotation); + my_update_offset = initial_displacement - my_update_orientation.rotate_3d_vector(my_reference_point); + my_reference_point += initial_displacement; +} + +void +Transformation::initialize() +{ + // this form of initialization assumes that set_initial_displacement() and set_initial_rotation() have been called or are zero + const Vector3d initial_displacement = my_update_offset; + my_update_offset = initial_displacement - my_update_orientation.rotate_3d_vector(my_reference_point); + my_reference_point += initial_displacement; +} + +void +Transformation::update( const double time ) const +{ + if (time == my_last_update) + { + return; + } + if (my_last_update < 0.0) + { + my_last_update = time; + return; + } + + const double dt = time - my_last_update; + const Vector3d update_rotation_angle = dt*my_rotational_velocity; + my_update_orientation.set_from_rotation_vector(update_rotation_angle); + my_update_offset = my_reference_point - my_update_orientation.rotate_3d_vector(my_reference_point) + my_translational_velocity * dt; + my_reference_point += my_translational_velocity * dt; + + my_last_update = time; +} + +void +Transformation::apply( Vector3d & x0 ) const +{ + x0 = my_update_orientation.rotate_3d_vector(x0) + my_update_offset; +} + +} // namespace krino diff --git a/packages/krino/krino/krino_lib/Akri_Transformation.hpp b/packages/krino/krino/krino_lib/Akri_Transformation.hpp new file mode 100644 index 000000000000..602d3510af9a --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Transformation.hpp @@ -0,0 +1,65 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Transformation_h +#define Akri_Transformation_h + +#include +#include + +namespace krino { + +class Quaternion { + public: + Quaternion() : q{{ 1.0, 0.0, 0.0, 0.0 }} {} + + void set_from_rotation_vector(const Vector3d & v); + Vector3d rotate_3d_vector(const Vector3d & v) const; + Vector3d reverse_rotate_3d_vector(const Vector3d & v) const; + + private: + std::array q; ///< q[0] stores angle, (q[1],q[2],a[3]) stores axis. +}; + +class Transformation { +public: + Transformation() + : my_translational_velocity(Vector3d::ZERO), my_rotational_velocity(Vector3d::ZERO), + my_reference_point(Vector3d::ZERO), my_last_update(-1.0), my_update_orientation(), my_update_offset(Vector3d::ZERO) {} + virtual ~Transformation() {} + + void set_translational_velocity(const Vector3d & v) { my_translational_velocity = v; } + void set_rotational_velocity(const Vector3d & v) { my_rotational_velocity = v; } + void set_reference_point(const Vector3d & v) { my_reference_point = v; } + + // temporary storage until initialize() + void set_initial_displacement(const Vector3d & v) { my_update_offset = v; } + void set_initial_rotation(const Vector3d & v) { my_update_orientation.set_from_rotation_vector(v); } + + const Vector3d & get_translational_velocity() const { return my_translational_velocity; } + const Vector3d & get_rotational_velocity() const { return my_rotational_velocity; } + const Vector3d & get_reference_point() const { return my_reference_point; } + + void initialize( const Vector3d & initial_displacement, const Vector3d & initial_rotation, const Vector3d & reference_point ); + void initialize(); // this form assumes that set_initial_displacement() and set_initial_rotation() have been called or are zero + void update( const double time ) const; + void apply( Vector3d & x0 ) const; + +protected: + Vector3d my_translational_velocity; + Vector3d my_rotational_velocity; + mutable Vector3d my_reference_point; + mutable double my_last_update; + mutable Quaternion my_update_orientation; + mutable Vector3d my_update_offset; +}; + + +} // namespace krino + +#endif // Akri_Transformation_h diff --git a/packages/krino/krino/krino_lib/Akri_Triangle.hpp b/packages/krino/krino/krino_lib/Akri_Triangle.hpp new file mode 100644 index 000000000000..868fbe2b0a1e --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Triangle.hpp @@ -0,0 +1,169 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Triangle_h +#define Akri_Triangle_h + +#include +#include +#include +#include + +#include + +namespace krino { + +enum class ProjectionType{NODE, EDGE, FACE, NUM_PROJ_TYPE}; + +template +class Triangle3 { + public: + typedef REAL Real; + + + Triangle3() {} /// Default, all coords zero + Triangle3(const Vec &n0, const Vec &n1, const Vec &n2) : nodes{{n0, n1, n2}} {} /// Explicit set three coordinates + explicit Triangle3(const MemberInit) {/* MemberInit::NONE */} /// Special non-initialize constructor (for performance) + + Vec normal() const { return normal_dir().unit_vector(); } /// Unit vector + Vec normal_dir() const {return Cross(GetNode(1)-GetNode(0),GetNode(2)-GetNode(0)); } /// Non-unit normal (faster) + Real area() const { return 0.5*normal_dir().length(); } + + Vec ParametricToRealCoords(const Vec & Param) const { + // Avoids temporary constructions + return Vec( + GetNode(0)[0]*(1.0-Param[0]-Param[1]) + GetNode(1)[0]*Param[0] + GetNode(2)[0]*Param[1], + GetNode(0)[1]*(1.0-Param[0]-Param[1]) + GetNode(1)[1]*Param[0] + GetNode(2)[1]*Param[1], + GetNode(0)[2]*(1.0-Param[0]-Param[1]) + GetNode(1)[2]*Param[0] + GetNode(2)[2]*Param[1] + ); + } + + /// Compute the closest point on the triangle to an input point. Return the type of projection, i.e., + /// is the projected point on a node, edge, surface of the triangle. Optionally calculate parametric coordinates. + template ProjectionType ClosestPoint(const Vec& p, Vec& ClosestPt, T ParamPt = nullptr) const; + + Real DistanceSquared(const Vec& P, Vec& ParamPt) const; + Real DistanceSquared(const Vec& P) const; + + const Vec& GetNode(const int index) const { assert(index >= 0 && index < 3); return nodes[index]; } + Vec& GetNode(const int index) { assert(index >= 0 && index < 3); return nodes[index]; } + + void SetNodes(const Vec& n0, const Vec& n1, const Vec& n2) { nodes = {{n0, n1, n2}}; } // Change triangle coordinates +private: + std::array,3> nodes; +}; + +namespace detail { +template +struct assign_parametric_coords { + static void apply(const REAL & x, const REAL & y, T parametric_coords) + { + static_assert(std::is_pointer::value, "Expecting pointer"); + parametric_coords[0] = x; + parametric_coords[1] = y; + } +}; +template +struct assign_parametric_coords { + static void apply(const REAL, const REAL, std::nullptr_t) {} +}; +} + +template +inline REAL Triangle3::DistanceSquared(const Vec& P, Vec& ParamPt) const +{ + Vec ClosestPt(MemberInit::NONE); + ClosestPoint(P, ClosestPt, ParamPt.data()); + return (P-ClosestPt).length_squared(); +} + +template +inline REAL Triangle3::DistanceSquared(const Vec& P) const +{ + Vec ClosestPt(MemberInit::NONE); + ClosestPoint(P, ClosestPt); + return (P-ClosestPt).length_squared(); +} + +// Adapted from closest face projection from "Real time Collision Detection" text, highly optimized +template +template +ProjectionType Triangle3::ClosestPoint(const Vec& p, Vec& ClosestPt, T ParamPt) const +{ + const Vec& a = GetNode(0); + const Vec& b = GetNode(1); + const Vec& c = GetNode(2); + + Vec ab(b-a); + Vec ac(c-a); + Vec ap(p-a); + + Real d1(Dot(ab,ap)); + Real d2(Dot(ac,ap)); + + if(d1 <= 0.0 && d2 <= 0.0) { + ClosestPt = a; + detail::assign_parametric_coords::apply(0.0, 0.0, ParamPt); + return ProjectionType::NODE; + } + + Vec bp(p-b); + Real d3(Dot(ab,bp)); + Real d4(Dot(ac,bp)); + + if(d3 >= 0.0 && d4 <= d3) { + ClosestPt = b; + detail::assign_parametric_coords::apply(1.0, 0.0, ParamPt); + return ProjectionType::NODE; + } + + Real vc(d1*d4-d3*d2); + if(vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) { + Real v = d1/(d1-d3); + ClosestPt = a + v * ab; + detail::assign_parametric_coords::apply(v, 0.0, ParamPt); + return ProjectionType::EDGE; + } + + Vec cp(p-c); + Real d5(Dot(ab,cp)); + Real d6(Dot(ac,cp)); + if(d6 >= 0.0 && d5 <= d6) { + ClosestPt = c; + detail::assign_parametric_coords::apply(0.0, 1.0, ParamPt); + return ProjectionType::NODE; + } + + Real vb(d5*d2 - d1*d6); + if(vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) { + Real w(d2/(d2-d6)); + ClosestPt = a + w * ac; + detail::assign_parametric_coords::apply(0.0, w, ParamPt); + return ProjectionType::EDGE; + } + + Real va(d3*d6 - d5*d4); + if(va <= 0.0 && (d4-d3) >= 0.0 && (d5-d6) >= 0.0) { + Real w((d4-d3)/((d4-d3) + (d5-d6))); + ClosestPt = b + w * (c-b); + detail::assign_parametric_coords::apply(1.0-w, w, ParamPt); + return ProjectionType::EDGE; + } + + Real denom(1.0/(va+vb+vc)); + Real v(vb*denom); + Real w(vc*denom); + ClosestPt = a + ab*v + ac*w; + detail::assign_parametric_coords::apply(v, w, ParamPt); + return ProjectionType::FACE; +} + +typedef Triangle3 Triangle3d; + +} +#endif // Akri_Triangle_h diff --git a/packages/krino/krino/krino_lib/Akri_TypeDefs.hpp b/packages/krino/krino/krino_lib/Akri_TypeDefs.hpp new file mode 100644 index 000000000000..3a949159289b --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_TypeDefs.hpp @@ -0,0 +1,27 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef SIERRA_Akri_TypeDefs_h +#define SIERRA_Akri_TypeDefs_h + +#include +#include +#include + +namespace krino { + + class NINT{}; + class NPE_VAR{}; + class NPE_COORD{}; + class DIM{}; + + typedef std::vector< Vector3d > PointVec; + +} + +#endif // SIERRA_Akri_TypeDefs_h diff --git a/packages/krino/krino/krino_lib/Akri_Utility.hpp b/packages/krino/krino/krino_lib/Akri_Utility.hpp new file mode 100644 index 000000000000..a8a7e7b69aa2 --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Utility.hpp @@ -0,0 +1,66 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef SIERRA_Akri_Utility_h +#define SIERRA_Akri_Utility_h + +#include +#include +#include +#include + +namespace krino { +namespace utility { + + inline bool sign_change( double f1, double f2 ) { + return ( (f1 < 0.) ? (f2 >= 0.) : (f2 < 0.) ); // GOMA sign convention + //return ( (f1 > 0.) ? (f2 <= 0.) : (f2 > 0.) ); // Marching cubes sign convention + } + + inline int sign( double f ) { + return ( (f < 0.) ? -1 : 1 ); // GOMA sign convention + //return ( (f > 0.) ? 1 : -1 ); // Marching cubes sign convention + } + + inline bool is_less(double f1, double f2, double tol) { return (f2-f1 > tol*(std::fabs(f1)+std::fabs(f2))); } + inline bool is_more(double f1, double f2, double tol) { return (is_less(f2,f1,tol)); } + inline bool is_not_equal(double f1, double f2, double tol) { return (is_less(f1,f2,tol) || is_less(f2,f1,tol)); } + inline bool is_equal(double f1, double f2, double tol) { return (!is_less(f1,f2,tol) && !is_less(f2,f1,tol)); } + + inline bool is_less(double f1, double f2) { return is_less(f1,f2,100.0*std::numeric_limits::epsilon()); } + inline bool is_more(double f1, double f2) { return is_more(f1,f2,100.0*std::numeric_limits::epsilon()); } + inline bool is_not_equal(double f1, double f2) { return is_not_equal(f1,f2,100.0*std::numeric_limits::epsilon()); } + inline bool is_equal(double f1, double f2) { return is_equal(f1,f2,100.0*std::numeric_limits::epsilon()); } + + inline bool is_less(double f1, int i1, double f2, int i2, double tol) { return is_less(f1,f2,tol) ? true : (is_more(f1,f2,tol) ? false : (i1::epsilon()); } + inline bool is_more(double f1, int i1, double f2, int i2) { return is_more(f1,i1,f2,i2,100.0*std::numeric_limits::epsilon()); } + + template + inline void + free_all(std::vector & vec) {std::vector empty_vec; vec.swap(empty_vec);} + + template + inline size_t + storage_size(const std::vector & vec) {return (sizeof(std::vector) + vec.size()*sizeof(T*)); } + + // linear cost, requires T::storage_size + template + inline size_t + storage_size(const std::vector> & autovec) + { + size_t store_size = sizeof(std::vector>) + autovec.size()*sizeof(std::unique_ptr); + for (auto && entry : autovec) store_size += entry->storage_size(); + return store_size; + } + +} // namespace utility +} // namespace krino + +#endif // SIERRA_Akri_Utility_h diff --git a/packages/krino/krino/krino_lib/Akri_Vec.cpp b/packages/krino/krino/krino_lib/Akri_Vec.cpp new file mode 100644 index 000000000000..fa4faa7d9e1c --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Vec.cpp @@ -0,0 +1,38 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +namespace krino { + +static bool float_less(double a, double b) +{ + return static_cast(a) < static_cast(b); +} + +bool is_less_than_in_x_then_y_then_z(const Vector3d& A, const Vector3d &B) +{ + if (float_less(A[0], B[0])) + return true; + else if (float_less(B[0], A[0])) + return false; + + if (float_less(A[1], B[1])) + return true; + else if (float_less(B[1], A[1])) + return false; + + if (float_less(A[2], B[2])) + return true; + else if (float_less(B[2], A[2])) + return false; + + return false; +} + +} diff --git a/packages/krino/krino/krino_lib/Akri_Vec.hpp b/packages/krino/krino/krino_lib/Akri_Vec.hpp new file mode 100644 index 000000000000..e1213d9bb77b --- /dev/null +++ b/packages/krino/krino/krino_lib/Akri_Vec.hpp @@ -0,0 +1,26 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Vec_h +#define Akri_Vec_h + +#include + +namespace krino { + +using stk::math::MemberInit; +using stk::math::Vec; +typedef stk::math::Vec Vector3d; +typedef stk::math::Vec Vector2d; +typedef stk::math::Vec Float3d; + +bool is_less_than_in_x_then_y_then_z(const Vector3d& A, const Vector3d &B); + +} // namespace krino + +#endif // Akri_Vec_h diff --git a/packages/krino/krino/krino_lib/CMakeLists.txt b/packages/krino/krino/krino_lib/CMakeLists.txt new file mode 100644 index 000000000000..ac224f4eba62 --- /dev/null +++ b/packages/krino/krino/krino_lib/CMakeLists.txt @@ -0,0 +1,19 @@ +SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + krino_lib + HEADERS ${HEADERS} + SOURCES ${SOURCES} + DEPLIBS krino_master_element_lib) + +INSTALL(FILES ${HEADERS} DESTINATION + ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/krino_lib) + diff --git a/packages/krino/krino/master_element/Akri_MasterElement.hpp b/packages/krino/krino/master_element/Akri_MasterElement.hpp new file mode 100644 index 000000000000..5749c4a6ca5d --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElement.hpp @@ -0,0 +1,21 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MasterElement_h +#define Akri_MasterElement_h + +#include + +namespace krino { + +// Set actual MasterElement type we will use +typedef MasterElementHybrid MasterElement; + +} // end namespace krino + +#endif // Akri_MasterElement_h diff --git a/packages/krino/krino/master_element/Akri_MasterElementBasis.hpp b/packages/krino/krino/master_element/Akri_MasterElementBasis.hpp new file mode 100644 index 000000000000..39cb2743a920 --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElementBasis.hpp @@ -0,0 +1,701 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MasterElementBasis_h +#define Akri_MasterElementBasis_h + +#define ATTR_RESTRICT __restrict__ + +namespace krino { + +class Basis +{ +public: + Basis(unsigned deg) : my_degree(deg) {} + + virtual ~Basis() {} + virtual void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const = 0; + virtual void shape_fcn(const int nint, const double* p_coords, double* result) const = 0; + virtual void shape_fcn_deriv(const int nint, const double* p_coords, double* result ) const = 0; + unsigned degree() const { return my_degree; } + +private: + unsigned my_degree; +}; + +class Basis_LINE_2 : public Basis +{ +public: + Basis_LINE_2() : Basis(1) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[0] = -1.0; + p_coords[1] = 1.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[ip]; + result[ip*2 + 0] = ( 1.0 - x ) * 0.5; + result[ip*2 + 1] = ( 1.0 + x ) * 0.5; + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + result[ip*2 + 0] = -0.5; + result[ip*2 + 1] = 0.5; + } + } +}; + +class Basis_LINE_3 : public Basis +{ +public: + Basis_LINE_3() : Basis(2) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[0] = -1.0; + p_coords[1] = 1.0; + p_coords[2] = 0.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[ip]; + result[ip*3 + 0] = -x * ( 1.0 - x ) * 0.5; + result[ip*3 + 1] = x * ( 1.0 + x ) * 0.5; + result[ip*3 + 2] = ( 1.0 - x ) * ( 1.0 + x ); + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[ip]; + result[ip*3 + 0] = x; + result[ip*3 + 1] = x; + result[ip*3 + 2] = -2.0*x; + } + } +}; + +class Basis_TRI_3 : public Basis +{ +public: + Basis_TRI_3() : Basis(1) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[0] = 0.0; p_coords[1] = 0.0; + p_coords[2] = 1.0; p_coords[3] = 0.0; + p_coords[4] = 0.0; p_coords[5] = 1.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[2*ip + 0]; + const double y = p_coords[2*ip + 1]; + result[ip*3 + 0] = 1.0 - x - y; + result[ip*3 + 1] = x; + result[ip*3 + 2] = y; + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + result[ip*6 + 0] = -1.0; + result[ip*6 + 1] = -1.0; + result[ip*6 + 2] = 1.0; + result[ip*6 + 3] = 0.0; + result[ip*6 + 4] = 0.0; + result[ip*6 + 5] = 1.0; + } + } +}; + +class Basis_TRI_6 : public Basis +{ +public: + Basis_TRI_6() : Basis(2) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = 0.0; p_coords[ 1] = 0.0; + p_coords[ 2] = 1.0; p_coords[ 3] = 0.0; + p_coords[ 4] = 0.0; p_coords[ 5] = 1.0; + p_coords[ 6] = 0.5; p_coords[ 7] = 0.0; + p_coords[ 8] = 0.5; p_coords[ 9] = 0.5; + p_coords[10] = 0.0; p_coords[11] = 0.5; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[2*ip + 0]; + const double y = p_coords[2*ip + 1]; + result[ip*6 + 0] = (x + y - 1.0)*(2.0*x + 2.0*y - 1.0); + result[ip*6 + 1] = x*(2.0*x - 1.0); + result[ip*6 + 2] = y*(2.0*y - 1.0); + result[ip*6 + 3] = -4.0*x*(x + y - 1.0); + result[ip*6 + 4] = 4.0*x*y; + result[ip*6 + 5] = -4.0*y*(x + y - 1.0); + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[2*ip + 0]; + const double y = p_coords[2*ip + 1]; + result[ip*12 + 0] = 4.0*x + 4.0*y - 3.0; + result[ip*12 + 1] = 4.0*x + 4.0*y - 3.0; + result[ip*12 + 2] = 4.0*x - 1.0; + result[ip*12 + 3] = 0.0; + result[ip*12 + 4] = 0.0; + result[ip*12 + 5] = 4.0*y - 1.0; + result[ip*12 + 6] = -4.0*(2.0*x + y - 1.0); + result[ip*12 + 7] = -4.0*x; + result[ip*12 + 8] = 4.0*y; + result[ip*12 + 9] = 4.0*x; + result[ip*12 + 10] = -4.0*y; + result[ip*12 + 11] = -4.0*(x + 2.0*y - 1.0); + } + } +}; + +class Basis_QUAD_4 : public Basis +{ +public: + Basis_QUAD_4() : Basis(1) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = -1.0; p_coords[ 1] = -1.0; + p_coords[ 2] = 1.0; p_coords[ 3] = -1.0; + p_coords[ 4] = 1.0; p_coords[ 5] = 1.0; + p_coords[ 6] = -1.0; p_coords[ 7] = 1.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[2*ip + 0]; + const double y = p_coords[2*ip + 1]; + result[ip*4 + 0] = 0.25*(1.0 - x)*(1.0 - y); + result[ip*4 + 1] = 0.25*(1.0 + x)*(1.0 - y); + result[ip*4 + 2] = 0.25*(1.0 + x)*(1.0 + y); + result[ip*4 + 3] = 0.25*(1.0 - x)*(1.0 + y); + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[2*ip + 0]; + const double y = p_coords[2*ip + 1]; + result[ip*8 + 0] = -0.25*(1.0 - y); + result[ip*8 + 1] = -0.25*(1.0 - x); + result[ip*8 + 2] = 0.25*(1.0 - y); + result[ip*8 + 3] = -0.25*(1.0 + x); + result[ip*8 + 4] = 0.25*(1.0 + y); + result[ip*8 + 5] = 0.25*(1.0 + x); + result[ip*8 + 6] = -0.25*(1.0 + y); + result[ip*8 + 7] = 0.25*(1.0 - x); + } + } +}; + +class Basis_QUAD_9 : public Basis +{ +public: + Basis_QUAD_9() : Basis(2) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = -1.0; p_coords[ 1] = -1.0; + p_coords[ 2] = 1.0; p_coords[ 3] = -1.0; + p_coords[ 4] = 1.0; p_coords[ 5] = 1.0; + p_coords[ 6] = -1.0; p_coords[ 7] = 1.0; + p_coords[ 8] = 0.0; p_coords[ 9] = -1.0; + p_coords[10] = 1.0; p_coords[11] = 0.0; + p_coords[12] = 0.0; p_coords[13] = 1.0; + p_coords[14] = -1.0; p_coords[15] = 0.0; + p_coords[16] = 0.0; p_coords[17] = 0.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[2*ip + 0]; + const double y = p_coords[2*ip + 1]; + result[ip*9 + 0] = 0.25*x*(x - 1.0)*y*(y - 1.0); + result[ip*9 + 1] = 0.25*x*(x + 1.0)*y*(y - 1.0); + result[ip*9 + 2] = 0.25*x*(x + 1.0)*y*(y + 1.0); + result[ip*9 + 3] = 0.25*x*(x - 1.0)*y*(y + 1.0); + result[ip*9 + 4] = 0.5*(1.0 - x)*(1.0 + x)*y*(y - 1.0); + result[ip*9 + 5] = 0.5*x*(x + 1.0)*(1.0 - y)*(1.0 + y); + result[ip*9 + 6] = 0.5*(1.0 - x)*(1.0 + x)*y*(y + 1.0); + result[ip*9 + 7] = 0.5*x*(x - 1.0)*(1.0 - y)*(1.0 + y); + result[ip*9 + 8] = (1.0 - x)*(1.0 + x)*(1.0 - y)*(1.0 + y); + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[2*ip + 0]; + const double y = p_coords[2*ip + 1]; + result[ip*18 + 0] = (-0.25 + 0.5*x)*(-1. + y)*y; + result[ip*18 + 1] = (-1.0 + x)*x*(-0.25 + 0.5*y); + result[ip*18 + 2] = (0.25 + 0.5*x)*(-1. + y)*y; + result[ip*18 + 3] = x*(1. + x)*(-0.25 + 0.5*y); + result[ip*18 + 4] = (0.25 + 0.5*x)*y*(1. + y); + result[ip*18 + 5] = x*(1. + x)*(0.25 + 0.5*y); + result[ip*18 + 6] = (-0.25 + 0.5*x)*y*(1. + y); + result[ip*18 + 7] = (-1. + x)*x*(0.25 + 0.5*y); + result[ip*18 + 8] = x*(1.0 - y)*y; + result[ip*18 + 9] = 0.5*(1.0 - x)*(1.0 + x)*(-1.0 + 2.0*y); + result[ip*18 + 10] = 0.5*(1.0 - y)*(1.0 + y)*(1.0 + 2.0*x); + result[ip*18 + 11] = -x*(1.0 + x)*y; + result[ip*18 + 12] = -y*(1.0 + y)*x; + result[ip*18 + 13] = 0.5*(1.0 - x)*(1.0 + x)*(1.0 + 2.0*y); + result[ip*18 + 14] = 0.5*(1.0 - y)*(1.0+ y)*(-1.0 + 2.0*x); + result[ip*18 + 15] = (1.0 - x)*x*y; + result[ip*18 + 16] = -2.0*(1.0 - y)*(1.0 + y)*x; + result[ip*18 + 17] = -2.0*(1.0 - x)*(1.0 + x)*y; + } + } +}; + +class Basis_TET_4 : public Basis +{ +public: + Basis_TET_4() : Basis(1) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = 0.0; p_coords[ 1] = 0.0; p_coords[ 2] = 0.0; + p_coords[ 3] = 1.0; p_coords[ 4] = 0.0; p_coords[ 5] = 0.0; + p_coords[ 6] = 0.0; p_coords[ 7] = 1.0; p_coords[ 8] = 0.0; + p_coords[ 9] = 0.0; p_coords[10] = 0.0; p_coords[11] = 1.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + result[ip*4 + 0] = 1.0 - x - y - z; + result[ip*4 + 1] = x; + result[ip*4 + 2] = y; + result[ip*4 + 3] = z; + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + result[ip*12 + 0] = -1.0; + result[ip*12 + 1] = -1.0; + result[ip*12 + 2] = -1.0; + result[ip*12 + 3] = 1.0; + result[ip*12 + 4] = 0.0; + result[ip*12 + 5] = 0.0; + result[ip*12 + 6] = 0.0; + result[ip*12 + 7] = 1.0; + result[ip*12 + 8] = 0.0; + result[ip*12 + 9] = 0.0; + result[ip*12 + 10] = 0.0; + result[ip*12 + 11] = 1.0; + } + } +}; + +class Basis_TET_10 : public Basis +{ +public: + Basis_TET_10() : Basis(2) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = 0.0; p_coords[ 1] = 0.0; p_coords[ 2] = 0.0; + p_coords[ 3] = 1.0; p_coords[ 4] = 0.0; p_coords[ 5] = 0.0; + p_coords[ 6] = 0.0; p_coords[ 7] = 1.0; p_coords[ 8] = 0.0; + p_coords[ 9] = 0.0; p_coords[10] = 0.0; p_coords[11] = 1.0; + p_coords[12] = 0.5; p_coords[13] = 0.0; p_coords[14] = 0.0; + p_coords[15] = 0.5; p_coords[16] = 0.5; p_coords[17] = 0.0; + p_coords[18] = 0.0; p_coords[19] = 0.5; p_coords[20] = 0.0; + p_coords[21] = 0.0; p_coords[22] = 0.0; p_coords[23] = 0.5; + p_coords[24] = 0.5; p_coords[25] = 0.0; p_coords[26] = 0.5; + p_coords[27] = 0.0; p_coords[28] = 0.5; p_coords[29] = 0.5; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + result[ip*10 + 0] = (-1. + x + y + z)*(-1. + 2.*x + 2.*y + 2.*z); + result[ip*10 + 1] = x*(-1. + 2.*x); + result[ip*10 + 2] = y*(-1. + 2.*y); + result[ip*10 + 3] = z*(-1. + 2.*z); + result[ip*10 + 4] = -4.*x*(-1. + x + y + z); + result[ip*10 + 5] = 4.*x*y; + result[ip*10 + 6] = -4.*y*(-1. + x + y + z); + result[ip*10 + 7] = -4.*z*(-1. + x + y + z); + result[ip*10 + 8] = 4.*x*z; + result[ip*10 + 9] = 4.*y*z; + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + result[ip*30 + 0] = -3.+ 4.*x + 4.*y + 4.*z; + result[ip*30 + 1] = -3.+ 4.*x + 4.*y + 4.*z; + result[ip*30 + 2] = -3.+ 4.*x + 4.*y + 4.*z; + result[ip*30 + 3] = -1.+ 4.*x; + result[ip*30 + 4] = 0.; + result[ip*30 + 5] = 0.; + result[ip*30 + 6] = 0.; + result[ip*30 + 7] = -1.+ 4.*y; + result[ip*30 + 8] = 0.; + result[ip*30 + 9] = 0.; + result[ip*30 + 10] = 0.; + result[ip*30 + 11] = -1.+ 4.*z; + result[ip*30 + 12] = -4.*(-1.+ 2*x + y + z); + result[ip*30 + 13] = -4.*x; + result[ip*30 + 14] = -4.*x; + result[ip*30 + 15] = 4.*y; + result[ip*30 + 16] = 4.*x; + result[ip*30 + 17] = 0.; + result[ip*30 + 18] = -4.*y; + result[ip*30 + 19] = -4.*(-1.+ x + 2*y + z); + result[ip*30 + 20] = -4.*y; + result[ip*30 + 21] = -4.*z; + result[ip*30 + 22] = -4.*z; + result[ip*30 + 23] = -4.*(-1.+ x + y + 2*z); + result[ip*30 + 24] = 4.*z; + result[ip*30 + 25] = 0.; + result[ip*30 + 26] = 4.*x; + result[ip*30 + 27] = 0.; + result[ip*30 + 28] = 4.*z; + result[ip*30 + 29] = 4.*y; + } + } +}; + +class Basis_HEX_8 : public Basis +{ +public: + Basis_HEX_8() : Basis(1) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = -1.0; p_coords[ 1] = -1.0; p_coords[ 2] = -1.0; + p_coords[ 3] = 1.0; p_coords[ 4] = -1.0; p_coords[ 5] = -1.0; + p_coords[ 6] = 1.0; p_coords[ 7] = 1.0; p_coords[ 8] = -1.0; + p_coords[ 9] = -1.0; p_coords[10] = 1.0; p_coords[11] = -1.0; + p_coords[12] = -1.0; p_coords[13] = -1.0; p_coords[14] = 1.0; + p_coords[15] = 1.0; p_coords[16] = -1.0; p_coords[17] = 1.0; + p_coords[18] = 1.0; p_coords[19] = 1.0; p_coords[20] = 1.0; + p_coords[21] = -1.0; p_coords[22] = 1.0; p_coords[23] = 1.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + result[ip*8 + 0] = 0.125*(1.0 - x)*(1.0 - y)*(1.0 - z); + result[ip*8 + 1] = 0.125*(1.0 + x)*(1.0 - y)*(1.0 - z); + result[ip*8 + 2] = 0.125*(1.0 + x)*(1.0 + y)*(1.0 - z); + result[ip*8 + 3] = 0.125*(1.0 - x)*(1.0 + y)*(1.0 - z); + result[ip*8 + 4] = 0.125*(1.0 - x)*(1.0 - y)*(1.0 + z); + result[ip*8 + 5] = 0.125*(1.0 + x)*(1.0 - y)*(1.0 + z); + result[ip*8 + 6] = 0.125*(1.0 + x)*(1.0 + y)*(1.0 + z); + result[ip*8 + 7] = 0.125*(1.0 - x)*(1.0 + y)*(1.0 + z); + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + result[ip*24 + 0] = -(1.0 - y)*(1.0 - z)*0.125; + result[ip*24 + 1] = -(1.0 - x)*(1.0 - z)*0.125; + result[ip*24 + 2] = -(1.0 - x)*(1.0 - y)*0.125; + result[ip*24 + 3] = (1.0 - y)*(1.0 - z)*0.125; + result[ip*24 + 4] = -(1.0 + x)*(1.0 - z)*0.125; + result[ip*24 + 5] = -(1.0 + x)*(1.0 - y)*0.125; + result[ip*24 + 6] = (1.0 + y)*(1.0 - z)*0.125; + result[ip*24 + 7] = (1.0 + x)*(1.0 - z)*0.125; + result[ip*24 + 8] = -(1.0 + x)*(1.0 + y)*0.125; + result[ip*24 + 9] = -(1.0 + y)*(1.0 - z)*0.125; + result[ip*24 + 10] = (1.0 - x)*(1.0 - z)*0.125; + result[ip*24 + 11] = -(1.0 - x)*(1.0 + y)*0.125; + result[ip*24 + 12] = -(1.0 - y)*(1.0 + z)*0.125; + result[ip*24 + 13] = -(1.0 - x)*(1.0 + z)*0.125; + result[ip*24 + 14] = (1.0 - x)*(1.0 - y)*0.125; + result[ip*24 + 15] = (1.0 - y)*(1.0 + z)*0.125; + result[ip*24 + 16] = -(1.0 + x)*(1.0 + z)*0.125; + result[ip*24 + 17] = (1.0 + x)*(1.0 - y)*0.125; + result[ip*24 + 18] = (1.0 + y)*(1.0 + z)*0.125; + result[ip*24 + 19] = (1.0 + x)*(1.0 + z)*0.125; + result[ip*24 + 20] = (1.0 + x)*(1.0 + y)*0.125; + result[ip*24 + 21] = -(1.0 + y)*(1.0 + z)*0.125; + result[ip*24 + 22] = (1.0 - x)*(1.0 + z)*0.125; + result[ip*24 + 23] = (1.0 - x)*(1.0 + y)*0.125; + } + } +}; + +class Basis_HEX_27 : public Basis +{ +public: + Basis_HEX_27() : Basis(1) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = -1.0; p_coords[ 1] = -1.0; p_coords[ 2] = -1.0; + p_coords[ 3] = 1.0; p_coords[ 4] = -1.0; p_coords[ 5] = -1.0; + p_coords[ 6] = 1.0; p_coords[ 7] = 1.0; p_coords[ 8] = -1.0; + p_coords[ 9] = -1.0; p_coords[10] = 1.0; p_coords[11] = -1.0; + p_coords[12] = -1.0; p_coords[13] = -1.0; p_coords[14] = 1.0; + p_coords[15] = 1.0; p_coords[16] = -1.0; p_coords[17] = 1.0; + p_coords[18] = 1.0; p_coords[19] = 1.0; p_coords[20] = 1.0; + p_coords[21] = -1.0; p_coords[22] = 1.0; p_coords[23] = 1.0; + p_coords[24] = 0.0; p_coords[25] = -1.0; p_coords[26] = -1.0; + p_coords[27] = 1.0; p_coords[28] = 0.0; p_coords[29] = -1.0; + p_coords[30] = 0.0; p_coords[31] = 1.0; p_coords[32] = -1.0; + p_coords[33] = -1.0; p_coords[34] = 0.0; p_coords[35] = -1.0; + p_coords[36] = -1.0; p_coords[37] = -1.0; p_coords[38] = 0.0; + p_coords[39] = 1.0; p_coords[40] = -1.0; p_coords[41] = 0.0; + p_coords[42] = 1.0; p_coords[43] = 1.0; p_coords[44] = 0.0; + p_coords[45] = -1.0; p_coords[46] = 1.0; p_coords[47] = 0.0; + p_coords[48] = 0.0; p_coords[49] = -1.0; p_coords[50] = 1.0; + p_coords[51] = 1.0; p_coords[52] = 0.0; p_coords[53] = 1.0; + p_coords[54] = 0.0; p_coords[55] = 1.0; p_coords[56] = 1.0; + p_coords[57] = -1.0; p_coords[58] = 0.0; p_coords[59] = 1.0; + p_coords[60] = 0.0; p_coords[61] = 0.0; p_coords[62] = 0.0; + p_coords[63] = 0.0; p_coords[64] = 0.0; p_coords[65] = -1.0; + p_coords[66] = 0.0; p_coords[67] = 0.0; p_coords[68] = 1.0; + p_coords[69] = -1.0; p_coords[70] = 0.0; p_coords[71] = 0.0; + p_coords[72] = 1.0; p_coords[73] = 0.0; p_coords[74] = 0.0; + p_coords[75] = 0.0; p_coords[76] = -1.0; p_coords[77] = 0.0; + p_coords[78] = 0.0; p_coords[79] = 1.0; p_coords[80] = 0.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + result[ip*27 + 0] = 0.125*(-1. + x)*x*(-1. + y)*y*(-1. + z)*z; + result[ip*27 + 1] = 0.125*x*(1.+ x)*(-1. + y)*y*(-1. + z)*z; + result[ip*27 + 2] = 0.125*x*(1.+ x)*y*(1.+ y)*(-1. + z)*z; + result[ip*27 + 3] = 0.125*(-1. + x)*x*y*(1.+ y)*(-1. + z)*z; + result[ip*27 + 4] = 0.125*(-1. + x)*x*(-1. + y)*y*z*(1.+ z); + result[ip*27 + 5] = 0.125*x*(1.+ x)*(-1. + y)*y*z*(1.+ z); + result[ip*27 + 6] = 0.125*x*(1.+ x)*y*(1.+ y)*z*(1.+ z); + result[ip*27 + 7] = 0.125*(-1. + x)*x*y*(1.+ y)*z*(1.+ z); + result[ip*27 + 8] = 0.25*(1. - x)*(1. + x)*(-1. + y)*y*(-1. + z)*z; + result[ip*27 + 9] = 0.25*x*(1.+ x)*(1. - y)*(1. + y)*(-1. + z)*z; + result[ip*27 + 10] = 0.25*(1. - x)*(1. + x)*y*(1.+ y)*(-1. + z)*z; + result[ip*27 + 11] = 0.25*(-1. + x)*x*(1. - y)*(1. + y)*(-1. + z)*z; + result[ip*27 + 12] = 0.25*(-1. + x)*x*(-1. + y)*y*(1. - z)*(1. + z); + result[ip*27 + 13] = 0.25*x*(1.+ x)*(-1. + y)*y*(1. - z)*(1. + z); + result[ip*27 + 14] = 0.25*x*(1.+ x)*y*(1.+ y)*(1. - z)*(1. + z); + result[ip*27 + 15] = 0.25*(-1. + x)*x*y*(1.+ y)*(1. - z)*(1. + z); + result[ip*27 + 16] = 0.25*(1. - x)*(1. + x)*(-1. + y)*y*z*(1.+ z); + result[ip*27 + 17] = 0.25*x*(1.+ x)*(1. - y)*(1. + y)*z*(1.+ z); + result[ip*27 + 18] = 0.25*(1. - x)*(1. + x)*y*(1.+ y)*z*(1.+ z); + result[ip*27 + 19] = 0.25*(-1. + x)*x*(1. - y)*(1. + y)*z*(1.+ z); + result[ip*27 + 20] = (1. - x)*(1. + x)*(1. - y)*(1. + y)*(1. - z)*(1. + z); + result[ip*27 + 21] = 0.5*(1. - x)*(1. + x)*(1. - y)*(1. + y)*(-1. + z)*z; + result[ip*27 + 22] = 0.5*(1. - x)*(1. + x)*(1. - y)*(1. + y)*z*(1.+ z); + result[ip*27 + 23] = 0.5*(-1. + x)*x*(1. - y)*(1. + y)*(1. - z)*(1. + z); + result[ip*27 + 24] = 0.5*x*(1.+ x)*(1. - y)*(1. + y)*(1. - z)*(1. + z); + result[ip*27 + 25] = 0.5*(1. - x)*(1. + x)*(-1. + y)*y*(1. - z)*(1. + z); + result[ip*27 + 26] = 0.5*(1. - x)*(1. + x)*y*(1.+ y)*(1. - z)*(1. + z); + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + result[ip*81 + 0] = (-0.125 + 0.25*x)*(-1. + y)*y*(-1. + z)*z; + result[ip*81 + 1] = (-1. + x)*x*(-0.125 + 0.25*y)*(-1. + z)*z; + result[ip*81 + 2] = (-1. + x)*x*(-1. + y)*y*(-0.125 + 0.25*z); + result[ip*81 + 3] = (0.125 + 0.25*x)*(-1. + y)*y*(-1. + z)*z; + result[ip*81 + 4] = x*(1. + x)*(-0.125 + 0.25*y)*(-1. + z)*z; + result[ip*81 + 5] = x*(1. + x)*(-1. + y)*y*(-0.125 + 0.25*z); + result[ip*81 + 6] = (0.125 + 0.25*x)*y*(1. + y)*(-1. + z)*z; + result[ip*81 + 7] = x*(1. + x)*(0.125 + 0.25*y)*(-1. + z)*z; + result[ip*81 + 8] = x*(1. + x)*y*(1. + y)*(-0.125 + 0.25*z); + result[ip*81 + 9] = (-0.125 + 0.25*x)*y*(1. + y)*(-1. + z)*z; + result[ip*81 + 10] = (-1. + x)*x*(0.125 + 0.25*y)*(-1. + z)*z; + result[ip*81 + 11] = (-1. + x)*x*y*(1. + y)*(-0.125 + 0.25*z); + result[ip*81 + 12] = (-0.125 + 0.25*x)*(-1. + y)*y*z*(1. + z); + result[ip*81 + 13] = (-1. + x)*x*(-0.125 + 0.25*y)*z*(1. + z); + result[ip*81 + 14] = (-1. + x)*x*(-1. + y)*y*(0.125 + 0.25*z); + result[ip*81 + 15] = (0.125 + 0.25*x)*(-1. + y)*y*z*(1. + z); + result[ip*81 + 16] = x*(1. + x)*(-0.125 + 0.25*y)*z*(1. + z); + result[ip*81 + 17] = x*(1. + x)*(-1. + y)*y*(0.125 + 0.25*z); + result[ip*81 + 18] = (0.125 + 0.25*x)*y*(1. + y)*z*(1. + z); + result[ip*81 + 19] = x*(1. + x)*(0.125 + 0.25*y)*z*(1. + z); + result[ip*81 + 20] = x*(1. + x)*y*(1. + y)*(0.125 + 0.25*z); + result[ip*81 + 21] = (-0.125 + 0.25*x)*y*(1. + y)*z*(1. + z); + result[ip*81 + 22] = (-1. + x)*x*(0.125 + 0.25*y)*z*(1. + z); + result[ip*81 + 23] = (-1. + x)*x*y*(1. + y)*(0.125 + 0.25*z); + result[ip*81 + 24] = -0.5*x*(-1. + y)*y*(-1. + z)*z; + result[ip*81 + 25] = (1. - x)*(1. + x)*(-0.25 + 0.5*y)*(-1. + z)*z; + result[ip*81 + 26] = (1. - x)*(1. + x)*(-1. + y)*y*(-0.25 + 0.5*z); + result[ip*81 + 27] = (0.25 + 0.5*x)*(1. - y)*(1. + y)*(-1. + z)*z; + result[ip*81 + 28] = x*(1. + x)*(-0.5*y)*(-1. + z)*z; + result[ip*81 + 29] = x*(1. + x)*(1. - y)*(1. + y)*(-0.25 + 0.5*z); + result[ip*81 + 30] = -0.5*x*y*(1. + y)*(-1. + z)*z; + result[ip*81 + 31] = (1. - x)*(1. + x)*(0.25 + 0.5*y)*(-1. + z)*z; + result[ip*81 + 32] = (1. - x)*(1. + x)*y*(1. + y)*(-0.25 + 0.5*z); + result[ip*81 + 33] = (-0.25 + 0.5*x)*(1. - y)*(1. + y)*(-1. + z)*z; + result[ip*81 + 34] = (-1. + x)*x*(-0.5*y)*(-1. + z)*z; + result[ip*81 + 35] = (-1. + x)*x*(1. - y)*(1. + y)*(-0.25 + 0.5*z); + result[ip*81 + 36] = (-0.25 + 0.5*x)*(-1. + y)*y*(1. - z)*(1. + z); + result[ip*81 + 37] = (-1. + x)*x*(-0.25 + 0.5*y)*(1. - z)*(1. + z); + result[ip*81 + 38] = (-1. + x)*x*(-1. + y)*y*(-0.5*z); + result[ip*81 + 39] = (0.25 + 0.5*x)*(-1. + y)*y*(1. - z)*(1. + z); + result[ip*81 + 40] = x*(1. + x)*(-0.25 + 0.5*y)*(1. - z)*(1. + z); + result[ip*81 + 41] = x*(1. + x)*(-1. + y)*y*(-0.5*z); + result[ip*81 + 42] = (0.25 + 0.5*x)*y*(1. + y)*(1. - z)*(1. + z); + result[ip*81 + 43] = x*(1. + x)*(0.25 + 0.5*y)*(1. - z)*(1. + z); + result[ip*81 + 44] = x*(1. + x)*y*(1. + y)*(-0.5*z); + result[ip*81 + 45] = (-0.25 + 0.5*x)*y*(1. + y)*(1. - z)*(1. + z); + result[ip*81 + 46] = (-1. + x)*x*(0.25 + 0.5*y)*(1. - z)*(1. + z); + result[ip*81 + 47] = (-1. + x)*x*y*(1. + y)*(-0.5*z); + result[ip*81 + 48] = -0.5*x*(-1. + y)*y*z*(1. + z); + result[ip*81 + 49] = (1. - x)*(1. + x)*(-0.25 + 0.5*y)*z*(1. + z); + result[ip*81 + 50] = (1. - x)*(1. + x)*(-1. + y)*y*(0.25 + 0.5*z); + result[ip*81 + 51] = (0.25 + 0.5*x)*(1. - y)*(1. + y)*z*(1. + z); + result[ip*81 + 52] = x*(1. + x)*(-0.5*y)*z*(1. + z); + result[ip*81 + 53] = x*(1. + x)*(1. - y)*(1. + y)*(0.25 + 0.5*z); + result[ip*81 + 54] = -0.5*x*y*(1. + y)*z*(1. + z); + result[ip*81 + 55] = (1. - x)*(1. + x)*(0.25 + 0.5*y)*z*(1. + z); + result[ip*81 + 56] = (1. - x)*(1. + x)*y*(1. + y)*(0.25 + 0.5*z); + result[ip*81 + 57] = (-0.25 + 0.5*x)*(1. - y)*(1. + y)*z*(1. + z); + result[ip*81 + 58] = (-1. + x)*x*(-0.5*y)*z*(1. + z); + result[ip*81 + 59] = (-1. + x)*x*(1. - y)*(1. + y)*(0.25 + 0.5*z); + result[ip*81 + 60] = -2.*x*(1. - y)*(1. + y)*(1. - z)*(1. + z); + result[ip*81 + 61] = (1. - x)*(1. + x)*(-2.*y)*(1. - z)*(1. + z); + result[ip*81 + 62] = (1. - x)*(1. + x)*(1. - y)*(1. + y)*(-2.*z); + result[ip*81 + 63] = -x*(1. - y)*(1. + y)*(-1. + z)*z; + result[ip*81 + 64] = (1. - x)*(1. + x)*(-y)*(-1. + z)*z; + result[ip*81 + 65] = (1. - x)*(1. + x)*(1. - y)*(1. + y)*(-0.5 + z); + result[ip*81 + 66] = -x*(1. - y)*(1. + y)*z*(1. + z); + result[ip*81 + 67] = (1. - x)*(1. + x)*(-y)*z*(1. + z); + result[ip*81 + 68] = (1. - x)*(1. + x)*(1. - y)*(1. + y)*(0.5 + z); + result[ip*81 + 69] = (-0.5 + x)*(1. - y)*(1. + y)*(1. - z)*(1. + z); + result[ip*81 + 70] = (-1. + x)*x*(-y)*(1. - z)*(1. + z); + result[ip*81 + 71] = (-1. + x)*x*(1. - y)*(1. + y)*(-z); + result[ip*81 + 72] = (0.5 + x)*(1. - y)*(1. + y)*(1. - z)*(1. + z); + result[ip*81 + 73] = x*(1. + x)*(-y)*(1. - z)*(1. + z); + result[ip*81 + 74] = x*(1. + x)*(1. - y)*(1. + y)*(-z); + result[ip*81 + 75] = -x*(-1. + y)*y*(1. - z)*(1. + z); + result[ip*81 + 76] = (1. - x)*(1. + x)*(-0.5 + y)*(1. - z)*(1. + z); + result[ip*81 + 77] = (1. - x)*(1. + x)*(-1. + y)*y*(-z); + result[ip*81 + 78] = -x*y*(1. + y)*(1. - z)*(1. + z); + result[ip*81 + 79] = (1. - x)*(1. + x)*(0.5 + y)*(1. - z)*(1. + z); + result[ip*81 + 80] = (1. - x)*(1. + x)*y*(1. + y)*(-z); + } + } +}; + +class Basis_WEDGE_6 : public Basis +{ +public: + Basis_WEDGE_6() : Basis(1) {} + + void nodal_parametric_coordinates(double* ATTR_RESTRICT p_coords) const + { + p_coords[ 0] = 0.0; p_coords[ 1] = 0.0; p_coords[ 2] = -1.0; + p_coords[ 3] = 1.0; p_coords[ 4] = 0.0; p_coords[ 5] = -1.0; + p_coords[ 6] = 0.0; p_coords[ 7] = 1.0; p_coords[ 8] = -1.0; + p_coords[ 9] = 0.0; p_coords[10] = 0.0; p_coords[11] = 1.0; + p_coords[12] = 1.0; p_coords[13] = 0.0; p_coords[14] = 1.0; + p_coords[15] = 0.0; p_coords[16] = 1.0; p_coords[17] = 1.0; + } + void shape_fcn(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for (int ip(0); ip < nint; ++ip) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + const double t = 1. - x - y; + + result[ip*6 + 0] = 0.5 * t * (1.0 - z); + result[ip*6 + 1] = 0.5 * x * (1.0 - z); + result[ip*6 + 2] = 0.5 * y * (1.0 - z); + result[ip*6 + 3] = 0.5 * t * (1.0 + z); + result[ip*6 + 4] = 0.5 * x * (1.0 + z); + result[ip*6 + 5] = 0.5 * y * (1.0 + z); + } + } + void shape_fcn_deriv(const int nint, const double* ATTR_RESTRICT p_coords, double* ATTR_RESTRICT result) const + { + for ( int ip(0); ip < nint; ++ip ) + { + const double x = p_coords[3*ip + 0]; + const double y = p_coords[3*ip + 1]; + const double z = p_coords[3*ip + 2]; + const double t = 1. - x - y; + + result[ip*18 + 0] = -0.5 * (1.0 - z); + result[ip*18 + 1] = -0.5 * (1.0 - z); + result[ip*18 + 2] = -0.5 * t; + result[ip*18 + 3] = 0.5 * (1.0 - z); + result[ip*18 + 4] = 0.; + result[ip*18 + 5] = -0.5 * x; + result[ip*18 + 6] = 0.; + result[ip*18 + 7] = 0.5 * (1.0 - z); + result[ip*18 + 8] = -0.5 * y; + result[ip*18 + 9] = -0.5 * (1.0 + z); + result[ip*18 + 10] = -0.5 * (1.0 + z); + result[ip*18 + 11] = 0.5 * t; + result[ip*18 + 12] = 0.5 * (1.0 + z); + result[ip*18 + 13] = 0.; + result[ip*18 + 14] = 0.5 * x; + result[ip*18 + 15] = 0.; + result[ip*18 + 16] = 0.5 * (1.0 + z); + result[ip*18 + 17] = 0.5 * y; + } + } +}; + +} // end namespace krino + +#endif // Akri_MasterElementBasis_h diff --git a/packages/krino/krino/master_element/Akri_MasterElementCalc.cpp b/packages/krino/krino/master_element/Akri_MasterElementCalc.cpp new file mode 100644 index 000000000000..fa29448343ac --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElementCalc.cpp @@ -0,0 +1,456 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include + +namespace krino { + +void +MasterElementCalc::scalar_gradient( + const int nint, //: number of intg points + const int nelem, //: number of elements to process + const int ndims, + const int nnodes, + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) //: (nvec,nelem,nint) +{ + for ( int ip(0); ip < nint; ++ip ) { + for ( int elem(0); elem < nelem; ++elem) { + for ( int dim(0); dim < ndims; ++dim ) { + double & val = vector[ip*ndims + dim]; + val = 0.0; + for ( int node(0); node < nnodes; ++node ) { + val += gradop[( (ip*nelem + elem)*nnodes + node)*ndims + dim] * sfield[ node ]; + } + } + } + } +} + +void +MasterElementCalc::determinant( + const int num_elem_dims, + const int num_coord_dims, + const int nint, + const int npe_g, + const double* deriv_g, // (num_elem_dims,npe_g,nint) + const int nelem, + const double* coords, // (num_coord_dims,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) // (nelem) +{ + if (num_elem_dims != num_coord_dims) + { + if (2 == num_elem_dims) + { + ThrowAssert(3 == num_coord_dims); + MasterElementCalc::determinant_element2d_in_3d(nint, npe_g, deriv_g, nelem, coords, det_J, error); + } + else + { + ThrowAssert(1 == num_elem_dims); + if (2 == num_coord_dims) + { + MasterElementCalc::determinant_element1d_in_2d(nint, npe_g, deriv_g, nelem, coords, det_J, error); + } + else + { + MasterElementCalc::determinant_element1d_in_3d(nint, npe_g, deriv_g, nelem, coords, det_J, error); + } + } + } + else + { + ThrowAssert(num_elem_dims >= 2 && num_elem_dims <= 3); + if (2 == num_elem_dims) MasterElementCalc::determinant_2d(nint, npe_g, deriv_g, nelem, coords, det_J, error); + else MasterElementCalc::determinant_3d(nint, npe_g, deriv_g, nelem, coords, det_J, error); + } +} + +void +MasterElementCalc::gradient_operator( + const int num_elem_dims, + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error) +{ + ThrowAssert(num_elem_dims >= 2 && num_elem_dims <= 3); + if (2 == num_elem_dims) gradient_operator_2d(nint, npe_g, deriv_g, npe_f, deriv_f, nelem, coords, gradop, det_J, error); + else gradient_operator_3d(nint, npe_g, deriv_g, npe_f, deriv_f, nelem, coords, gradop, det_J, error); +} + +void +MasterElementCalc::determinant_2d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error) +{ + auto c2d = [coords,npe](int d, int i, int e) { return coords[d + 2*(i + npe*e)]; }; + auto d2d = [deriv,npe](int d, int n, int q) { return deriv[d + 2*(n + npe*q)]; }; + auto detj = [det_J,nelem](int e, int q) -> double& { return det_J[e+nelem*q]; }; + + for (int elem(0); elem < nelem; ++elem) error[elem] = 0.; + + for (int ke(0); ke < nelem; ++ke) { + for ( int ki(0); ki < nint; ++ki ) { + double dx_ds0 = 0.; + double dx_ds1 = 0.; + double dy_ds0 = 0.; + double dy_ds1 = 0.; + + for ( int kn(0); kn < npe; ++kn ) { + dx_ds0 += d2d(0,kn,ki)*c2d(0,kn,ke); + dx_ds1 += d2d(1,kn,ki)*c2d(0,kn,ke); + + dy_ds0 += d2d(0,kn,ki)*c2d(1,kn,ke); + dy_ds1 += d2d(1,kn,ki)*c2d(1,kn,ke); + } + + detj(ke,ki) = dx_ds0*dy_ds1 - dy_ds0*dx_ds1; + + if ( detj(ke,ki) <= 0. ) + { + error[ke] = 1.; + } + } + } +} + +void +MasterElementCalc::determinant_3d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error) +{ + auto c3d = [coords,npe](int d, int i, int e) { return coords[d + 3*(i + npe*e)]; }; + auto d3d = [deriv,npe](int d, int n, int q) { return deriv[d + 3*(n + npe*q)]; }; + auto detj = [det_J,nelem](int e, int q) -> double& { return det_J[e+nelem*q]; }; + + for (int elem(0); elem < nelem; ++elem) error[elem] = 0.; + + for (int ke(0); ke < nelem; ++ke) { + for ( int ki(0); ki < nint; ++ki ) { + double dx_ds0 = 0.; + double dx_ds1 = 0.; + double dx_ds2 = 0.; + double dy_ds0 = 0.; + double dy_ds1 = 0.; + double dy_ds2 = 0.; + double dz_ds0 = 0.; + double dz_ds1 = 0.; + double dz_ds2 = 0.; + + for ( int kn(0); kn < npe; ++kn ) { + dx_ds0 += d3d(0,kn,ki)*c3d(0,kn,ke); + dx_ds1 += d3d(1,kn,ki)*c3d(0,kn,ke); + dx_ds2 += d3d(2,kn,ki)*c3d(0,kn,ke); + + dy_ds0 += d3d(0,kn,ki)*c3d(1,kn,ke); + dy_ds1 += d3d(1,kn,ki)*c3d(1,kn,ke); + dy_ds2 += d3d(2,kn,ki)*c3d(1,kn,ke); + + dz_ds0 += d3d(0,kn,ki)*c3d(2,kn,ke); + dz_ds1 += d3d(1,kn,ki)*c3d(2,kn,ke); + dz_ds2 += d3d(2,kn,ki)*c3d(2,kn,ke); + } + + detj(ke,ki) = dx_ds0*( dy_ds1*dz_ds2 - dz_ds1*dy_ds2 ) + + dy_ds0*( dz_ds1*dx_ds2 - dx_ds1*dz_ds2 ) + + dz_ds0*( dx_ds1*dy_ds2 - dy_ds1*dx_ds2 ); + + if ( detj(ke,ki) <= 0. ) + { + error[ke] = 1.; + } + } + } +} + +void +MasterElementCalc::determinant_element2d_in_3d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error) +{ + auto c3d = [coords,npe](int d, int i, int e) { return coords[d + 3*(i + npe*e)]; }; + auto d2d = [deriv,npe](int d, int n, int q) { return deriv[d + 2*(n + npe*q)]; }; + auto detj = [det_J,nelem](int e, int q) -> double& { return det_J[e+nelem*q]; }; + + for (int elem(0); elem < nelem; ++elem) error[elem] = 0.; + + for (int ke(0); ke < nelem; ++ke) { + for ( int ki(0); ki < nint; ++ki ) { + double dx_ds0 = 0.; + double dx_ds1 = 0.; + double dy_ds0 = 0.; + double dy_ds1 = 0.; + double dz_ds0 = 0.; + double dz_ds1 = 0.; + + for ( int kn(0); kn < npe; ++kn ) { + dx_ds0 += d2d(0,kn,ki)*c3d(0,kn,ke); + dx_ds1 += d2d(1,kn,ki)*c3d(0,kn,ke); + + dy_ds0 += d2d(0,kn,ki)*c3d(1,kn,ke); + dy_ds1 += d2d(1,kn,ki)*c3d(1,kn,ke); + + dz_ds0 += d2d(0,kn,ki)*c3d(2,kn,ke); + dz_ds1 += d2d(1,kn,ki)*c3d(2,kn,ke); + } + + const double detXY = dx_ds0*dy_ds1 - dx_ds1*dy_ds0; + const double detYZ = dy_ds0*dz_ds1 - dy_ds1*dz_ds0; + const double detXZ =-dx_ds0*dz_ds1 + dx_ds1*dz_ds0; + + detj(ke,ki) = std::sqrt(detXY*detXY + detYZ*detYZ + detXZ*detXZ); + + if ( detj(ke,ki) <= 0. ) + { + error[ke] = 1.; + } + } + } +} + +void +MasterElementCalc::determinant_element1d_in_3d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error) +{ + auto c3d = [coords,npe](int d, int i, int e) { return coords[d + 3*(i + npe*e)]; }; + auto d1d = [deriv,npe](int d, int n, int q) { return deriv[d + n + npe*q]; }; + auto detj = [det_J,nelem](int e, int q) -> double& { return det_J[e+nelem*q]; }; + + for (int elem(0); elem < nelem; ++elem) error[elem] = 0.; + + for (int ke(0); ke < nelem; ++ke) { + for ( int ki(0); ki < nint; ++ki ) { + double dx_ds = 0.; + double dy_ds = 0.; + double dz_ds = 0.; + + for ( int kn(0); kn < npe; ++kn ) { + dx_ds += d1d(0,kn,ki)*c3d(0,kn,ke); + dy_ds += d1d(0,kn,ki)*c3d(1,kn,ke); + dz_ds += d1d(0,kn,ki)*c3d(2,kn,ke); + } + + detj(ke,ki) = std::sqrt(dx_ds*dx_ds + dy_ds*dy_ds + dz_ds*dz_ds); + + if ( detj(ke,ki) <= 0. ) + { + error[ke] = 1.; + } + } + } +} + +void +MasterElementCalc::determinant_element1d_in_2d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error) +{ + auto c2d = [coords,npe](int d, int i, int e) { return coords[d + 2*(i + npe*e)]; }; + auto d1d = [deriv,npe](int d, int n, int q) { return deriv[d + n + npe*q]; }; + auto detj = [det_J,nelem](int e, int q) -> double& { return det_J[e+nelem*q]; }; + + for (int elem(0); elem < nelem; ++elem) error[elem] = 0.; + + for (int ke(0); ke < nelem; ++ke) { + for ( int ki(0); ki < nint; ++ki ) { + double dx_ds = 0.; + double dy_ds = 0.; + + for ( int kn(0); kn < npe; ++kn ) { + dx_ds += d1d(0,kn,ki)*c2d(0,kn,ke); + dy_ds += d1d(0,kn,ki)*c2d(1,kn,ke); + } + + detj(ke,ki) = std::sqrt(dx_ds*dx_ds + dy_ds*dy_ds); + + if ( detj(ke,ki) <= 0. ) + { + error[ke] = 1.; + } + } + } +} + +void +MasterElementCalc::gradient_operator_2d( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error) +{ + auto c2d = [coords,npe_g](int d, int i, int e) { return coords[d + 2*(i + npe_g*e)]; }; + auto d2d = [deriv_g,npe_g](int d, int n, int q) { return deriv_g[d + 2*(n + npe_g*q)]; }; + auto d2df = [deriv_f,npe_f](int d, int n, int q) { return deriv_f[d + 2*(n + npe_f*q)]; }; + auto detj = [det_J,nelem](int e, int q) -> double& { return det_J[e+nelem*q]; }; + auto g2d = [gradop,npe_f,nelem](int d, int n, int e, int q) -> double& { return gradop[d + 2*(n + npe_f*(e+nelem*q))]; }; + + for (int elem(0); elem < nelem; ++elem) error[elem] = 0.; + + for (int ke(0); ke < nelem; ++ke) { + for ( int ki(0); ki < nint; ++ki ) { + double dx_ds0 = 0.; + double dx_ds1 = 0.; + double dy_ds0 = 0.; + double dy_ds1 = 0.; + + for ( int kn(0); kn < npe_g; ++kn ) { + dx_ds0 = dx_ds0+d2d(0,kn,ki)*c2d(0,kn,ke); + dx_ds1 = dx_ds1+d2d(1,kn,ki)*c2d(0,kn,ke); + + dy_ds0 = dy_ds0+d2d(0,kn,ki)*c2d(1,kn,ke); + dy_ds1 = dy_ds1+d2d(1,kn,ki)*c2d(1,kn,ke); + } + + detj(ke,ki) = dx_ds0*dy_ds1 - dy_ds0*dx_ds1; + + double denom = 0.0; + if ( detj(ke,ki) <= 0. ) + { + error[ke] = 1.; + } + else + { + denom = 1./detj(ke,ki); + } + + // compute the gradient operators at the integration station - + for ( int kn(0); kn < npe_f; ++kn ) { + g2d(0,kn,ki,ke) = denom * ( d2df(0,kn,ki)*dy_ds1 - d2df(1,kn,ki)*dy_ds0 ); + g2d(1,kn,ki,ke) = denom * ( d2df(1,kn,ki)*dx_ds0 - d2df(0,kn,ki)*dx_ds1 ); + } + } + } +} + +void +MasterElementCalc::gradient_operator_3d( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error) +{ + auto c3d = [coords,npe_g](int d, int i, int e) { return coords[d + 3*(i + npe_g*e)]; }; + auto d3d = [deriv_g,npe_g](int d, int n, int q) { return deriv_g[d + 3*(n + npe_g*q)]; }; + auto d3df = [deriv_f,npe_f](int d, int n, int q) { return deriv_f[d + 3*(n + npe_f*q)]; }; + auto detj = [det_J,nelem](int e, int q) -> double& { return det_J[e+nelem*q]; }; + auto g3d = [gradop,npe_f,nelem](int d, int n, int e, int q) -> double& { return gradop[d + 3*(n + npe_f*(e+nelem*q))]; }; + + for (int elem(0); elem < nelem; ++elem) error[elem] = 0.; + + for (int ke(0); ke < nelem; ++ke) { + for ( int ki(0); ki < nint; ++ki ) { + double dx_ds0 = 0.; + double dx_ds1 = 0.; + double dx_ds2 = 0.; + double dy_ds0 = 0.; + double dy_ds1 = 0.; + double dy_ds2 = 0.; + double dz_ds0 = 0.; + double dz_ds1 = 0.; + double dz_ds2 = 0.; + + for ( int kn(0); kn < npe_g; ++kn ) { + dx_ds0 = dx_ds0+d3d(0,kn,ki)*c3d(0,kn,ke); + dx_ds1 = dx_ds1+d3d(1,kn,ki)*c3d(0,kn,ke); + dx_ds2 = dx_ds2+d3d(2,kn,ki)*c3d(0,kn,ke); + + dy_ds0 = dy_ds0+d3d(0,kn,ki)*c3d(1,kn,ke); + dy_ds1 = dy_ds1+d3d(1,kn,ki)*c3d(1,kn,ke); + dy_ds2 = dy_ds2+d3d(2,kn,ki)*c3d(1,kn,ke); + + dz_ds0 = dz_ds0+d3d(0,kn,ki)*c3d(2,kn,ke); + dz_ds1 = dz_ds1+d3d(1,kn,ki)*c3d(2,kn,ke); + dz_ds2 = dz_ds2+d3d(2,kn,ki)*c3d(2,kn,ke); + } + + detj(ke,ki) = dx_ds0*( dy_ds1*dz_ds2 - dz_ds1*dy_ds2 ) + + dy_ds0*( dz_ds1*dx_ds2 - dx_ds1*dz_ds2 ) + + dz_ds0*( dx_ds1*dy_ds2 - dy_ds1*dx_ds2 ); + + double denom = 0.0; + if ( detj(ke,ki) <= 0. ) + { + error[ke] = 1.; + } + else + { + denom = 1./detj(ke,ki); + } + + // compute the gradient operators at the integration station - + for ( int kn(0); kn < npe_f; ++kn ) { + g3d(0,kn,ki,ke) = denom * + ( d3df(0,kn,ki)*(dy_ds1*dz_ds2 - dz_ds1*dy_ds2) + + d3df(1,kn,ki)*(dz_ds0*dy_ds2 - dy_ds0*dz_ds2) + + d3df(2,kn,ki)*(dy_ds0*dz_ds1 - dz_ds0*dy_ds1) ); + + g3d(1,kn,ki,ke) = denom * + ( d3df(0,kn,ki)*(dz_ds1*dx_ds2 - dx_ds1*dz_ds2) + + d3df(1,kn,ki)*(dx_ds0*dz_ds2 - dz_ds0*dx_ds2) + + d3df(2,kn,ki)*(dz_ds0*dx_ds1 - dx_ds0*dz_ds1) ); + + g3d(2,kn,ki,ke) = denom * + ( d3df(0,kn,ki)*(dx_ds1*dy_ds2 - dy_ds1*dx_ds2) + + d3df(1,kn,ki)*(dy_ds0*dx_ds2 - dx_ds0*dy_ds2) + + d3df(2,kn,ki)*(dx_ds0*dy_ds1 - dy_ds0*dx_ds1) ); + } + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/master_element/Akri_MasterElementCalc.hpp b/packages/krino/krino/master_element/Akri_MasterElementCalc.hpp new file mode 100644 index 000000000000..f5d99e687f9f --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElementCalc.hpp @@ -0,0 +1,122 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MasterElementCalc_h +#define Akri_MasterElementCalc_h + +namespace krino { + +class MasterElementCalc +{ +public: + static void scalar_gradient( + const int nint, //: number of intg points + const int nelem, //: number of elements to process + const int ndims, + const int nnodes, + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ); //: (nvec,nelem,nint) + + static void determinant( + const int num_elem_dims, + const int num_coord_dims, + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error ); // (nelem) + + static void gradient_operator( + const int num_elem_dims, + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error); // (nelem) + + static void determinant_2d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error); // (nelem) + static void determinant_3d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error); // (nelem) + + static void determinant_element2d_in_3d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error); // (nelem) + + static void determinant_element1d_in_3d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error); // (nelem) + + static void determinant_element1d_in_2d( + const int nint, + const int npe, + const double* deriv, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error); // (nelem) + + static void gradient_operator_2d( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error); // (nelem) + + static void gradient_operator_3d( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error); // (nelem) +}; + +} // end namespace krino + +#endif // Akri_MasterElementCalc_h diff --git a/packages/krino/krino/master_element/Akri_MasterElementHybrid.cpp b/packages/krino/krino/master_element/Akri_MasterElementHybrid.cpp new file mode 100644 index 000000000000..d8b7f86c13b7 --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElementHybrid.cpp @@ -0,0 +1,181 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include // for get_cell_topology +#include + +#ifdef __INTEL_COMPILER +#include +#else +//FieldContainer has shadowed variables +//this disables the checking on GCC only +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop +#endif +#include +#include +#include +#include +#include +#include + +namespace krino { + +MasterElementHybrid::MasterElementHybrid( + stk::topology topology, + std::unique_ptr basis) +: m_topology(topology), + m_Basis(std::move(basis)) +{ + // set the cubature + Intrepid::DefaultCubatureFactory cubatureFactory; + Teuchos::RCP> intrepidCubature = cubatureFactory.create(stk::mesh::get_cell_topology(topology), 2*m_Basis->degree()); + m_numIntgPts = intrepidCubature->getNumPoints(); + + m_numNodes = topology.num_nodes(); + m_numElemDims = intrepidCubature->getDimension(); + + // Allocate reference data + m_shapeFuncs.resize(m_numIntgPts*m_numNodes); + m_pointGrads.resize(m_numIntgPts*m_numNodes*m_numElemDims ); + m_refPoints.resize(m_numIntgPts*m_numElemDims); + m_refWeights.resize(m_numIntgPts); + m_refCoords.resize(m_numNodes*m_numElemDims); + + // retrieve the cubature points and weights + std::vector refPointsDims = {m_numIntgPts, m_numElemDims}; + Intrepid::FieldContainer refPointsFC( refPointsDims, m_refPoints.data() ); + std::vector refWeightsDims = {m_numIntgPts}; + Intrepid::FieldContainer refWeightsFC( refWeightsDims, m_refWeights.data() ); + intrepidCubature->getCubature(refPointsFC, refWeightsFC); + + // compute the reference values and gradients at the integration points + m_Basis->shape_fcn(m_numIntgPts, m_refPoints.data(), m_shapeFuncs.data()); + m_Basis->shape_fcn_deriv(m_numIntgPts, m_refPoints.data(), m_pointGrads.data()); + m_Basis->nodal_parametric_coordinates(m_refCoords.data()); + + m_centroidParCoords.resize(m_numElemDims, 0.); + for(int n=0; n < m_numNodes; ++n) + { + for(int d=0; d < m_numElemDims; ++d) + { + m_centroidParCoords[d] += m_refCoords[n*m_numElemDims + d]; + } + } +} + +void +MasterElementHybrid::determinant( + const int numCoordDims, + const int nelem, + const double* coords, // (numCoordDims,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const // (nelem) +{ + MasterElementCalc::determinant(m_numElemDims, numCoordDims, m_numIntgPts, m_numNodes, m_pointGrads.data(), nelem, coords, det_J, error); +} + +void +MasterElementHybrid::shape_fcn( + const int nint, // returns array(npe,nint) + const double* p_coords, + double* result) const +{ + m_Basis->shape_fcn(nint, p_coords, result); +} + +void +MasterElementHybrid::shape_fcn_deriv( + const int nint, + const double* p_coords, + double* result ) const +{ + m_Basis->shape_fcn_deriv(nint, p_coords, result); +} + + +void +MasterElementHybrid::interpolate_point( + const int npar_coord, + const double * par_coord, // (npar_coord) + const int ncomp_field, + const double * field, // (ncomp_field,num_nodes) + double * result ) const // (ncomp_field) +{ + std::vector shape(m_numNodes); + shape_fcn(1, par_coord, shape.data()); + for ( int comp(0); comp < ncomp_field; ++comp ) { + result[ comp ] = 0.0; + for ( int node(0); node < m_numNodes; ++node ) { + result[ comp ] += shape[node] * field[ ncomp_field * node + comp ]; + } + } +} + +void +MasterElementHybrid::scalar_gradient( + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const //: (nvec,nelem,nint) +{ + MasterElementCalc::scalar_gradient(m_numIntgPts, nelem, m_numElemDims, m_numNodes, gradop, det_J, sfield, vector); +} + +void +MasterElementHybrid::scalar_gradient( + const int nint, //: number of intg points + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const //: (nvec,nelem,nint) +{ + MasterElementCalc::scalar_gradient(nint, nelem, m_numElemDims, m_numNodes, gradop, det_J, sfield, vector); +} + +void +MasterElementHybrid::determinant( + const int numCoordDims, + const int nint, + const int npe_g, + const double* deriv_g, // (m_numElemDims,npe_g,nint) + const int nelem, + const double* coords, // (numCoordDims,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const // (nelem) +{ + MasterElementCalc::determinant(m_numElemDims, numCoordDims, nint, npe_g, deriv_g, nelem, coords, det_J, error); +} + +void +MasterElementHybrid::gradient_operator( + const int numCoordDims, + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error) const +{ + ThrowRequireMsg(m_numElemDims == numCoordDims, "MasterElementHybrid::gradient_operator does not support lower rank elements in higher dimensions (e.g. BAR,QUAD,TRI in 3D)."); + MasterElementCalc::gradient_operator(m_numElemDims, nint, npe_g, deriv_g, npe_f, deriv_f, nelem, coords, gradop, det_J, error); +} + +} // namespace krino diff --git a/packages/krino/krino/master_element/Akri_MasterElementHybrid.hpp b/packages/krino/krino/master_element/Akri_MasterElementHybrid.hpp new file mode 100644 index 000000000000..e0bac17fb04b --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElementHybrid.hpp @@ -0,0 +1,137 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MasterElementHybrid_h +#define Akri_MasterElementHybrid_h + +#include +#include +#include + +namespace krino { class Basis; } + +namespace krino { + +class MasterElementHybrid +{ +public: + static const MasterElementHybrid & getMasterElement(stk::topology t); + + MasterElementHybrid( + stk::topology topology, + std::unique_ptr basis); + + // Copy and assignment are not allowed + MasterElementHybrid( const MasterElementHybrid & ) = delete; + MasterElementHybrid & operator=( const MasterElementHybrid & ) = delete; + + stk::topology get_topology() const { return m_topology; } + unsigned topology_dimension() const { return m_topology.dimension(); } + + // returns the number of integration points + unsigned num_intg_pts() const { return m_numIntgPts; } + + // returns the number of nodes + unsigned num_nodes() const { return m_numNodes; } + + //: Query the integration weights + const double* intg_weights() const { return m_refWeights.data(); } + + //: Query the integration points/stations + const double* intg_pt_locations() const { return m_refPoints.data(); } + + const double * nodal_parametric_coordinates() const { return m_refCoords.data(); } + const double * centroid_parametric_coordinates() const { return m_centroidParCoords.data(); } + + void determinant( + const int numCoordDims, + const int nelem, + const double* coords, // (numCoordDims,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const; // (nelem) + void determinant( + const int numCoordDims, + const int nint, + const int npe_g, + const double* deriv_g, // (m_numElemDims,npe_g,nint) + const int nelem, + const double* coords, // (numCoordDims,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const ; // (nelem) + + //: Returns the values of the nodal interpolation shape functions + //: at the integration stations. + const double* shape_fcn() const { return m_shapeFuncs.data(); } + void shape_fcn( + const int nint, // returns array(npe,nint) + const double* p_coords, + double* result) const; + + //: Returns the derivatives of the nodal interpolation shape functions + //: with respect to the local parametric coordinates at the integration + //: stations. + const double* shape_fcn_deriv() const { return m_pointGrads.data(); } + void shape_fcn_deriv( + const int nint, + const double* p_coords, + double* result ) const; + + void interpolate_point( + const int npar_coord, + const double * par_coord, // (npar_coord) + const int ncomp_field, + const double * field, // (ncomp_field,num_nodes) + double * result ) const; // (ncomp_field) + + void gradient_operator( + const int numCoordDims, + const int nint, + const int npe_g, + const double* deriv_g, // (numElemDims,npe_g,nint) + const int npe_f, + const double* deriv_f, // (numElemDims,npe_f,nint) + const int nelem, + const double* coords, // (numElemDims,npe,nelem) + double* gradop, // (numElemDims,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error) const ; + + void scalar_gradient( + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const ; //: (nvec,nelem,nint) + void scalar_gradient( + const int nint, //: number of intg points + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const ; //: (nvec,nelem,nint) + +private: + stk::topology m_topology; + int m_numNodes; + int m_numElemDims; + int m_numIntgPts; + + std::unique_ptr m_Basis; + + // Local FieldContainers + std::vector m_shapeFuncs; + std::vector m_pointGrads; + std::vector m_refPoints; + std::vector m_refWeights; + std::vector m_refCoords; + std::vector m_centroidParCoords; +}; + +} // end namespace krino + +#endif // Akri_MasterElementHybrid_h diff --git a/packages/krino/krino/master_element/Akri_MasterElementIntrepid.cpp b/packages/krino/krino/master_element/Akri_MasterElementIntrepid.cpp new file mode 100644 index 000000000000..3b026bb7189c --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElementIntrepid.cpp @@ -0,0 +1,287 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include "Akri_MasterElementCalc.hpp" + +#ifdef __INTEL_COMPILER +#include +#else +//FieldContainer has shadowed variables +//this disables the checking on GCC only +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include // for get_cell_topology + +namespace krino { + +const MasterElementIntrepid & +MasterElementIntrepid::getMasterElement(stk::topology t, const unsigned spatial_dimension) +{ + static std::vector> all_master_elems(stk::topology::BEGIN_TOPOLOGY + stk::topology::NUM_TOPOLOGIES); + std::unique_ptr & master_elem = all_master_elems[t()]; + if (nullptr == master_elem.get()) + { + std::unique_ptr>> basis; + switch(t()) + { + case stk::topology::LINE_2: + basis = std::make_unique>>(); + break; + case stk::topology::LINE_3: + basis = std::make_unique>>(2, Intrepid::POINTTYPE_SPECTRAL); + break; + case stk::topology::TRI_3: + basis = std::make_unique>>(); + break; + case stk::topology::TRI_6: + basis = std::make_unique>>(); + break; + case stk::topology::QUAD_4: + basis = std::make_unique>>(); + break; + case stk::topology::QUAD_9: + basis = std::make_unique>>(); + break; + case stk::topology::TRI_3_2D: + basis = std::make_unique>>(); + break; + case stk::topology::TRI_6_2D: + basis = std::make_unique>>(); + break; + case stk::topology::QUAD_4_2D: + basis = std::make_unique>>(); + break; + case stk::topology::QUAD_9_2D: + basis = std::make_unique>>(); + break; + case stk::topology::TET_4: + basis = std::make_unique>>(); + break; + case stk::topology::TET_10: + basis = std::make_unique>>(); + break; + case stk::topology::HEX_8: + basis = std::make_unique>>(); + break; + case stk::topology::HEX_27: + basis = std::make_unique>>(); + break; + default: + throw std::runtime_error("Element topology not found in MasterElementIntrepid::build: " + t.name()); + break; + } + master_elem = std::make_unique(t, std::move(basis), spatial_dimension); + } + return *master_elem; +} + +MasterElementIntrepid::MasterElementIntrepid( + stk::topology topology, + std::unique_ptr > > basis, + unsigned spatial_dimension) +: m_topology(topology), + m_numCoordDims(spatial_dimension), + m_intrepidBasis(std::move(basis)) +{ + shards::CellTopology cellType = stk::mesh::get_cell_topology(topology); + + // set the cubature + Intrepid::DefaultCubatureFactory cubatureFactory; + Teuchos::RCP> intrepidCubature = cubatureFactory.create(cellType, 2*m_intrepidBasis->getDegree()); + m_numIntgPts = intrepidCubature->getNumPoints(); + + m_numNodes = topology.num_nodes(); + m_numElemDims = intrepidCubature->getDimension(); + + // Allocate reference data + m_shapeFuncs.resize(m_numIntgPts*m_numNodes); + m_pointGrads.resize(m_numIntgPts*m_numNodes*m_numElemDims ); + m_refPoints.resize(m_numIntgPts*m_numElemDims); + m_refWeights.resize(m_numIntgPts); + m_refCoords.resize(m_numNodes*m_numElemDims); + + // retrieve the cubature points and weights + std::vector refPointsDims = {m_numIntgPts, m_numElemDims}; + Intrepid::FieldContainer refPointsFC( refPointsDims, m_refPoints.data() ); + std::vector refWeightsDims = {m_numIntgPts}; + Intrepid::FieldContainer refWeightsFC( refWeightsDims, m_refWeights.data() ); + intrepidCubature->getCubature(refPointsFC, refWeightsFC); + + // compute the refernce values and gradients at the integration points + Intrepid::FieldContainer pointVals(m_numNodes, m_numIntgPts); + Intrepid::FieldContainer pointGrads(m_numNodes, m_numIntgPts, m_numElemDims); + m_intrepidBasis->getValues(pointVals, refPointsFC, Intrepid::OPERATOR_VALUE); + m_intrepidBasis->getValues(pointGrads, refPointsFC, Intrepid::OPERATOR_GRAD); + + // re-order shape functions for consistency with other master elements + for ( int ip(0); ip < m_numIntgPts; ++ip ) { + for ( int node(0); node < m_numNodes; ++node ) { + m_shapeFuncs[ip*m_numNodes + node] = pointVals(node, ip); + } + } + for ( int ip(0); ip < m_numIntgPts; ++ip ) { + for ( int node(0); node < m_numNodes; ++node ) { + for ( int dim(0); dim < m_numElemDims; ++dim ) { + m_pointGrads[(ip*m_numNodes + node)*m_numElemDims + dim] = pointGrads(node, ip, dim); + } + } + } + + for ( int node(0); node < m_numNodes; ++node ) { + const double * node_coords = Intrepid::CellTools::getReferenceNode( cellType, node ); + for ( int dim(0); dim < m_numElemDims; ++dim ) { + m_refCoords[node*m_numElemDims + dim] = node_coords[dim]; + } + } +} + +void +MasterElementIntrepid::determinant( + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const // (nelem) +{ + determinant(m_numIntgPts, m_numNodes, m_pointGrads.data(), nelem, coords, det_J, error); +} + +void +MasterElementIntrepid::shape_fcn( + const int nint, // returns array(npe,nint) + const double* p_coords, + double* result) const +{ + Intrepid::FieldContainer pointVals(m_numNodes, nint); + + // create the pcoordVec FC from the p_coords ptr and it's dimensions + std::vector pcoordDims = { nint, m_numElemDims }; + Intrepid::FieldContainer pcoordVec( pcoordDims, const_cast< double * >(p_coords) ); + + // compute the shape function values at the integration points + m_intrepidBasis->getValues(pointVals, pcoordVec, Intrepid::OPERATOR_VALUE); + + // re-order shape functions for consistency with other master elements + for ( int ip(0); ip < nint; ++ip ) { + for ( int node(0); node < m_numNodes; ++node ) { + result[ip*m_numNodes + node] = pointVals(node,ip); + } + } +} + +void +MasterElementIntrepid::shape_fcn_deriv( + const int nint, + const double* p_coords, + double* result ) const +{ + Intrepid::FieldContainer resultVec(m_numNodes, nint, m_numElemDims); + + // create the pcoordVec FC from the p_coords ptr and it's dimensions + std::vector pcoordDims = { nint, m_numElemDims }; + Intrepid::FieldContainer pcoordVec( pcoordDims, const_cast< double * >(p_coords) ); + + m_intrepidBasis->getValues(resultVec, pcoordVec, Intrepid::OPERATOR_GRAD); + + // re-order shape functions for consistency with other master elements + for ( int ip(0); ip < nint; ++ip ) { + for ( int node(0); node < m_numNodes; ++node ) { + for ( int dim(0); dim < m_numElemDims; ++dim ) { + result[(ip*m_numNodes + node)*m_numElemDims + dim] = resultVec(node,ip,dim); + } + } + } +} + +void +MasterElementIntrepid::interpolate_point( + const int npar_coord, + const double * par_coord, // (npar_coord) + const int ncomp_field, + const double * field, // (num_nodes,ncomp_field) + double * result ) const // (ncomp_field) +{ + std::vector shape(m_numNodes); + shape_fcn(1, par_coord, shape.data()); + for ( int comp(0); comp < ncomp_field; ++comp ) { + result[ comp ] = 0.0; + for ( int node(0); node < m_numNodes; ++node ) { + result[ comp ] += shape[node] * field[ m_numNodes * comp + node ]; + } + } +} + +void +MasterElementIntrepid::scalar_gradient( + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const //: (nvec,nelem,nint) +{ + MasterElementCalc::scalar_gradient(m_numIntgPts, nelem, m_numElemDims, m_numNodes, gradop, det_J, sfield, vector); +} + +void +MasterElementIntrepid::scalar_gradient( + const int nint, //: number of intg points + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const //: (nvec,nelem,nint) +{ + MasterElementCalc::scalar_gradient(nint, nelem, m_numElemDims, m_numNodes, gradop, det_J, sfield, vector); +} + +void +MasterElementIntrepid::determinant( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const // (nelem) +{ + MasterElementCalc::determinant(m_numElemDims, m_numCoordDims, nint, npe_g, deriv_g, nelem, coords, det_J, error); +} + +void +MasterElementIntrepid::gradient_operator( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error) const +{ + ThrowRequireMsg(m_numElemDims == m_numCoordDims, "MasterElementHybrid::gradient_operator does not support lower rank elements in higher dimensions (e.g. BAR,QUAD,TRI in 3D)."); + MasterElementCalc::gradient_operator(m_numElemDims, nint, npe_g, deriv_g, npe_f, deriv_f, nelem, coords, gradop, det_J, error); +} + +} // namespace krino diff --git a/packages/krino/krino/master_element/Akri_MasterElementIntrepid.hpp b/packages/krino/krino/master_element/Akri_MasterElementIntrepid.hpp new file mode 100644 index 000000000000..c35933b1d337 --- /dev/null +++ b/packages/krino/krino/master_element/Akri_MasterElementIntrepid.hpp @@ -0,0 +1,135 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MasterElementIntrepid_h +#define Akri_MasterElementIntrepid_h + +#include +#include + +namespace Intrepid { template class FieldContainer; } +namespace Intrepid { template class Basis; } + +namespace krino { + +class MasterElementIntrepid +{ +public: + MasterElementIntrepid( + stk::topology topology, + std::unique_ptr > > basis, + unsigned spatial_dimension); + + // Copy and assignment are not allowed + MasterElementIntrepid( const MasterElementIntrepid & ) = delete; + MasterElementIntrepid & operator=( const MasterElementIntrepid & ) = delete; + + static const MasterElementIntrepid & getMasterElement(stk::topology t, const unsigned spatial_dimension); + + stk::topology get_topology() const { return m_topology; } + unsigned dimension() const { return m_topology.dimension(); } + + // returns the number of integration points + unsigned num_intg_pts() const { return m_numIntgPts; } + + // returns the number of nodes + unsigned num_nodes() const { return m_numNodes; } + + //: Query the integration weights + const double* intg_weights() const { return m_refWeights.data(); } + + //: Query the integration points/stations + const double* intg_pt_locations() const { return m_refPoints.data(); } + + const double * nodal_parametric_coordinates() const { return m_refCoords.data(); } + + void determinant( + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const; // (nelem) + void determinant( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* det_J, // (nelem,nint) + double* error ) const ; // (nelem) + + //: Returns the values of the nodal interpolation shape functions + //: at the integration stations. + const double* shape_fcn() const { return m_shapeFuncs.data(); } + void shape_fcn( + const int nint, // returns array(npe,nint) + const double* p_coords, + double* result) const; + + //: Returns the derivatives of the nodal interpolation shape functions + //: with respect to the local parametric coordinates at the integration + //: stations. + const double* shape_fcn_deriv() const { return m_pointGrads.data(); } + void shape_fcn_deriv( + const int nint, + const double* p_coords, + double* result ) const; + + void interpolate_point( + const int npar_coord, + const double * par_coord, // (npar_coord) + const int ncomp_field, + const double * field, // (num_nodes,ncomp_field) + double * result ) const; // (ncomp_field) + + void gradient_operator( + const int nint, + const int npe_g, + const double* deriv_g, // (nvec,npe_g,nint) + const int npe_f, + const double* deriv_f, // (nvec,npe_f,nint) + const int nelem, + const double* coords, // (nvec,npe,nelem) + double* gradop, // (nvec,npe,nelem,nint) + double* det_J, // (nelem,nint) + double* error) const ; + + void scalar_gradient( + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const ; //: (nvec,nelem,nint) + void scalar_gradient( + const int nint, //: number of intg points + const int nelem, //: number of elements to process + const double* gradop, //: (nvec,npe,nelem,nint) + const double* det_J, //: (nelem,nint) + const double* sfield, //: (npe,nelem) + double* vector ) const ; //: (nvec,nelem,nint) + +private: + stk::topology m_topology; + int m_numNodes; + int m_numElemDims; + int m_numCoordDims; + int m_numIntgPts; + + // the Intrepid Basis + std::unique_ptr>> m_intrepidBasis; + + // Local FieldContainers + std::vector m_shapeFuncs; + std::vector m_pointGrads; + std::vector m_refPoints; + std::vector m_refWeights; + std::vector m_refCoords; +}; + +} // end namespace krino + +#endif // Akri_MasterElementIntrepid_h diff --git a/packages/krino/krino/master_element/CMakeLists.txt b/packages/krino/krino/master_element/CMakeLists.txt new file mode 100644 index 000000000000..da7d6357181c --- /dev/null +++ b/packages/krino/krino/master_element/CMakeLists.txt @@ -0,0 +1,19 @@ +SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + krino_master_element_lib + HEADERS ${HEADERS} + SOURCES ${SOURCES} + ) + +INSTALL(FILES ${HEADERS} DESTINATION + ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/krino_master_element_lib) + diff --git a/packages/krino/krino/parser/Akri_CDFEM_Options_Parser.cpp b/packages/krino/krino/parser/Akri_CDFEM_Options_Parser.cpp new file mode 100644 index 000000000000..f1eb515d80bf --- /dev/null +++ b/packages/krino/krino/parser/Akri_CDFEM_Options_Parser.cpp @@ -0,0 +1,139 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include + +namespace krino { + +void +CDFEM_Options_Parser::parse(const YAML::Node & region_node, RegionInterface & region) +{ + const YAML::Node cdfem_node = YAML_Parser::get_map_if_present(region_node, "cdfem_options"); + if ( cdfem_node ) + { + CDFEM_Support & cdfem_support = CDFEM_Support::get(region.get_stk_mesh_meta_data()); + + std::string cdfem_edge_degeneracy_handling_string; + if (YAML_Parser::get_if_present(cdfem_node, "cdfem_edge_degeneracy_handling", cdfem_edge_degeneracy_handling_string)) + { + std::transform(cdfem_edge_degeneracy_handling_string.begin(), cdfem_edge_degeneracy_handling_string.end(), cdfem_edge_degeneracy_handling_string.begin(), ::toupper); + static std::map valid_entries = + { {"SNAP_TO_NODE", SNAP_TO_NODE}, {"SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS", SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE} }; + auto it = valid_entries.find(cdfem_edge_degeneracy_handling_string); + if (it == valid_entries.end()) + { + stk::RuntimeDoomedAdHoc() << "Invalid cdfem_edge_degeneracy_handling type: " << YAML_Parser::info(cdfem_node); + } + else + { + cdfem_support.set_cdfem_edge_degeneracy_handling( it->second ); + } + } + + double cdfem_edge_tol = 0.0; + if (YAML_Parser::get_if_present(cdfem_node, "cdfem_edge_tolerance", cdfem_edge_tol)) + { + const double minimum_edge_tol = 1.e-12; + if (cdfem_edge_tol < minimum_edge_tol) + { + krinolog << "Using minimum edge tolerance of " << minimum_edge_tol << " instead of specified tolerance of " << cdfem_edge_tol << stk::diag::dendl; + cdfem_edge_tol = minimum_edge_tol; + } + cdfem_support.set_cdfem_edge_tol( cdfem_edge_tol ); + } + + std::string cdfem_simplex_generation_method_string; + if (YAML_Parser::get_if_present(cdfem_node, "cdfem_simplex_generation_method", cdfem_simplex_generation_method_string)) + { + std::transform(cdfem_simplex_generation_method_string.begin(), cdfem_simplex_generation_method_string.end(), cdfem_simplex_generation_method_string.begin(), ::toupper); + static std::map valid_entries = { + {"CUT_QUADS_BY_GLOBAL_IDENTIFIER", CUT_QUADS_BY_GLOBAL_IDENTIFIER}, + {"CUT_QUADS_BY_LARGEST_ANGLE", CUT_QUADS_BY_LARGEST_ANGLE}, + {"CUT_QUADS_BY_NEAREST_EDGE_CUT", CUT_QUADS_BY_NEAREST_EDGE_CUT} + }; + auto it = valid_entries.find(cdfem_simplex_generation_method_string); + if (it == valid_entries.end()) + { + stk::RuntimeDoomedAdHoc() << "Invalid cdfem_simplex_generation_method type: " << YAML_Parser::info(cdfem_node); + } + else + { + cdfem_support.set_simplex_generation_method( it->second ); + } + } + + int num_init_decomp_cycles = 0; + if (YAML_Parser::get_if_present(cdfem_node, "number_of_initial_decomposition_cycles", num_init_decomp_cycles)) + { + cdfem_support.set_num_initial_decomposition_cycles( num_init_decomp_cycles ); + } + + bool interface_refinement_specified = false; + int interface_minimum_refinement_level = 0; + if(YAML_Parser::get_if_present(cdfem_node, "cdfem_interface_minimum_refinement_level", interface_minimum_refinement_level)) + { + interface_refinement_specified = true; + } + + int interface_maximum_refinement_level = 0; + if(YAML_Parser::get_if_present(cdfem_node, "cdfem_interface_maximum_refinement_level", interface_maximum_refinement_level)) + { + interface_refinement_specified = true; + } + + if (interface_refinement_specified) + { + cdfem_support.activate_interface_refinement( interface_minimum_refinement_level, interface_maximum_refinement_level ); + } + + int nonconformal_adapt_levels = 0; + if (YAML_Parser::get_if_present(cdfem_node, "cdfem_nonconformal_adaptivity_levels", nonconformal_adapt_levels)) + { + cdfem_support.activate_nonconformal_adaptivity( nonconformal_adapt_levels ); + } + + int post_adapt_refine_levels = 0; + if (YAML_Parser::get_if_present(cdfem_node, "post_adaptivity_uniform_refinement_levels", post_adapt_refine_levels)) + { + cdfem_support.set_post_adapt_refinement_levels( post_adapt_refine_levels ); + } + + uint64_t nonconformal_adapt_target_element_count = 0; + if (YAML_Parser::get_if_present(cdfem_node, "nonconformal_adaptivity_target_element_count", nonconformal_adapt_target_element_count)) + { + cdfem_support.activate_nonconformal_adapt_target_count( nonconformal_adapt_target_element_count ); + } + + int post_cdfem_refinement_levels = 0; + if (YAML_Parser::get_if_present(cdfem_node, "post_cdfem_refinement_levels", post_cdfem_refinement_levels)) + { + cdfem_support.set_post_cdfem_refinement_levels( post_cdfem_refinement_levels ); + } + + const YAML::Node post_cdfem_refinement_blocks_seq = YAML_Parser::get_sequence_if_present(cdfem_node, "post_cdfem_refinement_blocks"); + if (post_cdfem_refinement_blocks_seq) + { + std::vector post_cdfem_refinement_blocks; + for ( auto && post_cdfem_refinement_block_node : post_cdfem_refinement_blocks_seq ) + { + const std::string post_cdfem_refinement_block = post_cdfem_refinement_block_node.as(); + post_cdfem_refinement_blocks.push_back(post_cdfem_refinement_block); + } + cdfem_support.set_post_cdfem_refinement_blocks( post_cdfem_refinement_blocks ); + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_CDFEM_Options_Parser.hpp b/packages/krino/krino/parser/Akri_CDFEM_Options_Parser.hpp new file mode 100644 index 000000000000..dfdf2a05e559 --- /dev/null +++ b/packages/krino/krino/parser/Akri_CDFEM_Options_Parser.hpp @@ -0,0 +1,21 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_CDFEM_Options_Parser_h +#define Akri_CDFEM_Options_Parser_h + +namespace YAML { class Node; } +namespace krino { class RegionInterface; } + +namespace krino { +namespace CDFEM_Options_Parser { + void parse(const YAML::Node & node, RegionInterface & region); +} +} + +#endif // Akri_CDFEM_Options_Parser_h diff --git a/packages/krino/krino/parser/Akri_IC_Parser.cpp b/packages/krino/krino/parser/Akri_IC_Parser.cpp new file mode 100644 index 000000000000..f8199ab14a45 --- /dev/null +++ b/packages/krino/krino/parser/Akri_IC_Parser.cpp @@ -0,0 +1,457 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace krino { + +namespace { + +Sphere * +parse_sphere(const YAML::Node & ic_node) +{ + std::string name; + YAML_Parser::get_if_present(ic_node, "name", name); + + std::vector center; + if (YAML_Parser::get_if_present(ic_node, "center", center)) + { + if (center.size() != 3) + { + stk::RuntimeDoomedAdHoc() << "Expecting 3 real values for center for IC sphere.\n"; + } + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing center for IC sphere.\n"; + } + + double sign = 1.0; + if (YAML_Parser::get_if_present(ic_node, "sign", sign)) + { + if (sign != -1.0 && sign == 1.0) + { + stk::RuntimeDoomedAdHoc() << "Sign for sphere must be -1 or 1.\n"; + } + } + + double radius = 0.0; + if (!YAML_Parser::get_if_present(ic_node, "radius", radius)) + { + stk::RuntimeDoomedAdHoc() << "Missing radius for IC sphere.\n"; + } + + return new Sphere(name, Vector3d(center.data()), radius, sign); +} + +Ellipsoid * +parse_ellipsoid(const YAML::Node & ic_node) +{ + std::string name; + YAML_Parser::get_if_present(ic_node, "name", name); + + std::vector center; + if (YAML_Parser::get_if_present(ic_node, "center", center)) + { + if (center.size() != 3) + { + stk::RuntimeDoomedAdHoc() << "Expecting 3 real values for center for IC sphere.\n"; + } + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing center for IC sphere.\n"; + } + + std::vector semiaxes; + if (YAML_Parser::get_if_present(ic_node, "semiaxes", semiaxes)) + { + if (semiaxes.size() != 3) + { + stk::RuntimeDoomedAdHoc() << "Expecting 3 real values for semiaxes for IC ellipsoid.\n"; + } + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing semiaxes for IC ellipsoid.\n"; + } + + double sign = 1.0; + if (YAML_Parser::get_if_present(ic_node, "sign", sign)) + { + if (sign != -1.0 && sign == 1.0) + { + stk::RuntimeDoomedAdHoc() << "Sign for ellipsoid must be -1 or 1.\n"; + } + } + + std::vector rotationVec; + if (YAML_Parser::get_if_present(ic_node, "rotation", rotationVec)) + { + if (semiaxes.size() != 3) + { + stk::RuntimeDoomedAdHoc() << "Expecting 3 real values for rotation for IC ellipsoid.\n"; + } + } + + return new Ellipsoid(name, center, semiaxes, rotationVec, sign); +} + +Plane * +parse_plane(const YAML::Node & ic_node) +{ + std::string name; + YAML_Parser::get_if_present(ic_node, "name", name); + + std::vector normal; + if (YAML_Parser::get_if_present(ic_node, "normal", normal)) + { + if (normal.size() != 3) + { + stk::RuntimeDoomedAdHoc() << "Expecting 3 real values for center for IC plane.\n"; + } + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing normal for IC plane.\n"; + } + + double multiplier = 1.0; + YAML_Parser::get_if_present(ic_node, "multiplier", multiplier); + + double sign = 1.0; + if (YAML_Parser::get_if_present(ic_node, "sign", sign)) + { + if (sign != -1.0 && sign == 1.0) + { + stk::RuntimeDoomedAdHoc() << "Sign for IC plane must be -1 or 1.\n"; + } + } + multiplier *= sign; + + double offset = 0.0; + if (!YAML_Parser::get_if_present(ic_node, "offset", offset)) + { + stk::RuntimeDoomedAdHoc() << "Missing offset for IC plane.\n"; + } + + return new Plane(name, normal.data(), offset, multiplier); +} + +Cylinder * +parse_cylinder(const YAML::Node & ic_node) +{ + std::string name; + YAML_Parser::get_if_present(ic_node, "name", name); + + std::vector p1; + if (YAML_Parser::get_if_present(ic_node, "p1", p1)) + { + if (p1.size() != 3) + { + stk::RuntimeDoomedAdHoc() << "Expecting 3 real values for p1 for IC cylinder.\n"; + } + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing normal for IC cylinder.\n"; + } + + std::vector p2; + if (YAML_Parser::get_if_present(ic_node, "p2", p2)) + { + if (p2.size() != 3) + { + stk::RuntimeDoomedAdHoc() << "Expecting 3 real values for center for IC cylinder.\n"; + } + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing p2 for IC cylinder.\n"; + } + + double radius = 0.0; + if (!YAML_Parser::get_if_present(ic_node, "radius", radius)) + { + stk::RuntimeDoomedAdHoc() << "Missing radius for IC cylinder.\n"; + } + + double sign = 1.0; + if (YAML_Parser::get_if_present(ic_node, "sign", sign)) + { + if (sign != -1.0 && sign == 1.0) + { + stk::RuntimeDoomedAdHoc() << "Sign for IC cylinder must be -1 or 1.\n"; + } + } + + return new Cylinder(name, p1.data(), p2.data(), radius, sign); +} + +std::unique_ptr +parse_binder(const YAML::Node & ic_node) +{ + std::string binder_type; + if (!YAML_Parser::get_if_present(ic_node, "type", binder_type)) + { + stk::RuntimeDoomedAdHoc() << "Missing type for Binder IC.\n"; + } + + double interface_size = 0.0; + double smooth_bridge_size = 0.0; + double smooth_bridge_offset = 0.0; + double other_ls_scale_factor = 0.0; + int ibinder_type = 0; + bool root_smooth_bridge = false; + + if (binder_type == "interface") + { + ibinder_type = 0; + if (!YAML_Parser::get_if_present(ic_node, "interface_size", interface_size)) + { + stk::RuntimeDoomedAdHoc() << "Missing interface_size for IC binder.\n"; + } + } + else if (binder_type == "smooth_bridge") + { + ibinder_type = 1; + YAML_Parser::get_if_present(ic_node, "smooth_bridge_size", smooth_bridge_size); + YAML_Parser::get_if_present(ic_node, "smooth_bridge_offset", smooth_bridge_offset); + YAML_Parser::get_if_present(ic_node, "other_ls_scale_factor", other_ls_scale_factor); + YAML_Parser::get_if_present(ic_node, "root_smooth_bridge", root_smooth_bridge); + + if (smooth_bridge_size < 0.0) + { + stk::RuntimeDoomedAdHoc() << "IC binder: Smooth bridge size should not be negative.\n"; + } + + if (smooth_bridge_size != 0.0) + { + if (smooth_bridge_offset <= 0.0) + { + stk::RuntimeDoomedAdHoc() << "IC binder: Smooth bridge offset should be greater than zero when a bridge size is specified.\n"; + } + if (other_ls_scale_factor <= 0.0) + { + stk::RuntimeDoomedAdHoc() << "IC binder: Scaling of other level sets should be greater than zero when a bridge size is specified. Typical values are O(1e2).\n"; + } + } + } + else + { + stk::RuntimeDoomedAdHoc() << "Binder IC type should be either interface or smooth_bridge. \n"; + } + + return std::make_unique(interface_size, smooth_bridge_size, smooth_bridge_offset, other_ls_scale_factor, ibinder_type, root_smooth_bridge); +} + +Faceted_Surface * +parse_facets(const YAML::Node & ic_node, const stk::diag::Timer &parent_timer) +{ + std::string surface_name; + YAML_Parser::get_if_present(ic_node, "name", surface_name); + + std::string facet_filename; + if (!YAML_Parser::get_if_present(ic_node, "filename", facet_filename)) + { + stk::RuntimeDoomedAdHoc() << "Missing filename for IC facets.\n"; + } + + std::string facet_format; + if (!YAML_Parser::get_if_present(ic_node, "format", facet_format)) + { + stk::RuntimeDoomedAdHoc() << "Missing format for IC facets.\n"; + } + + bool scaleSpecified = false; + double scale = 1.0; + if (YAML_Parser::get_if_present(ic_node, "scale", scale)) + { + scaleSpecified = true; + if (scale <= 0.0) + { + stk::RuntimeDoomedAdHoc() << "Scale for IC facets must be >= 0.\n"; + } + } + + Vector3d scaleVec{scale, scale, scale}; + std::vector scaleComponents = {"scaleX","scaleY","scaleZ"}; + for (int i=0; i<3; i++) + { + if (YAML_Parser::get_if_present(ic_node, scaleComponents[i], scaleVec[i])) + { + if (scaleSpecified) + stk::RuntimeDoomedAdHoc() << "Cannot specify both scale and " << scaleComponents[i] << "\n"; + if (scaleVec[i] <= 0.0) + stk::RuntimeDoomedAdHoc() << scaleComponents[i] << " for IC facets must be >= 0.\n"; + } + } + + double sign = 1.0; + if (YAML_Parser::get_if_present(ic_node, "sign", sign)) + { + if (sign != -1.0 && sign == 1.0) + { + stk::RuntimeDoomedAdHoc() << "Sign for IC facets must be -1 or 1.\n"; + } + } + + std::transform(facet_format.begin(), facet_format.end(), facet_format.begin(), ::toupper); + Faceted_Surface_From_File * surface = nullptr; + + if (facet_format == "STL") + surface = new STLSurface(surface_name, parent_timer, facet_filename, sign, scaleVec); + else if (facet_format == "FAC") + surface = new FACSurface(surface_name, parent_timer, facet_filename, sign, scaleVec); + else if (facet_format == "PLY") + surface = new PLYSurface(surface_name, parent_timer, facet_filename, sign, scaleVec); + else if (facet_format == "EXO") + surface = new EXOSurface(surface_name, parent_timer, facet_filename, sign, scaleVec); + else + stk::RuntimeDoomedAdHoc() << "Unrecognized facet format: " << facet_format; + + return surface; +} + +MeshSurface * +parse_mesh_surface(const YAML::Node & ic_node, LevelSet & ls) +{ + std::string surface_name; + ThrowRequire(YAML_Parser::get_if_present(ic_node, "mesh", surface_name)); + + double sign = 1.0; + if (YAML_Parser::get_if_present(ic_node, "sign", sign)) + { + if (sign != -1.0 && sign == 1.0) + { + stk::RuntimeDoomedAdHoc() << "Sign for IC facets must be -1 or 1.\n"; + } + } + + ThrowErrorMsgIf(!ls.aux_meta().has_part(surface_name), "Could not locate a surface named " << surface_name); + const stk::mesh::Part & io_part = ls.aux_meta().get_part(surface_name); + + const stk::mesh::Field* coords = reinterpret_cast*>(&LevelSet::get_current_coordinates(ls.meta()).field()); + ThrowRequire(nullptr != coords); + + const stk::mesh::Selector surface_selector = stk::mesh::Selector(io_part); + return new MeshSurface(ls.meta(), *coords, surface_selector, sign); +} + +void +parse_composition_method(const YAML::Node & ic_node, IC_Alg& ic_alg) +{ + // This is a little strange because composition looks like an IC, but really sets a flag + std::string composition_method; + ThrowRequire(YAML_Parser::get_if_present(ic_node, "composition_method", composition_method)); + + std::transform(composition_method.begin(), composition_method.end(), composition_method.begin(), ::toupper); + + if (composition_method == "MINIMUM_SIGNED_DISTANCE") + ic_alg.set_composition_method(Composite_Surface::MINIMUM_SIGNED_DISTANCE); + else if (composition_method == "MAXIMUM_SIGNED_DISTANCE") + ic_alg.set_composition_method(Composite_Surface::MAXIMUM_SIGNED_DISTANCE); + else + stk::RuntimeDoomedAdHoc() << "Unrecognized composition_method: " << composition_method; +} + +void +parse_IC(const YAML::Node & ic_node, LevelSet &ls) +{ + if (ic_node.Type() == YAML::NodeType::Scalar) + { + // support random specified as Scalar (no :) + std::string ic_type = ic_node.as(); + if (ic_type == "random") + { + Random * random = new Random(0); + ls.get_IC_alg().addSurface(random); + } + else if (ic_type == "analytic_isosurface") + { + Analytic_Isosurface * surf = new Analytic_Isosurface(); + ls.get_IC_alg().addSurface(surf); + } + else + { + stk::RuntimeDoomedAdHoc() << "Unrecognized Levelset IC type: " << YAML_Parser::info(ic_node); + } + return; + } + + if ( YAML_Parser::get_null_if_present(ic_node, "sphere") ) + { + ls.get_IC_alg().addSurface(parse_sphere(ic_node)); + } + else if ( YAML_Parser::get_null_if_present(ic_node, "ellipsoid") ) + { + ls.get_IC_alg().addSurface(parse_ellipsoid(ic_node)); + } + else if ( YAML_Parser::get_null_if_present(ic_node, "plane") ) + { + ls.get_IC_alg().addSurface(parse_plane(ic_node)); + } + else if ( YAML_Parser::get_null_if_present(ic_node, "cylinder") ) + { + ls.get_IC_alg().addSurface(parse_cylinder(ic_node)); + } + else if ( YAML_Parser::get_null_if_present(ic_node, "facets") ) + { + ls.get_IC_alg().addSurface(parse_facets(ic_node, ls.get_timer())); + } + else if ( YAML_Parser::get_scalar_if_present(ic_node, "mesh") ) + { + ls.get_IC_alg().addSurface(parse_mesh_surface(ic_node, ls)); + } + else if ( YAML_Parser::get_null_if_present(ic_node, "random") ) + { + int seed = 0; + YAML_Parser::get_if_present(ic_node, "seed", seed); + Random * random = new Random(seed); + ls.get_IC_alg().addSurface(random); + } + else if ( YAML_Parser::get_null_if_present(ic_node, "binder") ) + { + ls.get_IC_alg().addCalculator(parse_binder(ic_node)); + } + else if ( YAML_Parser::get_scalar_if_present(ic_node, "composition_method") ) + { + parse_composition_method(ic_node, ls.get_IC_alg()); + } + else + { + stk::RuntimeDoomedAdHoc() << "Unrecognized Levelset IC type: " << YAML_Parser::info(ic_node); + } +} + +} + +void +IC_Parser::parse(const YAML::Node & node, LevelSet & ls) +{ + const YAML::Node ic_nodes = YAML_Parser::get_sequence_if_present(node, "initial_conditions"); + if ( ic_nodes ) + { + for ( auto && ic_node : ic_nodes ) + { + parse_IC(ic_node, ls); + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_IC_Parser.hpp b/packages/krino/krino/parser/Akri_IC_Parser.hpp new file mode 100644 index 000000000000..833d49664d92 --- /dev/null +++ b/packages/krino/krino/parser/Akri_IC_Parser.hpp @@ -0,0 +1,21 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_IC_Parser_h +#define Akri_IC_Parser_h + +namespace YAML { class Node; } +namespace krino { class LevelSet; } + +namespace krino { +namespace IC_Parser { + void parse(const YAML::Node & node, LevelSet & ls); +} +} + +#endif // Akri_IC_Parser_h diff --git a/packages/krino/krino/parser/Akri_LevelSet_Parser.cpp b/packages/krino/krino/parser/Akri_LevelSet_Parser.cpp new file mode 100644 index 000000000000..8196db1a77e1 --- /dev/null +++ b/packages/krino/krino/parser/Akri_LevelSet_Parser.cpp @@ -0,0 +1,167 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace krino { + +namespace { + +void +register_blocks_for_level_set(RegionInterface & reg, LevelSet & ls) +{ + const std::string composite_name = ls.get_composite_name(); + if (!composite_name.empty()) + { + LS_SideTag::declare_composite(ls.get_identifier(), LevelSet::get_identifier(composite_name)); + } + + Phase_Support & phase_support = Phase_Support::get(reg.get_stk_mesh_meta_data()); + const PhaseVec & mesh_phases = Phase_Support::get_phases(reg.name_of_input_mesh()); + const std::vector ls_phases = Phase_Support::get_level_set_phases(mesh_phases, ls); + const std::vector decomposed_blocks = phase_support.get_blocks_decomposed_by_levelset(ls_phases); + phase_support.register_blocks_for_level_set(&ls, decomposed_blocks); +} + +} + +void +LevelSet_Parser::parse(const YAML::Node & region_node, RegionInterface & region) +{ + const YAML::Node ls_nodes = YAML_Parser::get_sequence_if_present(region_node, "level_set_interfaces"); + if ( ls_nodes ) + { + for ( auto && ls_node : ls_nodes ) + { + std::string ls_name; + YAML_Parser::get_if_present(ls_node, "name", ls_name); + if (ls_name.empty()) + { + stk::RuntimeDoomedAdHoc() << "Blank or missing levelset name.\n"; + } + LevelSet & ls = LevelSet::build(region.get_stk_mesh_meta_data(), ls_name, region.getRegionTimer()); + + std::string distance_name; + if (krino::YAML_Parser::get_if_present(ls_node, "distance_variable", distance_name)) + { + ls.set_distance_name(distance_name); + } + + std::vector extension_velocity; + if (krino::YAML_Parser::get_if_present(ls_node, "extension_velocity", extension_velocity)) + { + if (extension_velocity.size() != ls.spatial_dimension) + { + stk::RuntimeDoomedAdHoc() << "Expecting " << ls.spatial_dimension << " real values for extension_velocity for level set " << ls_name << ".\n"; + } + ls.set_extension_velocity(Vector3d(extension_velocity.data(), extension_velocity.size())); + } + + double narrow_band_multiplier = 0.0; + if (YAML_Parser::get_if_present(ls_node, "narrow_band_element_size_multiplier", narrow_band_multiplier)) + { + if( narrow_band_multiplier < 0. ) + { + stk::RuntimeDoomedAdHoc() << "Error: Narrow band element size multiplier must be >= 0.\n"; + } + else if ( narrow_band_multiplier < 1. ) + { + stk::RuntimeWarningAdHoc() << "Narrow band element size multiplier is less than 1. " + << "Except in certain cases of adaptive refinement around the interface, this will produce errors in the distance field." + << std::endl; + } + ls.narrow_band_multiplier(narrow_band_multiplier); + } + + std::string redistance_method_name; + if (YAML_Parser::get_if_present(ls_node, "redistance_method", redistance_method_name)) + { + std::transform(redistance_method_name.begin(), redistance_method_name.end(), redistance_method_name.begin(), ::toupper); + Redistance_Method redistance_method = CLOSEST_POINT; + if (redistance_method_name == "CLOSEST_POINT") + redistance_method = CLOSEST_POINT; + else if (redistance_method_name == "FAST_MARCHING") + redistance_method = FAST_MARCHING; + else + stk::RuntimeWarningAdHoc() << "Unrecognized redistance method: " << redistance_method_name << std::endl; + + ls.set_redistance_method(redistance_method); + } + + bool perform_initial_redistance; + if (YAML_Parser::get_if_present(ls_node, "perform_initial_redistance", perform_initial_redistance)) + { + ls.perform_initial_redistance(perform_initial_redistance); + } + + double initial_offset_distance = 0.0; + if (YAML_Parser::get_if_present(ls_node, "initial_offset_distance", initial_offset_distance)) + { + ls.set_ic_offset(initial_offset_distance); + } + + double initial_scale_factor = 1.0; + if (YAML_Parser::get_if_present(ls_node, "initial_scale_factor", initial_scale_factor)) + { + ls.set_ic_scale(initial_scale_factor); + } + + double narrow_band_size = 0.0; + if (YAML_Parser::get_if_present(ls_node, "narrow_band_size", narrow_band_size)) + { + if( narrow_band_size < 0. ) + { + stk::RuntimeDoomedAdHoc() << "Error: Narrow band size must be >= 0.\n"; + } + ls.narrow_band_size(narrow_band_size); + } + + const YAML::Node comp_dist_surfs = YAML_Parser::get_sequence_if_present(ls_node, "compute_surface_distance"); + if ( comp_dist_surfs ) + { + std::vector compute_distance_surfaces; + for ( auto && comp_dist_surf : comp_dist_surfs ) + { + const std::string surface_name = comp_dist_surf.as(); + + ThrowErrorMsgIf( !ls.aux_meta().has_part(surface_name), + "Could not locate a surface named " << surface_name); + + stk::mesh::Part & io_part = ls.aux_meta().get_part(surface_name); + ThrowErrorMsgIf( ls.meta().side_rank() != io_part.primary_entity_rank(), + "Part " << surface_name << " is not a side-rank part."); + + ls.get_compute_surface_distance_parts().push_back(&io_part); + compute_distance_surfaces.push_back(surface_name); + } + ThrowErrorMsgIf( ls.get_compute_surface_distance_parts().empty(), + "Please specify surfaces for compute surface distance."); + } + + IC_Parser::parse(ls_node, ls); + + register_blocks_for_level_set(region, ls); + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_LevelSet_Parser.hpp b/packages/krino/krino/parser/Akri_LevelSet_Parser.hpp new file mode 100644 index 000000000000..8016393f28f7 --- /dev/null +++ b/packages/krino/krino/parser/Akri_LevelSet_Parser.hpp @@ -0,0 +1,21 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_LevelSet_Parser_h +#define Akri_LevelSet_Parser_h + +namespace YAML { class Node; } +namespace krino { class RegionInterface; } + +namespace krino { +namespace LevelSet_Parser { + void parse(const YAML::Node & node, RegionInterface & region); +} +} + +#endif // Akri_LevelSet_Parser_h diff --git a/packages/krino/krino/parser/Akri_MeshInput_Parser.cpp b/packages/krino/krino/parser/Akri_MeshInput_Parser.cpp new file mode 100644 index 000000000000..d455924ffa11 --- /dev/null +++ b/packages/krino/krino/parser/Akri_MeshInput_Parser.cpp @@ -0,0 +1,181 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +#include +#include +#include +#include + +#include + +namespace krino { + +void +MeshInput_Parser::parse(const YAML::Node & base_node) +{ + const YAML::Node fem_nodes = YAML_Parser::get_sequence_if_present(base_node, "finite_element_models"); + if (!fem_nodes) return; + + for ( auto && fem_node : fem_nodes ) + { + std::string model_name; + YAML_Parser::get_if_present(fem_node, "name", model_name); + if (model_name.empty()) + { + stk::RuntimeDoomedAdHoc() << "Missing finite element model name.\n"; + } + std::shared_ptr options = MeshInputOptions::get_or_create(model_name); + + const bool has_generated_mesh = parse_generated_mesh(fem_node, *options); + + std::string mesh_name; + YAML_Parser::get_if_present(fem_node, "mesh", mesh_name); + if (!mesh_name.empty() && !has_generated_mesh) + { + options->set_filename(mesh_name); + } + else if (!has_generated_mesh) + { + stk::RuntimeDoomedAdHoc() << "Must specify input mesh name or generated mesh options.\n"; + } + + std::string decomposition_method; + if (YAML_Parser::get_if_present(fem_node, "decomposition_method", decomposition_method)) + { + // no checking here for validity + std::transform(decomposition_method.begin(), decomposition_method.end(), decomposition_method.begin(), ::toupper); + options->set_decomposition_method(decomposition_method); + } + + Phase_Parser::parse(fem_node, model_name); + } +} + +bool +MeshInput_Parser::parse_generated_mesh(const YAML::Node & fem_node, MeshInputOptions & options) +{ + const YAML::Node generated_mesh_node = YAML_Parser::get_map_if_present(fem_node, "generated_mesh"); + if (!generated_mesh_node) return false; + + double mesh_size = 0.0; + if (!YAML_Parser::get_if_present(generated_mesh_node, "mesh_size", mesh_size) || mesh_size < 0.0) + { + stk::RuntimeDoomedAdHoc() << "Missing or invalid mesh_size for generated_mesh.\n"; + } + options.set_generated_mesh_size(mesh_size); + + std::vector domain; + if (YAML_Parser::get_if_present(generated_mesh_node, "domain", domain)) + { + if (options.get_generated_mesh_domain_type() != MeshInputOptions::NO_GENERATED_MESH) + { + stk::RuntimeDoomedAdHoc() << "Must only specify domain or interface_bounding_box_with_dimension options.\n"; + } + options.set_generated_mesh_domain_type(MeshInputOptions::GENERATED_MESH_FOR_SPECIFIED_DOMAIN); + if (domain.size() != 4 && domain.size() != 6) + { + stk::RuntimeDoomedAdHoc() << "Domain must be a vector of length 4 for 2D or 6 for 3D (xmin,ymin,zmin, xmax,ymax,zmax).\n"; + } + options.set_generated_mesh_domain(domain); + } + + int interface_spatial_dim = 0; + if (YAML_Parser::get_if_present(generated_mesh_node, "interface_bounding_box_with_dimension", interface_spatial_dim)) + { + if (options.get_generated_mesh_domain_type() != MeshInputOptions::NO_GENERATED_MESH) + { + stk::RuntimeDoomedAdHoc() << "Must only specify domain or interface_bounding_box options.\n"; + } + if (interface_spatial_dim != 2 && interface_spatial_dim != 3) + { + stk::RuntimeDoomedAdHoc() << "interface_bounding_box_with_dimension only support 2 or 3 dimensions.\n"; + } + const MeshInputOptions::GeneratedMeshDomainType mesh_type = + interface_spatial_dim == 2 ? + MeshInputOptions::GENERATED_2D_MESH_FOR_INTERFACE_BOUNDING_BOX : + MeshInputOptions::GENERATED_3D_MESH_FOR_INTERFACE_BOUNDING_BOX; + options.set_generated_mesh_domain_type(mesh_type); + } + + std::string generated_mesh_element_type_string; + if (YAML_Parser::get_if_present(generated_mesh_node, "element_type", generated_mesh_element_type_string)) + { + std::transform(generated_mesh_element_type_string.begin(), generated_mesh_element_type_string.end(), generated_mesh_element_type_string.begin(), ::toupper); + static std::map valid_entries = + { {"TRIANGLE", stk::topology::TRIANGLE_3_2D}, + {"TRI", stk::topology::TRIANGLE_3_2D}, + {"QUADRILATERAL", stk::topology::QUADRILATERAL_4_2D}, + {"QUAD", stk::topology::QUADRILATERAL_4_2D}, + {"HEXAHEDRON", stk::topology::HEXAHEDRON_8}, + {"HEX", stk::topology::HEXAHEDRON_8}, + {"TETRAHEDRON", stk::topology::TETRAHEDRON_4}, + {"TET", stk::topology::TETRAHEDRON_4} }; + auto it = valid_entries.find(generated_mesh_element_type_string); + if (it == valid_entries.end()) + { + stk::RuntimeDoomedAdHoc() << "Invalid cdfem_simplex_generation_method type: " << YAML_Parser::info(generated_mesh_node); + } + else + { + options.set_generated_mesh_element_type( it->second ); + if (options.get_generated_mesh_spatial_dimension() != (int)options.get_generated_mesh_element_type().dimension()) + { + stk::RuntimeDoomedAdHoc() << "Mismatch in spatial dimension for generated mesh element type and domain specification. "; + } + } + } + else + { + stk::topology default_topology = + (options.get_generated_mesh_spatial_dimension() == 2) ? + stk::topology::TRIANGLE_3_2D : + stk::topology::TETRAHEDRON_4; + options.set_generated_mesh_element_type( default_topology ); + } + + std::string generated_mesh_type_string; + if (YAML_Parser::get_if_present(generated_mesh_node, "mesh_type", generated_mesh_type_string)) + { + std::transform(generated_mesh_type_string.begin(), generated_mesh_type_string.end(), generated_mesh_type_string.begin(), ::toupper); + static std::map valid_entries = + { {"CUBIC", BoundingBoxMeshStructureType::CUBIC_BOUNDING_BOX_MESH}, + {"BCC", BoundingBoxMeshStructureType::BCC_BOUNDING_BOX_MESH}, + {"FLAT_WALLED_BCC", BoundingBoxMeshStructureType::FLAT_WALLED_BCC_BOUNDING_BOX_MESH}, + {"TRIANGULAR_LATTICE", BoundingBoxMeshStructureType::TRIANGULAR_LATTICE_BOUNDING_BOX_MESH}, + {"FLAT_WALLED_TRIANGULAR_LATTICE", BoundingBoxMeshStructureType::FLAT_WALLED_TRIANGULAR_LATTICE_BOUNDING_BOX_MESH}}; + auto it = valid_entries.find(generated_mesh_type_string); + if (it == valid_entries.end()) + { + stk::RuntimeDoomedAdHoc() << "Invalid mesh_type: " << YAML_Parser::info(generated_mesh_node); + } + else + { + options.set_generated_mesh_structure_type(it->second); + if ((BoundingBoxMeshStructureType::BCC_BOUNDING_BOX_MESH == it->second || BoundingBoxMeshStructureType::FLAT_WALLED_BCC_BOUNDING_BOX_MESH == it->second) && options.get_generated_mesh_spatial_dimension() != 3) + { + stk::RuntimeDoomedAdHoc() << "BCC meshes only supported in 3D"; + } + if ((BoundingBoxMeshStructureType::TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == it->second || BoundingBoxMeshStructureType::FLAT_WALLED_TRIANGULAR_LATTICE_BOUNDING_BOX_MESH == it->second) && options.get_generated_mesh_spatial_dimension() != 2) + { + stk::RuntimeDoomedAdHoc() << "BCC meshes only supported in 2D"; + } + } + } + + if (options.get_generated_mesh_domain_type() == MeshInputOptions::NO_GENERATED_MESH) + { + stk::RuntimeDoomedAdHoc() << "Must specify domain or interface_bounding_box_with_dimension for generated_mesh.\n"; + } + return true; +} + + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_MeshInput_Parser.hpp b/packages/krino/krino/parser/Akri_MeshInput_Parser.hpp new file mode 100644 index 000000000000..1dcd2342aa5d --- /dev/null +++ b/packages/krino/krino/parser/Akri_MeshInput_Parser.hpp @@ -0,0 +1,22 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_MeshInput_Parser_h +#define Akri_MeshInput_Parser_h + +namespace YAML { class Node; } +namespace krino { class MeshInputOptions; } + +namespace krino { +namespace MeshInput_Parser { + void parse(const YAML::Node & node); + bool parse_generated_mesh(const YAML::Node & fem_node, MeshInputOptions & options); +} +} + +#endif // Akri_MeshInput_Parser_h diff --git a/packages/krino/krino/parser/Akri_Phase_Parser.cpp b/packages/krino/krino/parser/Akri_Phase_Parser.cpp new file mode 100644 index 000000000000..64f74ac76a99 --- /dev/null +++ b/packages/krino/krino/parser/Akri_Phase_Parser.cpp @@ -0,0 +1,101 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include + +namespace krino { + +void +Phase_Parser::parse(const YAML::Node & fem_node, const std::string & fem_model_name) +{ + PhaseVec & mesh_phases = Phase_Support::get_phases(fem_model_name); + + const YAML::Node subdom_nodes = YAML_Parser::get_sequence_if_present(fem_node, "subdomains"); + + if (subdom_nodes) + { + for ( auto && subdom_node : subdom_nodes ) + { + std::string subdom_name; + YAML_Parser::get_if_present(subdom_node, "name", subdom_name); + if (subdom_name.empty()) + { + stk::RuntimeDoomedAdHoc() << "Missing subdomain name in finite element model " << fem_model_name; + } + mesh_phases.push_back(NamedPhase(subdom_name)); + + const YAML::Node where_nodes = YAML_Parser::get_sequence_if_present(subdom_node, "level_set_regions"); + + if (where_nodes) + { + for ( auto && where_node : where_nodes ) + { + std::string negative_ls_name; + if (YAML_Parser::get_if_present(where_node, "negative_level_set", negative_ls_name)) + { + mesh_phases.back().tag().add(LevelSet::get_identifier(negative_ls_name),-1); + } + + std::string positive_ls_name; + if (YAML_Parser::get_if_present(where_node, "positive_level_set", positive_ls_name)) + { + mesh_phases.back().tag().add(LevelSet::get_identifier(positive_ls_name),+1); + } + } + } + + std::string smallest_ls_name; + if (YAML_Parser::get_if_present(subdom_node, "smallest_level_set", smallest_ls_name)) + { + if ( where_nodes ) + { + stk::RuntimeDoomedAdHoc() << "Cannot combine \"negative_level_set|positive_level_set\" syntax with \"smallest_level_set\" syntax."; + } + Phase_Support::set_one_levelset_per_phase(true); + mesh_phases.back().tag().add(LevelSet::get_identifier(smallest_ls_name),-1); + } + } + } + + const YAML::Node part_nodes = YAML_Parser::get_sequence_if_present(fem_node, "parts"); + + if (part_nodes) + { + std::map> & FEmodel_block_phase_names = Phase_Support::get_block_phases_by_name(fem_model_name); + + for ( auto && part_node : part_nodes ) + { + std::string part_name; + YAML_Parser::get_if_present(part_node, "name", part_name); + if (part_name.empty()) + { + stk::RuntimeDoomedAdHoc() << "Missing part name in finite element model " << fem_model_name; + } + + const YAML::Node part_subdom_nodes = YAML_Parser::get_sequence_if_present(part_node, "subdomains"); + if (part_subdom_nodes) + { + std::vector & block_phase_names = FEmodel_block_phase_names[part_name]; + for ( auto && subdom_node : part_subdom_nodes ) + { + const std::string subdom_name = subdom_node.as(); + block_phase_names.push_back(subdom_name); + } + } + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_Phase_Parser.hpp b/packages/krino/krino/parser/Akri_Phase_Parser.hpp new file mode 100644 index 000000000000..53c0f08b7c0f --- /dev/null +++ b/packages/krino/krino/parser/Akri_Phase_Parser.hpp @@ -0,0 +1,22 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Phase_Parser_h +#define Akri_Phase_Parser_h + +#include + +namespace YAML { class Node; } + +namespace krino { +namespace Phase_Parser { + void parse(const YAML::Node & fem_node, const std::string & fem_model_name); +} +} + +#endif // Akri_Phase_Parser_h diff --git a/packages/krino/krino/parser/Akri_Region_Parser.cpp b/packages/krino/krino/parser/Akri_Region_Parser.cpp new file mode 100644 index 000000000000..56310b64bade --- /dev/null +++ b/packages/krino/krino/parser/Akri_Region_Parser.cpp @@ -0,0 +1,84 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace krino { + +void +Region_Parser::parse(const YAML::Node & simulation_node, Simulation & simulation) +{ + const YAML::Node region_nodes = YAML_Parser::get_sequence_if_present(simulation_node, "regions"); + if ( !region_nodes ) return; + + for ( auto && region_node : region_nodes ) + { + std::string region_name; + YAML_Parser::get_if_present(region_node, "name", region_name); + if (region_name.empty()) + { + stk::RuntimeDoomedAdHoc() << "Blank or missing region name.\n"; + } + Region * region = new Region(simulation, region_name ); + + int initial_refinement_levels = 0; + if (YAML_Parser::get_if_present(region_node, "initial_uniform_refinement_levels", initial_refinement_levels)) + { + region->set_initial_refinement_levels(initial_refinement_levels); + } + + bool use_32bit_ids = false; + YAML_Parser::get_if_present(region_node, "use_32bit_ids", use_32bit_ids); + + bool force_64bit_ids = !use_32bit_ids; + YAML_Parser::get_if_present(region_node, "force_64bit_ids", force_64bit_ids); + + if (use_32bit_ids && force_64bit_ids) + { + stk::RuntimeDoomedAdHoc() << "Can't specify options use_32bit_ids=true and force_64bit_ids=true together in region " << region_name << "\n"; + } + + std::string fem_model_name; + YAML_Parser::get_if_present(region_node, "finite_element_model", fem_model_name); + if (fem_model_name.empty()) + { + stk::RuntimeDoomedAdHoc() << "Blank or missing finite element model for region " << region_name << "\n"; + } + region->associate_input_mesh(fem_model_name, use_32bit_ids, force_64bit_ids); + + RegionInterface::set_currently_parsed_region(region->get_stk_mesh_meta_data(), region->name(), region->getRegionTimer(), region->name_of_input_mesh(), region->get_input_io_region()); + + Phase_Support & phase_support = krino::Phase_Support::get(region->get_stk_mesh_meta_data()); + const PhaseVec & mesh_phases = Phase_Support::get_phases(region->name_of_input_mesh()); + const PartnamePhasenameMap & mesh_block_phase_names = Phase_Support::get_block_phases_by_name(region->name_of_input_mesh()); + phase_support.determine_block_phases(mesh_phases, mesh_block_phase_names); + + + const Block_Surface_Connectivity block_surf_info(region->get_stk_mesh_meta_data()); + phase_support.set_input_block_surface_connectivity(block_surf_info); + phase_support.setup_phases(mesh_phases); + + LevelSet_Parser::parse(region_node, RegionInterface::get_currently_parsed_region()); + CDFEM_Options_Parser::parse(region_node, RegionInterface::get_currently_parsed_region()); + ResultsOutput_Parser::parse(region_node, *region); + } +} + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_Region_Parser.hpp b/packages/krino/krino/parser/Akri_Region_Parser.hpp new file mode 100644 index 000000000000..a860592c7b53 --- /dev/null +++ b/packages/krino/krino/parser/Akri_Region_Parser.hpp @@ -0,0 +1,21 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Region_Parser_h +#define Akri_Region_Parser_h + +namespace YAML { class Node; } +namespace krino { class Simulation; } + +namespace krino { +namespace Region_Parser { + void parse(const YAML::Node & node, Simulation & simulation); +} +} + +#endif // Akri_Region_Parser_h diff --git a/packages/krino/krino/parser/Akri_ResultsOutput_Parser.cpp b/packages/krino/krino/parser/Akri_ResultsOutput_Parser.cpp new file mode 100644 index 000000000000..a94751ac5e60 --- /dev/null +++ b/packages/krino/krino/parser/Akri_ResultsOutput_Parser.cpp @@ -0,0 +1,72 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include + +namespace krino { + +void +ResultsOutput_Parser::parse(const YAML::Node & region_node, Region & region) +{ + const YAML::Node results_node = YAML_Parser::get_map_if_present(region_node, "output"); + if ( results_node ) + { + ResultsOutputOptions * options = region.get_results_options(); + + std::string results_database_name; + if (YAML_Parser::get_if_present(results_node, "database_name", results_database_name)) + { + options->set_filename(results_database_name); + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing results database_name for region " << region.name() << ".\n"; + } + + std::string results_title; + if (YAML_Parser::get_if_present(results_node, "title", results_title)) + { + options->set_title(results_title); + } + + bool compose_results; + if (YAML_Parser::get_if_present(results_node, "compose_results", compose_results)) + { + if (compose_results) options->add_property(Ioss::Property("COMPOSE_RESULTS", 1)); + } + + int output_frequency = 1; + if (YAML_Parser::get_if_present(results_node, "output_frequency", output_frequency)) + { + options->add_step_increment(0, output_frequency); + } + + const YAML::Node nodal_variable_nodes = YAML_Parser::get_sequence_if_present(results_node, "nodal_variables"); + for ( auto && variable_node : nodal_variable_nodes ) + { + const std::string field_name = variable_node.as(); + options->add_nodal_field(field_name, field_name); + } + + const YAML::Node element_variable_nodes = YAML_Parser::get_sequence_if_present(results_node, "element_variables"); + for ( auto && variable_node : element_variable_nodes ) + { + const std::string field_name = variable_node.as(); + options->add_element_field(field_name, field_name); + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_ResultsOutput_Parser.hpp b/packages/krino/krino/parser/Akri_ResultsOutput_Parser.hpp new file mode 100644 index 000000000000..352a0947d673 --- /dev/null +++ b/packages/krino/krino/parser/Akri_ResultsOutput_Parser.hpp @@ -0,0 +1,21 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_ResultsOutput_Parser_h +#define Akri_ResultsOutput_Parser_h + +namespace YAML { class Node; } +namespace krino { class Region; } + +namespace krino { +namespace ResultsOutput_Parser { + void parse(const YAML::Node & node, Region & region); +} +} + +#endif // Akri_ResultsOutput_Parser_h diff --git a/packages/krino/krino/parser/Akri_Simulation_Parser.cpp b/packages/krino/krino/parser/Akri_Simulation_Parser.cpp new file mode 100644 index 000000000000..be02d91cbf15 --- /dev/null +++ b/packages/krino/krino/parser/Akri_Simulation_Parser.cpp @@ -0,0 +1,54 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include + +namespace krino { + +void +Simulation_Parser::parse(const YAML::Node & node) +{ + const YAML::Node sim_node = YAML_Parser::get_map_if_present(node, "simulation"); + if ( sim_node ) + { + Simulation & simulation = Simulation::build("krino simulation"); + + double start_time = 0.0; + if (YAML_Parser::get_if_present(sim_node, "start_time", start_time)) + { + simulation.set_current_time(start_time); + } + + double stop_time = 0.0; + if (YAML_Parser::get_if_present(sim_node, "stop_time", stop_time)) + { + simulation.set_stop_time(stop_time); + } + + double time_step = 0.0; + if (YAML_Parser::get_if_present(sim_node, "time_step", time_step)) + { + simulation.set_time_step(time_step); + } + + Region_Parser::parse(sim_node, simulation); + } + else + { + stk::RuntimeDoomedAdHoc() << "Missing simulation.\n"; + } +} + +} // namespace krino diff --git a/packages/krino/krino/parser/Akri_Simulation_Parser.hpp b/packages/krino/krino/parser/Akri_Simulation_Parser.hpp new file mode 100644 index 000000000000..45c02b36ca47 --- /dev/null +++ b/packages/krino/krino/parser/Akri_Simulation_Parser.hpp @@ -0,0 +1,20 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Simulation_Parser_h +#define Akri_Simulation_Parser_h + +namespace YAML { class Node; } + +namespace krino { +namespace Simulation_Parser { + void parse(const YAML::Node & node); +} +} + +#endif // Akri_Simulation_Parser_h diff --git a/packages/krino/krino/parser/Akri_YAML.hpp b/packages/krino/krino/parser/Akri_YAML.hpp new file mode 100644 index 000000000000..1a5de9994874 --- /dev/null +++ b/packages/krino/krino/parser/Akri_YAML.hpp @@ -0,0 +1,48 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_KRINO_PARSER_AKRI_YAML_HPP_ +#define KRINO_KRINO_PARSER_AKRI_YAML_HPP_ +#include + +#ifdef KRINO_HAVE_YAML + +#ifdef __INTEL_COMPILER +#include +#else +//YAML has shadowed variables +//this disables the checking on GCC only +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop +#endif + +#else +// Fake struct to mimic YAML Node if we don't have YAML +namespace YAML { + struct NodeType { + enum value { Undefined, Null, Scalar, Sequence, Map }; + }; + + struct Node { + NodeType::value Type() const { return NodeType::Null; } + + template const T as() const { return T(); } + + explicit operator bool() const { return false; } + + Node * begin() const { return nullptr; } + Node * end() const { return nullptr; } + + Node operator[](std::string) const { return Node(); } + }; +} +#endif + +#endif /* KRINO_KRINO_PARSER_AKRI_YAML_HPP_ */ diff --git a/packages/krino/krino/parser/Akri_YAML_Parser.cpp b/packages/krino/krino/parser/Akri_YAML_Parser.cpp new file mode 100644 index 000000000000..9f1e4df98276 --- /dev/null +++ b/packages/krino/krino/parser/Akri_YAML_Parser.cpp @@ -0,0 +1,235 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef KRINO_HAVE_YAML + +static void emit(YAML::Emitter& emout, const YAML::Node & node) { + // recursive depth first + YAML::NodeType::value type = node.Type(); + std::string out; + switch (type) + { + case YAML::NodeType::Scalar: + out = node.as(); + emout << out; + break; + case YAML::NodeType::Sequence: + emout << YAML::BeginSeq; + for (unsigned int i = 0; i < node.size(); ++i) { + const YAML::Node & subnode = node[i]; + emit(emout, subnode); + } + emout << YAML::EndSeq; + break; + case YAML::NodeType::Map: + emout << YAML::BeginMap ; + for (auto && entry : node) { + const YAML::Node & key = entry.first; + const YAML::Node & value = entry.second; + out = key.as(); + emout << YAML::Key << out; + emout << YAML::Value; + emit(emout, value); + } + emout << YAML::EndMap ; + break; + case YAML::NodeType::Null: + emout << " (empty) "; + break; + default: + std::cerr << "Warning: emit: unknown/unsupported node type" << std::endl; + break; + } +} + +/// uses Emitter to print node to stream +static void emit(std::ostream& sout, const YAML::Node & node) { + YAML::Emitter out; + emit(out,node); + sout << out.c_str() << std::endl; +} + +static YAML::Node parse_yaml(std::istream & input_stream) +{ + return YAML::Load(input_stream); + YAML::Node doc; + + try { + doc = YAML::Load(input_stream); + } + catch (YAML::ParserException &e) { + throw std::runtime_error(e.what()); + } + + return doc; +} + +static std::string line_info(const YAML::Node & node) { + std::ostringstream sout; + sout << "(line,column) = (" + << node.Mark().line+1 << ", " + << node.Mark().column+1 << ")"; + return sout.str(); +} + +#else + +static YAML::Node parse_yaml(std::istream & input_stream) { throw std::runtime_error("YAML parser not enabled in krino build."); } +static void emit(std::ostream& sout, const YAML::Node & node) {} +static std::string line_info(const YAML::Node & node) { return std::string(); } + +#endif + +namespace krino{ +namespace YAML_Parser { + +std::string info(const YAML::Node & node) { + std::ostringstream sout; + sout << "Node at " << line_info(node) << " => \n" ; + emit(sout, node); + return sout.str(); +} + +const YAML::Node +get_if_present(const YAML::Node& node, const std::string& key) +{ + static std::string types[] = {"Null", "Scalar", "Sequence", "Map"}; + std::ostringstream err_msg; + + if (node.Type() == YAML::NodeType::Null || node.Type() == YAML::NodeType::Scalar) + { + emit(err_msg, node); + err_msg << "Check structure of input file."; + stk::RuntimeDoomedAdHoc() + << "parsing within non-searchable node of type = " << types[node.Type()] + << " for key= " << key + << " at " << line_info(node) + << " node= " << err_msg.str() << std::endl; + YAML::Node empty; + return empty; + } + return node[key]; +} + +const YAML::Node +get_type_if_present(const YAML::Node& node, const std::string& key, YAML::NodeType::value type) +{ + static std::string types[] = {"Null", "Scalar", "Sequence", "Map"}; + std::ostringstream err_msg; + + const YAML::Node value = get_if_present(node, key); + if (value && (value.Type() != type)) + { + emit(err_msg, node); + err_msg << "Check indentation of input file."; + stk::RuntimeDoomedAdHoc() + << "parsing expected type " << types[type] << " got type = " << types[value.Type()] + << " for key= " << key + << " at " << line_info(node) + << " node= " << err_msg.str() << std::endl; + } + return value; +} + +// template specialization +template<> +bool get_if_present(const YAML::Node & node, const std::string& key, bool& result) +{ + std::string resultString; + const bool haveNode = get_if_present(node, key, resultString); + + if (haveNode) + { + result = false; + std::transform(resultString.begin(), resultString.end(), resultString.begin(), ::toupper); + if (resultString == "TRUE" || resultString == "YES" || resultString == "1") + result = true; + else if (resultString == "FALSE" || resultString == "NO" || resultString == "0") + result = false; + else + { + std::ostringstream err_msg; + emit(err_msg, node); + stk::RuntimeDoomedAdHoc() << "Failed to parse boolean " + << " for key= " << key + << " at " << line_info(node) + << " node= " << err_msg.str() << std::endl; + } + return true; + } + return false; +} + + +void parse() +{/* %TRACE% */ Trace trace__("krino::YAML_Parser::parse()"); /* %TRACE% */ + const std::string inputFileName = stk::EnvData::getInputFileName(); + const std::string &aprepro = sierra::Env::get_param("aprepro"); + const bool use_aprepro = aprepro.empty() || aprepro != "off"; + const std::string &aprepro_defines = sierra::Env::get_param("define"); + + // parallel version of input file + mpi_filebuf input_parallel_file(use_aprepro, aprepro_defines); + if (!input_parallel_file.open(stk::EnvData::parallel_comm(), 0, std::ios::in, inputFileName.c_str())) { + throw std::runtime_error("failed to open input file"); + } + if(use_aprepro) { + sierra::Env::outputP0()< +#include +#include + +namespace krino{ +namespace YAML_Parser { + +void parse(); +std::string info(const YAML::Node & node); + +const YAML::Node +get_if_present(const YAML::Node& node, const std::string& key); + +/// these can be used to check and ensure a type of yaml node is as expected +const YAML::Node +get_type_if_present(const YAML::Node& node, const std::string& key, YAML::NodeType::value type); + +inline const YAML::Node +get_null_if_present(const YAML::Node& node, const std::string& key) {return get_type_if_present(node, key, YAML::NodeType::Null); } + +inline const YAML::Node +get_scalar_if_present(const YAML::Node& node, const std::string& key) {return get_type_if_present(node, key, YAML::NodeType::Scalar); } + +inline const YAML::Node +get_sequence_if_present(const YAML::Node& node, const std::string& key) {return get_type_if_present(node, key, YAML::NodeType::Sequence); } + +inline const YAML::Node +get_map_if_present(const YAML::Node& node, const std::string& key) {return get_type_if_present(node, key, YAML::NodeType::Map); } + +/// Doesn't change @param result unless the @param key is present in the @param node +template +bool get_if_present(const YAML::Node & node, const std::string& key, T& result) +{ + const YAML::Node value = get_if_present(node, key); + if (value) + { + result = value.as(); + return true; + } + return false; +} + +template<> +bool get_if_present(const YAML::Node & node, const std::string& key, bool& result); + +} // namespace parser +} // namespace krino + +#endif // Akri_YAML_Parser_h diff --git a/packages/krino/krino/parser/CMakeLists.txt b/packages/krino/krino/parser/CMakeLists.txt new file mode 100644 index 000000000000..66cbc42a19c1 --- /dev/null +++ b/packages/krino/krino/parser/CMakeLists.txt @@ -0,0 +1,19 @@ +SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + krino_parser_lib + HEADERS ${HEADERS} + SOURCES ${SOURCES} + DEPLIBS krino_lib krino_region_lib) + +INSTALL(FILES ${HEADERS} DESTINATION + ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/krino_parser_lib) + diff --git a/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.cpp b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.cpp new file mode 100644 index 000000000000..432c925d4a24 --- /dev/null +++ b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.cpp @@ -0,0 +1,230 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include + +namespace krino { +namespace rebalance_utils { + +namespace { + +class MultipleCriteriaSettings : public stk::balance::GraphCreationSettings +{ +public: + MultipleCriteriaSettings(stk::mesh::BulkData & stkMeshBulkData, + const std::vector *> critFields, + const double default_weight = 0.0) + : m_stkMeshBulkData(stkMeshBulkData), + m_critFields(critFields), + m_defaultWeight(default_weight) + { + method = "rcb"; + setUseNodeBalancer(true); + setNodeBalancerTargetLoadBalance(getImbalanceTolerance()); + setNodeBalancerMaxIterations(10); + } + virtual ~MultipleCriteriaSettings() = default; + + virtual double + getGraphEdgeWeight(stk::topology element1Topology, stk::topology element2Topology) const override + { + return 1.0; + } + virtual bool areVertexWeightsProvidedViaFields() const override { return true; } + virtual bool includeSearchResultsInGraph() const override { return false; } + virtual int getGraphVertexWeight(stk::topology type) const override { return 1; } + virtual double getImbalanceTolerance() const override { return 1.05; } + virtual void setDecompMethod(const std::string & input_method) override { method = input_method; } + virtual std::string getDecompMethod() const override { return method; } + virtual int getNumCriteria() const override { return m_critFields.size(); } + virtual bool isMultiCriteriaRebalance() const override { return true; } + virtual bool shouldFixMechanisms() const override { return false; } + + virtual double getGraphVertexWeight(stk::mesh::Entity entity, int criteria_index) const override + { + ThrowRequireWithSierraHelpMsg( + criteria_index >= 0 && static_cast(criteria_index) < m_critFields.size()); + const double * weight = stk::mesh::field_data(*m_critFields[criteria_index], entity); + if (weight != nullptr) + { + ThrowRequireWithSierraHelpMsg(*weight >= 0); + return *weight; + } + else + { + return m_defaultWeight; + } + } + +protected: + MultipleCriteriaSettings() = delete; + MultipleCriteriaSettings(const MultipleCriteriaSettings &) = delete; + MultipleCriteriaSettings & operator=(const MultipleCriteriaSettings &) = delete; + + const stk::mesh::BulkData & m_stkMeshBulkData; + const std::vector *> m_critFields; + const double m_defaultWeight; +}; + +class CDFEMRebalance final : public MultipleCriteriaSettings +{ +public: + CDFEMRebalance(stk::mesh::BulkData & bulk_data, + CDMesh * cdmesh, + const std::string & coordinates_field_name, + const std::vector *> & weights_fields, + const double imbalance_threshold, + const double default_weight = 0.) + : MultipleCriteriaSettings(bulk_data, weights_fields, default_weight), + my_cdmesh(cdmesh), + my_bulk_data(bulk_data), + my_coordinates_field_name(coordinates_field_name), + my_imbalance_threshold(imbalance_threshold) + {} + + ~CDFEMRebalance() = default; + + double getImbalanceTolerance() const override { return my_imbalance_threshold; } + std::string getCoordinateFieldName() const override { return my_coordinates_field_name; } + + void modifyDecomposition(stk::balance::DecompositionChangeList & decomp_changes) const override; + bool shouldPrintMetrics() const override { return true; } + bool isIncrementalRebalance() const override { return true; } + + virtual double getGraphVertexWeight(stk::mesh::Entity entity, int criteria_index) const override + { + double scaleVertexWeightForTestingDueToSmallMesh = 12; + return scaleVertexWeightForTestingDueToSmallMesh*MultipleCriteriaSettings::getGraphVertexWeight(entity, criteria_index); + } + +private: + CDMesh * my_cdmesh; + stk::mesh::BulkData & my_bulk_data; + std::string my_coordinates_field_name; + double my_imbalance_threshold; +}; + +void CDFEMRebalance::modifyDecomposition(stk::balance::DecompositionChangeList & decomp_changes) const +{ + /* Algorithm: + * 1) Go through change list and update destinations of all adaptivity children to match their + * root parent. Also move family trees with the parent+child entities. + * 2) Go through change list and remove any CDFEM child elements whose parents aren't being moved, + * as well as update the destination of all CDFEM children with parents that are being moved to + * the parent destination. + */ + + impl::update_rebalance_for_adaptivity(decomp_changes, my_bulk_data); + + if(my_cdmesh) + { + impl::update_rebalance_for_cdfem(decomp_changes, my_bulk_data, *my_cdmesh); + } +} + +} // anonymous namespace + +void +update_parent_child_rebalance_weights(const stk::mesh::BulkData & bulk_data, + stk::mesh::Field & element_weights_field, + const CDMesh * cdmesh) +{ + // First sum CDFEM child weights to their CDFEM parents, then adaptivity children to + // their parents. Adaptivity intermediate parents should not have their weights + // summed, they should be 0. + if(cdmesh) + { + impl::accumulate_cdfem_child_weights_to_parents(bulk_data, element_weights_field, *cdmesh); + } + + impl::accumulate_adaptivity_child_weights_to_parents(bulk_data, element_weights_field); +} + +bool rebalance_mesh(stk::mesh::BulkData & bulk_data, + CDMesh * cdmesh, + const std::string & element_weights_field_name, + const std::string & coordinates_field_name, + const std::vector & selections_to_rebalance_separately, + const std::string & decomp_method, + const double imbalance_threshold) +{ + const auto & meta = bulk_data.mesh_meta_data(); + auto weights_base = meta.get_field(stk::topology::ELEMENT_RANK, element_weights_field_name); + ThrowRequireMsg(weights_base, + "Failed to find element rank field " << element_weights_field_name + << " to use for rebalance weights."); + const auto element_weights_field = static_cast *>(weights_base); + + update_parent_child_rebalance_weights(bulk_data, *element_weights_field, cdmesh); + + ThrowAssert(impl::check_family_tree_element_and_side_ownership(bulk_data)); + + CDFEMRebalance balancer( + bulk_data, cdmesh, coordinates_field_name, {element_weights_field}, imbalance_threshold); + balancer.setDecompMethod(decomp_method); + const bool rebalanced = + stk::balance::balanceStkMesh(balancer, bulk_data, selections_to_rebalance_separately); + + ThrowAssert(impl::check_family_tree_element_and_side_ownership(bulk_data)); + + if(cdmesh) + { + cdmesh->rebuild_after_rebalance(); + } + + return rebalanced; +} + +bool rebalance_mesh(stk::mesh::BulkData & bulk_data, + CDMesh * cdmesh, + const std::vector & element_weights_field_names, + const std::string & coordinates_field_name, + const std::string & decomp_method, + const double imbalance_threshold) +{ + const auto & meta = bulk_data.mesh_meta_data(); + + std::vector *> weights_fields; + for (auto && field_name : element_weights_field_names) + { + auto weights_base = meta.get_field(stk::topology::ELEMENT_RANK, field_name); + ThrowRequireMsg(weights_base, + "Failed to find element rank field " << field_name << " to use for rebalance weights."); + const auto element_weights_field = static_cast *>(weights_base); + + update_parent_child_rebalance_weights(bulk_data, *element_weights_field, cdmesh); + weights_fields.push_back(element_weights_field); + } + + ThrowAssert(impl::check_family_tree_element_and_side_ownership(bulk_data)); + + CDFEMRebalance balancer( + bulk_data, cdmesh, coordinates_field_name, weights_fields, imbalance_threshold); + balancer.setDecompMethod(decomp_method); + const bool rebalanced = stk::balance::balanceStkMesh(balancer, bulk_data); + if (rebalanced) + stk::balance::balanceStkMeshNodes(balancer, bulk_data); + + ThrowAssert(impl::check_family_tree_element_and_side_ownership(bulk_data)); + + if (cdmesh) + { + cdmesh->rebuild_after_rebalance(); + } + + return rebalanced; +} +} +} + diff --git a/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.hpp b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.hpp new file mode 100644 index 000000000000..dc77cc83328f --- /dev/null +++ b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils.hpp @@ -0,0 +1,46 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_REBALANCE_UTILS_INCLUDE_AKRI_REBALANCEUTILS_H_ +#define KRINO_REBALANCE_UTILS_INCLUDE_AKRI_REBALANCEUTILS_H_ + +#include +#include +#include + +namespace krino { class CDMesh; } + +namespace krino { +namespace rebalance_utils { + +// This function will call STK rebalance to rebalance the specified selections of the +// mesh based on the ELEMENT_RANK field element_weights_field. If cdmesh != nullptr the +// rebalance operation will ensure that all CDFEM child elements are moved to the same +// processor as their parent elements. The weights field will be adjusted +// such that the parent elements are weighted with the sum of their child +// weights, and the child weights are 0 for both CDFEM and adaptivity parents/children. +bool rebalance_mesh(stk::mesh::BulkData & bulk_data, + CDMesh * cdmesh, + const std::string & element_weights_field_name, + const std::string & coordinates_field_name, + const std::vector & selections_to_rebalance_separately, + const std::string & decomp_method = "parmetis", + const double imbalance_threshold = 1.05); + +// This version handles multiple criteria rebalancing with different weights for each +// criterion. +bool rebalance_mesh(stk::mesh::BulkData & bulk_data, + CDMesh * cdmesh, + const std::vector & element_weights_field_names, + const std::string & coordinates_field_name, + const std::string & decomp_method = "parmetis", + const double imbalance_threshold = 1.05); +} +} + +#endif /* KRINO_REBALANCE_UTILS_INCLUDE_AKRI_REBALANCEUTILS_H_ */ diff --git a/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.cpp b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.cpp new file mode 100644 index 000000000000..2b6f450d3546 --- /dev/null +++ b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.cpp @@ -0,0 +1,348 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { +namespace rebalance_utils { +namespace impl { + +void set_family_tree_destinations(stk::balance::DecompositionChangeList & decomp_changes, const stk::mesh::BulkData & bulk_data) +{ + // At this point decomp_changes has all adaptivity parent + child elements that are moving + // and their dest procs match. + // First iterate the element family trees and set their destinations. + auto changes = decomp_changes.get_all_partition_changes(); + for (auto && change : changes) + { + const auto elem = change.first; + const auto dest_proc = change.second; + + const auto * fts = bulk_data.begin(elem, stk::topology::CONSTRAINT_RANK); + const int num_fts = bulk_data.num_connectivity(elem, stk::topology::CONSTRAINT_RANK); + for (int i = 0; i < num_fts; ++i) + { + if (bulk_data.bucket(elem).owned()) + { + ThrowRequire(!decomp_changes.has_entity(fts[i]) || + decomp_changes.get_entity_destination(fts[i]) == dest_proc); + decomp_changes.set_entity_destination(fts[i], dest_proc); + } + } + } + + std::vector child_sides; + const auto side_rank = bulk_data.mesh_meta_data().side_rank(); + changes = decomp_changes.get_all_partition_changes(); + for (auto && change : changes) + { + const auto elem = change.first; + const auto dest_proc = change.second; + + if (bulk_data.entity_rank(elem) != stk::topology::ELEMENT_RANK || + is_refinement_child(bulk_data, elem) || !has_refinement_children(bulk_data, elem)) + continue; + + const auto * root_sides = bulk_data.begin(elem, side_rank); + const int num_root_sides = bulk_data.num_connectivity(elem, side_rank); + for (int i = 0; i < num_root_sides; ++i) + { + if (!bulk_data.bucket(root_sides[i]).owned()) continue; + + get_refinement_all_children(bulk_data, root_sides[i], child_sides); + child_sides.push_back(root_sides[i]); + + for (auto && child_side : child_sides) + { + ThrowRequire(bulk_data.bucket(child_side).owned()); + const auto * fts = bulk_data.begin(child_side, stk::topology::CONSTRAINT_RANK); + const int num_fts = bulk_data.num_connectivity(child_side, stk::topology::CONSTRAINT_RANK); + for (int j = 0; j < num_fts; ++j) + { + const auto ft = fts[j]; + if (!decomp_changes.has_entity(ft) || + decomp_changes.get_entity_destination(ft) > dest_proc) + { + decomp_changes.set_entity_destination(ft, dest_proc); + } + } + } + } + } +} + +void +update_rebalance_for_adaptivity(stk::balance::DecompositionChangeList & decomp_changes, + const stk::mesh::BulkData & bulk_data) +{ + auto all_changes = decomp_changes.get_all_partition_changes(); + + // First pass remove all refinement children from the list of changes. + // Second pass will set their destinations all to the destination of their root parent + // if it is moving. + for(auto && change : all_changes) + { + stk::mesh::Entity entity = change.first; + if(is_refinement_child(bulk_data, entity)) + { + decomp_changes.delete_entity(entity); + } + } + + all_changes = decomp_changes.get_all_partition_changes(); + stk::mesh::EntityVector adapt_children; + for(auto && change : all_changes) + { + stk::mesh::Entity entity = change.first; + const auto dest = change.second; + + get_refinement_all_children(bulk_data, entity, adapt_children); + for(auto && child : adapt_children) + { + decomp_changes.set_entity_destination(child, dest); + } + } + + set_family_tree_destinations(decomp_changes, bulk_data); + + all_changes = decomp_changes.get_all_partition_changes(); + for(auto && change : all_changes) + { + stk::mesh::Entity entity = change.first; + const auto dest = change.second; + + if (bulk_data.entity_rank(entity) == stk::topology::CONSTRAINT_RANK) + { + auto side_rank = bulk_data.mesh_meta_data().side_rank(); + const auto * conn_sides = bulk_data.begin(entity, side_rank); + const int num_sides = bulk_data.num_connectivity(entity, side_rank); + for (int i = 0; i < num_sides; ++i) + { + ThrowRequire(!decomp_changes.has_entity(conn_sides[i])); + const auto * conn_fts = bulk_data.begin(conn_sides[i], stk::topology::CONSTRAINT_RANK); + const int num_fts = + bulk_data.num_connectivity(conn_sides[i], stk::topology::CONSTRAINT_RANK); + for (int j = 0; j < num_fts; ++j) + { + ThrowErrorMsgIf(decomp_changes.get_entity_destination(conn_fts[j]) != dest, + "Family Tree:\n" << debug_entity(bulk_data, conn_fts[j]) + << "\nHas destination proc = " + << decomp_changes.get_entity_destination(conn_fts[j]) + << ", which is different than " << dest + << " the destination for family tree:\n" + << debug_entity(bulk_data, entity) << "\n\n"); + } + } + } + } +} + +void +update_rebalance_for_cdfem(stk::balance::DecompositionChangeList & decomp_changes, + const stk::mesh::BulkData & bulk_data, + const CDMesh & cdmesh) +{ + auto all_changes = decomp_changes.get_all_partition_changes(); + std::vector subelements; + const auto & cdfem_parent_part = cdmesh.get_parent_part(); + const auto & cdfem_child_part = cdmesh.get_child_part(); + for(auto && change : all_changes) + { + stk::mesh::Entity entity = change.first; + const auto dest = change.second; + + if(bulk_data.bucket(entity).member(cdfem_parent_part)) + { + const auto * mesh_elem = cdmesh.find_mesh_element(bulk_data.identifier(entity)); + ThrowRequire(mesh_elem); + + subelements.clear(); + mesh_elem->get_subelements(subelements); + + for(auto && subelem : subelements) + { + auto subelem_entity = subelem->entity(); + ThrowRequire(bulk_data.is_valid(subelem_entity)); + + decomp_changes.set_entity_destination(subelem_entity, dest); + } + } + else if(bulk_data.bucket(entity).member(cdfem_child_part)) + { + const auto parent_elem = cdmesh.get_parent_element(entity); + if(!decomp_changes.has_entity(parent_elem)) decomp_changes.delete_entity(entity); + } + } +} + +void +accumulate_cdfem_child_weights_to_parents(const stk::mesh::BulkData & bulk_data, + stk::mesh::Field & element_weights_field, + const CDMesh & cdmesh) +{ + auto selector = + stk::mesh::selectField(element_weights_field) & cdmesh.get_child_part() & + bulk_data.mesh_meta_data().locally_owned_part(); + const auto & buckets = bulk_data.get_buckets(stk::topology::ELEMENT_RANK, selector); + for(auto && b_ptr : buckets) + { + double * weight_data = stk::mesh::field_data(element_weights_field, *b_ptr); + const int b_size = b_ptr->size(); + for(int i=0; i < b_size; ++i) + { + const auto child_elem = (*b_ptr)[i]; + const auto parent = cdmesh.get_parent_element(child_elem); + double * parent_weight_data = stk::mesh::field_data(element_weights_field, parent); + ThrowRequire(parent_weight_data); + *parent_weight_data += weight_data[i]; + weight_data[i] = 0.; + } + } +} + +void accumulate_adaptivity_child_weights_to_parents( + const stk::mesh::BulkData & bulk_data, stk::mesh::Field & element_weights_field) +{ + auto selector = stk::mesh::selectField(element_weights_field) & + bulk_data.mesh_meta_data().locally_owned_part(); + std::vector all_children; + const auto & buckets = bulk_data.get_buckets(stk::topology::ELEMENT_RANK, selector); + for (auto && b_ptr : buckets) + { + for (auto && elem : *b_ptr) + { + if (is_refinement_child(bulk_data, elem)) continue; + + all_children.clear(); + get_refinement_all_children(bulk_data, elem, all_children); + + double child_weights_sum = 0.; + for (auto && child : all_children) + { + double & child_weight = *stk::mesh::field_data(element_weights_field, child); + child_weights_sum += child_weight; + child_weight = 0.; + } + double & parent_weight = *stk::mesh::field_data(element_weights_field, elem); + parent_weight += child_weights_sum; + } + } +} + +namespace +{ +bool is_owned(const stk::mesh::BulkData & mesh, const stk::mesh::Entity entity) +{ + const bool owned_part = mesh.bucket(entity).member(mesh.mesh_meta_data().locally_owned_part()); + const bool owner_rank = mesh.parallel_rank() == mesh.parallel_owner_rank(entity); + ThrowErrorMsgIf(owned_part != owner_rank, + "Mismatch between parallel_owner_rank and locally_owned_part membership for:\n" + << debug_entity(mesh, entity) << "\n"); + return owner_rank; +} +} + +bool check_family_tree_element_and_side_ownership(const stk::mesh::BulkData & bulk_data) +{ + const auto & meta = bulk_data.mesh_meta_data(); + bool passed = true; + + const auto & locally_owned_part = meta.locally_owned_part(); + const auto & elem_buckets = bulk_data.get_buckets(stk::topology::ELEMENT_RANK, locally_owned_part); + for(auto && b_ptr : elem_buckets) + { + for(auto && elem : *b_ptr) + { + const auto * fts = bulk_data.begin(elem, stk::topology::CONSTRAINT_RANK); + const int num_fts = bulk_data.num_connectivity(elem, stk::topology::CONSTRAINT_RANK); + for(int i=0; i < num_fts; ++i) + { + if(!is_owned(bulk_data, fts[i])) + { + krinolog << "Found non-owned family tree connected to owned element.\n"; + krinolog << "Family tree:\n" << debug_entity(bulk_data, fts[i]) << "\n"; + passed = false; + } + } + } + } + const auto side_rank = meta.side_rank(); + const auto & side_buckets = bulk_data.get_buckets(side_rank, locally_owned_part); + for(auto && b_ptr : side_buckets) + { + for(auto && side : *b_ptr) + { + const auto * fts = bulk_data.begin(side, stk::topology::CONSTRAINT_RANK); + const int num_fts = bulk_data.num_connectivity(side, stk::topology::CONSTRAINT_RANK); + for(int i=0; i < num_fts; ++i) + { + if(!is_owned(bulk_data, fts[i])) + { + krinolog << "Found non-owned family tree connected to owned side.\n"; + krinolog << "Family tree:\n" << debug_entity(bulk_data, fts[i]) << "\n"; + passed = false; + } + } + } + } + + const auto & ft_buckets = bulk_data.get_buckets(stk::topology::CONSTRAINT_RANK, locally_owned_part); + for(auto && b_ptr : ft_buckets) + { + for(auto && ft : *b_ptr) + { + const auto * elems = bulk_data.begin(ft, stk::topology::ELEMENT_RANK); + const int num_elems = bulk_data.num_connectivity(ft, stk::topology::ELEMENT_RANK); + for(int i=0; i < num_elems; ++i) + { + if(!is_owned(bulk_data, elems[i])) + { + krinolog << "Found non-owned element connected to owned family tree.\n"; + krinolog << "Element ID:\n" << bulk_data.identifier(elems[i]) << "\n"; + krinolog << "Family tree:\n" << debug_entity(bulk_data, ft) << "\n"; + passed = false; + } + } + const auto * sides = bulk_data.begin(ft, side_rank); + const int num_sides = bulk_data.num_connectivity(ft, side_rank); + for(int i=0; i < num_sides; ++i) + { + if(!is_owned(bulk_data, sides[i])) + { + krinolog << "Found non-owned side connected to owned family tree.\n"; + krinolog << "Element ID:\n" << bulk_data.identifier(sides[i]) << "\n"; + krinolog << "Family tree:\n" << debug_entity(bulk_data, ft) << "\n"; + passed = false; + } + } + } + } + + return passed; +} + +} +} +} diff --git a/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.hpp b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.hpp new file mode 100644 index 000000000000..85865baa15a4 --- /dev/null +++ b/packages/krino/krino/rebalance_utils/Akri_RebalanceUtils_Impl.hpp @@ -0,0 +1,44 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_REBALANCE_UTILS_INCLUDE_AKRI_REBALANCEUTILS_IMPL_H_ +#define KRINO_REBALANCE_UTILS_INCLUDE_AKRI_REBALANCEUTILS_IMPL_H_ + +#include + +namespace stk { namespace balance { class DecompositionChangeList; } } +namespace stk { namespace mesh { class BulkData; } } +namespace krino { class CDMesh; } + +namespace krino { +namespace rebalance_utils { +namespace impl { + +void +update_rebalance_for_adaptivity(stk::balance::DecompositionChangeList & decomp, + const stk::mesh::BulkData & bulk_data); + +void +update_rebalance_for_cdfem(stk::balance::DecompositionChangeList & decomp, + const stk::mesh::BulkData & bulk_data, + const CDMesh & cdmesh); + +void +accumulate_cdfem_child_weights_to_parents(const stk::mesh::BulkData & bulk_data, + stk::mesh::Field & element_weights_field, + const CDMesh & cdmesh); + +void accumulate_adaptivity_child_weights_to_parents( + const stk::mesh::BulkData & bulk_data, stk::mesh::Field & element_weights_field); + +bool check_family_tree_element_and_side_ownership(const stk::mesh::BulkData & bulk_data); +} +} +} + +#endif /* KRINO_REBALANCE_UTILS_INCLUDE_AKRI_REBALANCEUTILS_IMPL_H_ */ diff --git a/packages/krino/krino/rebalance_utils/CMakeLists.txt b/packages/krino/krino/rebalance_utils/CMakeLists.txt new file mode 100644 index 000000000000..b2820a96ae3b --- /dev/null +++ b/packages/krino/krino/rebalance_utils/CMakeLists.txt @@ -0,0 +1,19 @@ +SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + krino_rebalance_utils_lib + HEADERS ${HEADERS} + SOURCES ${SOURCES} + DEPLIBS krino_lib) + +INSTALL(FILES ${HEADERS} DESTINATION + ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/krino_rebalance_utils_lib) + diff --git a/packages/krino/krino/region/Akri_Region.cpp b/packages/krino/krino/region/Akri_Region.cpp new file mode 100644 index 000000000000..ebf7ac13eade --- /dev/null +++ b/packages/krino/krino/region/Akri_Region.cpp @@ -0,0 +1,664 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino{ + +//-------------------------------------------------------------------------------- + +Region::Region(Simulation & owning_simulation, const std::string & regionName) +: my_simulation(owning_simulation), + my_meta(nullptr), + my_bulk(nullptr), + my_name(regionName), + my_timerRegion(std::string("Region ") + regionName, sierra::Diag::TIMER_REGION, my_simulation.get_timer()), + my_timerInitialize("Initialize", my_timerRegion), + my_timerExecute("Execute", my_timerRegion), + my_timerMeshInput("Mesh input", my_timerRegion), + my_timerMeshOutput("Mesh output", my_timerRegion), + my_output_file_index(0), + my_output_file_created(false), + my_initial_refinement_levels(0) +{ /* %TRACE[ON]% */ Trace trace__("krino::Region::Region()"); /* %TRACE% */ + my_simulation.add_region(this); + myIOBroker = std::make_unique(stk::EnvData::parallel_comm()); + + std::vector entity_rank_names = stk::mesh::entity_rank_names(); + entity_rank_names.push_back("FAMILY_TREE"); + stk_IO().set_rank_name_vector(entity_rank_names); + + my_results_options = std::make_unique(); +} +//-------------------------------------------------------------------------------- +Region::~Region() +{ +} + +static bool locally_has_64bit_ids_in_use_for_nodes_or_elements(const stk::mesh::BulkData & mesh) +{ + const uint64_t max32bitId = ~0U; + for (auto entityRank : {stk::topology::NODE_RANK, stk::topology::ELEMENT_RANK}) + for (auto && bucketPtr : mesh.buckets(entityRank)) + for (auto && entity : *bucketPtr) + if (mesh.identifier(entity) > max32bitId) + return true; + + return false; +} + +static bool cdfem_mesh_displacements_requested_in_results_fields(const std::string & cdfemMeshDisplacementsFieldName, const std::set & resultsFields) +{ + for (auto && resultsField : resultsFields) + if (stk::equal_case(resultsField.first, cdfemMeshDisplacementsFieldName)) + return true; + return false; +} + +//-------------------------------------------------------------------------------- +void Region::commit() +{ /* %TRACE[ON]% */ Trace trace__("krino::Region::commit()"); /* %TRACE% */ + + auto & meta = get_stk_mesh_meta_data(); + LevelSet::setup(meta); + krino::CDFEM_Support & cdfem_support = krino::CDFEM_Support::get(meta); + + if (krino::CDFEM_Support::is_active(meta)) + { + if (cdfem_mesh_displacements_requested_in_results_fields(CDFEM_Support::cdfem_mesh_displacements_field_name(), my_results_options->get_nodal_fields())) + cdfem_support.register_cdfem_mesh_displacements_field(); + + if (cdfem_support.get_num_initial_decomposition_cycles() > 1) + cdfem_support.register_parent_node_ids_field(); // Needed to use piecewise linear location on previously cut edges + + cdfem_support.setup_fields(); + } + + auto & active_part = AuxMetaData::get(meta).active_part(); + stk::mesh::BulkData::AutomaticAuraOption auto_aura_option = stk::mesh::BulkData::NO_AUTO_AURA; + + if (my_initial_refinement_levels > 0 || (krino::CDFEM_Support::is_active(meta) && cdfem_support.get_interface_maximum_refinement_level() > 0) || + (krino::CDFEM_Support::is_active(meta) && cdfem_support.get_post_cdfem_refinement_levels() > 0)) + { + auto_aura_option = stk::mesh::BulkData::AUTO_AURA; + HAdapt::setup(meta, active_part, my_timerExecute); + } + + if (cdfem_support.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + { + auto_aura_option = stk::mesh::BulkData::AUTO_AURA; + } + + if (krino::CDFEM_Support::is_active(meta)) + { + cdfem_support.add_interpolation_field(cdfem_support.get_coords_field()); + const LevelSetManager & region_ls = LevelSetManager::get(meta); + for(auto&& ls : region_ls) + { + cdfem_support.add_interpolation_field(ls->get_distance_field()); + } + cdfem_support.finalize_fields(); + + if(cdfem_support.get_interface_maximum_refinement_level() > 0 || + cdfem_support.get_post_cdfem_refinement_levels() > 0) + { + cdfem_support.set_nonconformal_hadapt( + [&meta](const std::string & marker_field, int debug_level) + { + HAdapt::do_adaptive_refinement(meta, marker_field); + }); + } + } + + if (nullptr != my_generated_mesh.get()) + { + set_generated_mesh_domain(); + my_generated_mesh->populate_mesh(stk::EnvData::parallel_comm(), auto_aura_option); + my_bulk = &my_generated_mesh->bulk_data(); + stk_IO().set_bulk_data( Teuchos::rcpFromRef( *my_bulk ) ); + if (my_generated_mesh->has_flat_boundaries()) + my_generated_mesh->create_domain_sides(); + } + else + { + my_bulk = new stk::mesh::BulkData(meta,stk::EnvData::parallel_comm(),auto_aura_option); + stk_IO().set_bulk_data( Teuchos::rcp( my_bulk ) ); + stk_IO().populate_bulk_data(); + } + + if (AuxMetaData::get(get_stk_mesh_meta_data()).get_assert_32bit_flag()) + { + const bool has_64bit_ids_in_use = stk::is_true_on_any_proc(my_bulk->parallel(), locally_has_64bit_ids_in_use_for_nodes_or_elements(*my_bulk)); + ThrowErrorMsgIf(has_64bit_ids_in_use, "Option use_32_bit ids is active, but input file uses 64 bit ids."); + } + else + { + my_bulk->set_large_ids_flag(true); + } + + if (my_bulk->is_automatic_aura_on() || my_bulk->parallel_size() == 1) + { + // Used for element side connectivty checks + stk::mesh::create_exposed_block_boundary_sides(*my_bulk, meta.universal_part(), {&AuxMetaData::get(meta).exposed_boundary_part()}); + } + + + + activate_all_entities(*my_bulk, active_part); + + LevelSet::post_commit_setup(meta); +} + +namespace { + +void zero_error_indicator(const LevelSetManager & region_ls, stk::mesh::BulkData & mesh) +{ + auto & meta = mesh.mesh_meta_data(); + const auto & cdfem_support = CDFEM_Support::get(meta); + const auto & aux_meta = AuxMetaData::get(meta); + auto indicator_field = + aux_meta.get_field(stk::topology::ELEMENT_RANK, + cdfem_support.get_nonconformal_adapt_indicator_name()); + + const auto & buckets = mesh.get_buckets(stk::topology::ELEMENT_RANK, + meta.locally_owned_part()); + for(auto && b_ptr : buckets) + { + auto * data = field_data(indicator_field, *b_ptr); + auto length = b_ptr->size(); + std::fill(data, data + length, 0.); + } +} + +void mark_selected_elements(const std::string & marker_field_name, const int current_refinement_level, const AuxMetaData & auxMeta, const int max_refinement_levels, const stk::mesh::Selector & selector) +{ + FieldRef marker_field = auxMeta.get_field(stk::topology::ELEMENT_RANK, marker_field_name); + const bool do_adapt = current_refinement_level < max_refinement_levels; + if (do_adapt) + { + stk::mesh::field_fill(1, marker_field, selector); + } +} + +void do_adaptive_refinement(const krino::CDFEM_Support & cdfemSupport, const LevelSetManager & region_ls, stk::mesh::BulkData & mesh) +{ + const auto target_count = cdfemSupport.get_nonconformal_adapt_target_count(); + if(target_count > 0) zero_error_indicator(region_ls, mesh); + { + stk::diag::TimeBlock adapt_timer__(cdfemSupport.get_timer_adapt()); + + const std::string & marker_name = "refine_field"; //cdfemSupport.get_nonconformal_adapt_marker_name(); + auto & h_adapt = cdfemSupport.get_nonconformal_hadapt(); + + const int num_refinement_levels = cdfemSupport.get_interface_maximum_refinement_level(); + const int num_refinement_steps = (target_count > 0) ? num_refinement_levels : 2*num_refinement_levels; + + const auto & indicator_field_name = cdfemSupport.get_nonconformal_adapt_indicator_name(); + + const bool do_post_adapt_refinement = cdfemSupport.get_post_adapt_refinement_levels() > 0; + std::function marker_function = + [&mesh, ®ion_ls, num_refinement_steps, do_post_adapt_refinement, target_count, indicator_field_name] + (const std::string & marker_field_name, int num_refinements) + { + const bool do_adapt = num_refinements < num_refinement_steps; + const bool more_to_do = do_adapt || do_post_adapt_refinement; + if(target_count > 0) zero_error_indicator(region_ls, mesh); + LevelSet::initialize(mesh.mesh_meta_data(), more_to_do); + if(!do_adapt) return; + + if(target_count == 0) + { + const LevelSetInterfaceGeometry interfaceGeometry(mesh.mesh_meta_data()); + CDMesh::mark_interface_elements_for_adaptivity(mesh, interfaceGeometry, marker_field_name, num_refinements); + } + else + { + HAdapt::mark_based_on_indicator_field(mesh, + marker_field_name, + indicator_field_name, + num_refinement_steps, + num_refinements, + target_count); + } + }; + + perform_multilevel_adaptivity(mesh, marker_name, marker_function, h_adapt, cdfem_do_not_refine_or_unrefine_selector(cdfemSupport)); + } +} + +void do_post_adapt_uniform_refinement(const Simulation & simulation, const krino::CDFEM_Support & cdfemSupport, const AuxMetaData & auxMeta, stk::mesh::BulkData & mesh) +{ + if ( cdfemSupport.get_post_adapt_refinement_levels() > 0 ) + { + if ( simulation.is_transient() ) + { + krinolog << "Can not do post-adaptivity uniform mesh refinement for transient problems..."; + } + else + { + krinolog << "Performing " << cdfemSupport.get_post_adapt_refinement_levels() << " levels of post-adaptivity uniform mesh refinement..." << std::endl; + + // Doing adaptive refinement with a uniform marker is better than doing uniform refinement here because of how + // the transition elements are handled. + const std::string & marker_name = "refine_field"; + auto & h_adapt = cdfemSupport.get_nonconformal_hadapt(); + const int num_levels = cdfemSupport.get_post_adapt_refinement_levels(); + FieldRef marker_field = auxMeta.get_field(stk::topology::ELEMENT_RANK, marker_name); + + std::function marker_function = + [&mesh, marker_field, num_levels] + (const std::string & marker_field_name, int num_refinements) + { + const bool do_adapt = num_refinements < num_levels; + LevelSet::initialize(mesh.mesh_meta_data(), do_adapt); + if (do_adapt) + { + stk::mesh::field_fill(1, marker_field); + } + }; + + perform_multilevel_adaptivity(mesh, marker_name, marker_function, h_adapt, cdfem_do_not_refine_or_unrefine_selector(cdfemSupport)); + } + } +} + +void do_post_cdfem_uniform_refinement(const Simulation & simulation, const krino::CDFEM_Support & cdfemSupport, const AuxMetaData & auxMeta, stk::mesh::BulkData & mesh) +{ + if ( cdfemSupport.get_post_cdfem_refinement_levels() > 0 ) + { + const int num_levels = cdfemSupport.get_post_cdfem_refinement_levels(); + if ( simulation.is_transient() ) + { + krinolog << "Can not do post-cdfem mesh refinement for transient problems..."; + } + else + { + krinolog << "Performing " << num_levels << " levels of post-cdfem mesh refinement..." << std::endl; + + // Doing adaptive refinement with a uniform marker is better than doing uniform refinement here because of how + // the transition elements are handled. + const std::string & marker_name = "refine_field"; + auto & h_adapt = cdfemSupport.get_nonconformal_hadapt(); + const stk::mesh::Selector & refinement_selector = cdfemSupport.get_post_cdfem_refinement_selector(); + std::function marker_function = + [&auxMeta, num_levels, &refinement_selector] + (const std::string & marker_field_name, int num_refinements) + { + mark_selected_elements(marker_field_name, num_refinements, auxMeta, num_levels, refinement_selector); + }; + + CDMesh::get_new_mesh()->delete_cdfem_parent_elements(); // Extreme work-around for the way percept messes up the active part on cdfem parents. + perform_multilevel_adaptivity(mesh, marker_name, marker_function, h_adapt); + } + } +} + +} + +//-------------------------------------------------------------------------------- +void Region::initialize() +{ /* %TRACE[ON]% */ Trace trace__("krino::Region::initialize()"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timerInitialize); + + const bool cdfem_is_active = krino::CDFEM_Support::is_active(get_stk_mesh_meta_data()); + krino::CDFEM_Support & cdfem_support = krino::CDFEM_Support::get(get_stk_mesh_meta_data()); + const bool adapt_is_active = cdfem_support.get_interface_maximum_refinement_level() > 0 || + cdfem_support.get_post_cdfem_refinement_levels() > 0; + + const LevelSetManager & region_ls = LevelSetManager::get(get_stk_mesh_meta_data()); + + if (my_initial_refinement_levels > 0) + { + cdfem_support.set_global_ids_are_NOT_parallel_consistent(); + HAdapt::do_uniform_refinement(get_stk_mesh_meta_data(), my_initial_refinement_levels); + } + + if (cdfem_is_active) + { + auto & bulk = get_stk_mesh_bulk_data(); + if(adapt_is_active) + { + do_adaptive_refinement(cdfem_support, region_ls, bulk); + + const auto & auxMeta = AuxMetaData::get(get_stk_mesh_meta_data()); + do_post_adapt_uniform_refinement(my_simulation, cdfem_support, auxMeta, bulk); + + const LevelSetInterfaceGeometry interfaceGeometry(get_stk_mesh_meta_data()); + krino::CDMesh::decompose_mesh(bulk, interfaceGeometry, my_simulation.get_time_step_count(), {}); + + do_post_cdfem_uniform_refinement(my_simulation, cdfem_support, auxMeta, bulk); + } + else + { + if ( cdfem_support.get_post_adapt_refinement_levels() > 0 ) + { + krinolog << "Post-adaptivity uniform refinement levels selected without any adaptivity levels. Not performing additional uniform refinement." << std::endl; + } + + const int num_init_decomp_cycles = cdfem_support.get_num_initial_decomposition_cycles(); + for (int icycle=0; icycleinitialize(0.); + + if (my_simulation.is_transient()) + { + // initialize does not end with the facets constructed so manually construct them now + ls->build_facets_locally(get_stk_mesh_meta_data().universal_part()); + + // debugging + if (krinolog.shouldPrint(LOG_FACETS)) + { + ls->facets_exoii(); + } + } + } + } + + // Skip output of empty parts + for (auto && part : get_stk_mesh_meta_data().get_parts()) + { + if (stk::io::is_part_io_part(*part)) + { + uint64_t local_num_entities = stk::mesh::count_selected_entities( + *part, get_stk_mesh_bulk_data().buckets(part->primary_entity_rank())); + uint64_t global_num_entities = 0; + stk::all_reduce_sum( + get_stk_mesh_bulk_data().parallel(), &local_num_entities, &global_num_entities, 1); + if(global_num_entities == 0) + { + stk::io::remove_io_part_attribute(*part); + } + } + } +} +//-------------------------------------------------------------------------------- + +void Region::execute() +{ /* %TRACE[ON]% */ Trace trace__("krino::Region::execute()"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timerExecute); + + // This is a hack for now to exit immediately when CDFEM is active + if (krino::CDFEM_Support::is_active(get_stk_mesh_meta_data())) + { + return; + } + + double deltaTime = time_step(); + + const LevelSetManager & region_ls = LevelSetManager::get(get_stk_mesh_meta_data()); + for(auto&& ls : region_ls) + { + ls->advance_semilagrangian(deltaTime); + } +} + +unsigned Region::spatial_dimension() const { return my_meta->spatial_dimension(); } +const stk::mesh::BulkData& Region::get_stk_mesh_bulk_data() const { ThrowRequire(my_bulk); return *my_bulk; } +stk::mesh::BulkData& Region::get_stk_mesh_bulk_data() { ThrowRequire(my_bulk); return *my_bulk; } +const stk::mesh::MetaData& Region::get_stk_mesh_meta_data() const { ThrowRequire(my_meta); return *my_meta; } +stk::mesh::MetaData& Region::get_stk_mesh_meta_data() { ThrowRequire(my_meta); return *my_meta; } +double Region::time_step() const { return my_simulation.get_time_step(); } + +stk::io::StkMeshIoBroker & Region::stk_IO() +{ + ThrowRequire(myIOBroker); + return *myIOBroker; +} + +Ioss::Region * Region::get_input_io_region() +{ + return stk_IO().get_input_io_region().get(); +} + +std::string Region::name_of_input_mesh() const +{ + return my_input_model_name; +} + +void Region::create_output_mesh() +{ + bool output_mesh = my_results_options->get_num_step_increments() != 0; + + if(!output_mesh) return; + + my_results_options->get_scheduler().set_termination_time(my_simulation.get_stop_time()); + + my_output_file_index = stk_IO().create_output_mesh(my_results_options->get_filename(), stk::io::WRITE_RESULTS, my_results_options->get_properties()); + + Teuchos::RCP active_selector = Teuchos::rcp(new stk::mesh::Selector(AuxMetaData::get(get_stk_mesh_meta_data()).active_part())); + stk_IO().set_subset_selector(my_output_file_index, active_selector); + + stk_IO().write_output_mesh(my_output_file_index); + + declare_output_variables(my_output_file_index); + + my_output_file_created = true; +} + +void +Region::declare_output_variables(size_t result_output_index) +{ + for (auto && outField : my_results_options->get_nodal_fields()) + { + const std::string & varName = outField.first; + const std::string & newName = outField.second; + stk::mesh::FieldBase *theField = get_stk_mesh_meta_data().get_field(stk::topology::NODE_RANK, varName); + if ( nullptr == theField ) + { + krinolog << "Sorry, no nodal field by the name " << varName << std::endl; + krinolog << "Available fields: " << std::endl; + for (auto && field : get_stk_mesh_meta_data().get_fields(stk::topology::NODE_RANK)) + { + krinolog << " " << field->name() << std::endl; + } + } + else + { + stk_IO().add_field(result_output_index, *theField, newName); + } + } + + for (auto && outField : my_results_options->get_element_fields()) + { + const std::string & varName = outField.first; + const std::string & newName = outField.second; + stk::mesh::FieldBase *theField = get_stk_mesh_meta_data().get_field(stk::topology::ELEMENT_RANK, varName); + if ( nullptr == theField ) + { + krinolog << "Sorry, no element field by the name " << varName << std::endl; + krinolog << "Available fields: " << std::endl; + for (auto && field : get_stk_mesh_meta_data().get_fields(stk::topology::ELEMENT_RANK)) + { + krinolog << " " << field->name() << std::endl; + } + } + else + { + stk_IO().add_field(result_output_index, *theField, newName); + } + } +} + +void Region::process_output(bool forceOutput) +{ + stk::diag::TimeBlock mesh_output_timeblock(my_timerMeshOutput); + + if(my_results_options->get_num_step_increments() == 0) return; + + stk::util::Scheduler & scheduler = my_results_options->get_scheduler(); + + if(forceOutput) scheduler.set_force_schedule(); + + const int stepCounter = my_simulation.get_time_step_count(); + const double currentTime = my_simulation.get_current_time(); + + const bool doOutput = scheduler.is_it_time(currentTime, stepCounter); + + if(!doOutput) return; + + // A couple of krino tests that just compute surface distances never call Region::initialize() + // where we normally create the output mesh. + if(!my_output_file_created) create_output_mesh(); + + stk_IO().begin_output_step(my_output_file_index, currentTime); + stk_IO().write_defined_output_fields(my_output_file_index); + stk_IO().end_output_step(my_output_file_index); +} + +void +Region::associate_input_mesh(const std::string & model_name, bool assert_32bit_ids, bool force_64bit_ids) +{ + stk::diag::TimeBlock mesh_input_timeblock(my_timerMeshInput); + + MeshInputOptions * db_options = MeshInputOptions::get(model_name); + ThrowRequireMsg(db_options != nullptr, "No finite element model found with name " << model_name); + my_input_model_name = model_name; + + ThrowRequire(db_options->is_valid()); + if (db_options->use_generated_mesh()) + { + stk::topology generated_mesh_element_type = db_options->get_generated_mesh_element_type(); + std::vector entity_rank_names = stk::mesh::entity_rank_names(); + entity_rank_names.push_back("FAMILY_TREE"); + my_generated_mesh = std::make_unique(generated_mesh_element_type,entity_rank_names); + my_meta = &my_generated_mesh->meta_data(); + stk::mesh::Field & coords_field = my_meta->declare_field>(stk::topology::NODE_RANK, "coordinates", 1); + stk::mesh::put_field_on_mesh(coords_field, my_meta->universal_part(), generated_mesh_element_type.dimension(), nullptr); + } + else + { + const std::string directory = sierra::Env::working_directory().c_str(); + const std::string filename(Ioss::Utils::local_filename(db_options->get_filename(), db_options->get_filetype(), directory)); + + stk::io::StkMeshIoBroker & stk_io = stk_IO(); + + stk_io.property_add(Ioss::Property("MAXIMUM_NAME_LENGTH", 180)); + + if (!db_options->get_decomposition_method().empty()) + { + stk_io.property_add(Ioss::Property("DECOMPOSITION_METHOD", Ioss::Utils::uppercase(db_options->get_decomposition_method()))); + } + + stk_io.add_mesh_database(db_options->get_filename(), stk::io::READ_MESH); + stk_io.create_input_mesh(); + my_meta = &stk_io.meta_data(); + AuxMetaData::create(*my_meta); + auto & active_part = AuxMetaData::get(*my_meta).active_part(); + stk::mesh::Selector activeSelector = active_part; + stk_io.set_active_selector(activeSelector); + } + + if (assert_32bit_ids) + { + AuxMetaData::get(*my_meta).set_assert_32bit_flag(); + } + if (!force_64bit_ids) + { + AuxMetaData::get(*my_meta).clear_force_64bit_flag(); + } +} + +void +Region::set_generated_mesh_domain() +{ + MeshInputOptions * db_options = MeshInputOptions::get(my_input_model_name); + ThrowRequire(db_options != nullptr); + my_generated_mesh->set_mesh_structure_type(db_options->get_generated_mesh_structure_type()); + const double mesh_size = db_options->get_generated_mesh_size(); + if (db_options->get_generated_mesh_domain_type() == MeshInputOptions::GENERATED_MESH_FOR_SPECIFIED_DOMAIN) + { + typename BoundingBoxMesh::BoundingBoxType bbox; + const std::vector & domain = db_options->get_generated_mesh_domain(); + if (db_options->get_generated_mesh_spatial_dimension() == 2) + { + ThrowRequire(domain.size() == 4); + const Vector3d min(domain[0], domain[1], 0.); + const Vector3d max(domain[2], domain[3], 0.); + ThrowRequireMsg(max[0]>min[0] && max[1]>min[1], "Invalid domain specified."); + bbox = BoundingBoxMesh::BoundingBoxType(min, max); + } + else + { + ThrowRequire(domain.size() == 6); + const Vector3d min(domain[0], domain[1], domain[2]); + const Vector3d max(domain[3], domain[4], domain[5]); + ThrowRequireMsg(max[0]>min[0] && max[1]>min[1] && max[2]>min[2], "Invalid domain specified."); + bbox = BoundingBoxMesh::BoundingBoxType(min, max); + } + my_generated_mesh->set_domain(bbox, mesh_size); + } + else + { + typename BoundingBoxMesh::BoundingBoxType domain_bbox; + const LevelSetManager & region_ls = LevelSetManager::get(get_stk_mesh_meta_data()); + for(auto&& ls : region_ls) + { + if (ls->has_IC_surfaces()) + { + BoundingBox ls_IC_surf_bbox = ls->get_IC_surface_bounding_box(); + domain_bbox.accommodate(ls_IC_surf_bbox); + } + } + + if (db_options->get_generated_mesh_spatial_dimension() == 2) + { + Vector3d min = domain_bbox.get_min(); + Vector3d max = domain_bbox.get_max(); + min[2] = 0.; + max[2] = 0.; + domain_bbox = typename BoundingBoxMesh::BoundingBoxType(min, max); + } + my_generated_mesh->set_domain(domain_bbox, mesh_size, 1); + } +} + +} // namespace krino + diff --git a/packages/krino/krino/region/Akri_Region.hpp b/packages/krino/krino/region/Akri_Region.hpp new file mode 100644 index 000000000000..7b175775ae6b --- /dev/null +++ b/packages/krino/krino/region/Akri_Region.hpp @@ -0,0 +1,81 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Region_h +#define Akri_Region_h + +#include +#include +#include + +namespace stk { namespace mesh { class MetaData; } } +namespace stk { namespace mesh { class BulkData; } } +namespace stk { namespace mesh { class Part; } } +namespace stk { namespace io { class StkMeshIoBroker; } } +namespace krino { class BoundingBoxMesh; } +namespace krino { class RegionForwarder; } +namespace krino { class ResultsOutputOptions; } +namespace krino { class Simulation; } +namespace Ioss { class Region; } + +namespace krino{ + +class Region { +public: + Region(Simulation & owning_simulation, const std::string & regionName); + virtual ~Region(); + + virtual void commit(); + virtual void initialize(); + virtual void execute(); + + double time_step() const; + const std::string & name() const { return my_name; } + unsigned spatial_dimension() const; + const stk::mesh::BulkData& get_stk_mesh_bulk_data() const; + stk::mesh::BulkData& get_stk_mesh_bulk_data(); + const stk::mesh::MetaData& get_stk_mesh_meta_data() const; + stk::mesh::MetaData& get_stk_mesh_meta_data(); + stk::diag::Timer & getRegionTimer() const { return my_timerRegion; } + stk::diag::Timer & getMeshInputTimer() const { return my_timerMeshInput; } + stk::diag::Timer & getMeshOutputTimer() const { return my_timerMeshOutput; } + + stk::io::StkMeshIoBroker & stk_IO(); + std::string name_of_input_mesh() const; + Ioss::Region * get_input_io_region(); + void associate_input_mesh(const std::string & model_name, bool assert_32bit_ids, bool force_64bit_ids); + void set_generated_mesh_domain(); + void create_output_mesh(); + void declare_output_variables(size_t result_output_index); + void process_output(bool forceOutput); + ResultsOutputOptions * get_results_options() { return my_results_options.get(); } + void set_initial_refinement_levels(int levels) { my_initial_refinement_levels = levels; } + +private: + Simulation & my_simulation; + stk::mesh::MetaData * my_meta; + stk::mesh::BulkData * my_bulk; + std::unique_ptr myIOBroker; + std::unique_ptr my_generated_mesh; + std::unique_ptr my_results_options; + std::string my_name; + std::string my_input_model_name; + + mutable stk::diag::Timer my_timerRegion; ///< Region's root timer + mutable stk::diag::Timer my_timerInitialize; ///< Initialize timer + mutable stk::diag::Timer my_timerExecute; ///< Execute timer + mutable stk::diag::Timer my_timerMeshInput; ///< Mesh input timer + mutable stk::diag::Timer my_timerMeshOutput; ///< Mesh output timer + size_t my_output_file_index; + bool my_output_file_created; + int my_initial_refinement_levels; +}; + +} // namespace krino + +#endif // Akri_Region_h diff --git a/packages/krino/krino/region/Akri_RegisterProduct.cpp b/packages/krino/krino/region/Akri_RegisterProduct.cpp new file mode 100644 index 000000000000..891ad313cb77 --- /dev/null +++ b/packages/krino/krino/region/Akri_RegisterProduct.cpp @@ -0,0 +1,30 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include + +namespace krino { + +const char * +get_product_name() +{ /* %TRACE% */ /* %TRACE% */ + return "Krino"; +} + + +void +register_product() +{ /* %TRACE% */ /* %TRACE% */ + // Register krino + stk::ProductRegistry::instance().addRegion(get_product_name()); +} + +} // namespace krino + diff --git a/packages/krino/krino/region/Akri_RegisterProduct.hpp b/packages/krino/krino/region/Akri_RegisterProduct.hpp new file mode 100644 index 000000000000..ab4d53824791 --- /dev/null +++ b/packages/krino/krino/region/Akri_RegisterProduct.hpp @@ -0,0 +1,20 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_RegisterProduct_h +#define Akri_RegisterProduct_h + +namespace krino { + +const char *get_product_name(); + +void register_product(); + +} // namespace krino + +#endif // Akri_RegisterProduct_h diff --git a/packages/krino/krino/region/Akri_ResultsOutputOptions.hpp b/packages/krino/krino/region/Akri_ResultsOutputOptions.hpp new file mode 100644 index 000000000000..2a0b1796bd2c --- /dev/null +++ b/packages/krino/krino/region/Akri_ResultsOutputOptions.hpp @@ -0,0 +1,88 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_ResultsOutputOptions_h +#define Akri_ResultsOutputOptions_h + +#include +#include +#include +#include +#include // for String +#include "stk_topology/topology.hpp" // for topology, etc +#include + +namespace stk { namespace mesh { class Part; } } +namespace stk { namespace mesh { class MetaData; } } + +namespace krino { + + typedef std::pair StepIncrement; + typedef std::pair FieldName_OutputName_Pair; + + class ResultsOutputOptions { + + public: + + ResultsOutputOptions() : + my_scheduler(), + my_numStepIncrements(0) + { + my_scheduler.set_lookahead(1); + my_filename = "default_filename"; + } + + stk::util::Scheduler & get_scheduler() { return my_scheduler; } + + void set_title(std::string title) { my_title = title; } + const std::string & get_title() const { return my_title; } + + void set_name(std::string theName) { my_name = theName; } + const std::string & get_name() const { return my_name; } + + void set_filename(std::string filename) { my_filename = filename; } + const std::string & get_filename() const { return my_filename; } + + void add_step_increment(int start, int increment) { + StepIncrement aStep(start, increment); + my_scheduler.add_interval(start, increment); + my_numStepIncrements++; + } + unsigned get_num_step_increments() const { return my_numStepIncrements; } + + void add_nodal_field(const std::string & internalName, const std::string & newName) + { + FieldName_OutputName_Pair aPair(internalName, newName); + my_nodal_fields.insert(aPair); + } + const std::set & get_nodal_fields() const { return my_nodal_fields; } + + void add_element_field(const std::string & internalName, const std::string & newName) + { + FieldName_OutputName_Pair aPair(internalName, newName); + my_element_fields.insert(aPair); + } + const std::set & get_element_fields() const { return my_element_fields; } + + void add_property(const Ioss::Property & property) { my_properties.add(property); } + Ioss::PropertyManager & get_properties() { return my_properties; } + + private: + stk::util::Scheduler my_scheduler; + unsigned my_numStepIncrements; + std::string my_name; + std::string my_title; + std::string my_filename; + std::set my_nodal_fields; + std::set my_element_fields; + Ioss::PropertyManager my_properties; + }; + +} // namespace krino + +#endif /* Akri_ResultsOutputOptions_h */ diff --git a/packages/krino/krino/region/Akri_Simulation.cpp b/packages/krino/krino/region/Akri_Simulation.cpp new file mode 100644 index 000000000000..313f6c1e4842 --- /dev/null +++ b/packages/krino/krino/region/Akri_Simulation.cpp @@ -0,0 +1,179 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace krino{ + +std::unique_ptr Simulation::the_simulation; + +Simulation & Simulation::build(const std::string & in_name) +{ + ThrowRequireMsg(!the_simulation, "Simulation already set."); + the_simulation = std::make_unique(in_name); + return *the_simulation; +} + +Simulation & Simulation::get() +{ + ThrowRequireMsg(the_simulation, "Simulation should already be set."); + return *the_simulation; +} + +Simulation::Simulation(const std::string & in_name) + : my_name(in_name), + my_timer("krino", sierra::Diag::sierraTimer()), + my_is_transient(false), + my_stop_time(0.0), + my_step_count(0), + my_current_time(0.0), + my_time_step_size(0.0) +{ + stk::diag::setEnabledTimerMetricsMask(stk::diag::METRICS_CPU_TIME | stk::diag::METRICS_WALL_TIME); +} + +Simulation::~Simulation() {} + +void Simulation::add_region(Region * region) +{ + my_regions.emplace_back(region); +} + +void Simulation::commit() +{/* %TRACE% */ Trace trace__("krino::Simulation::commit()"); /* %TRACE% */ + for (auto && region : my_regions) + { + region->commit(); + } +} + +void Simulation::execute() +{/* %TRACE% */ Trace trace__("krino::Simulation::execute()"); /* %TRACE% */ + stk::diag::TimeBlock timer__(my_timer); + + //===================================== + // start-up procedure + //===================================== + + // initial conditions + for (auto && region : my_regions) + { + region->initialize(); + } + + for (auto && region : my_regions) + { + region->process_output(false); + } + + //===================================== + // time integration + //===================================== + + while ( my_current_time < my_stop_time ) + { + static const double sqrt_epsilon = std::sqrt(std::numeric_limits::epsilon()); + if (my_current_time+my_time_step_size > my_stop_time*(1.-sqrt_epsilon)) + { + my_time_step_size = my_stop_time - my_current_time; + my_current_time = my_stop_time; + } + else + { + my_current_time += my_time_step_size; + } + ++my_step_count; + + const double percentComplete = 100.0*my_current_time/my_stop_time; + + krinolog << "Krino Simulation " << my_name + << ", step " << my_step_count + << ", time " << my_current_time + << ", time step " << my_time_step_size + << ", " << percentComplete << "% complete" + << stk::diag::dendl; + + for (auto && region : my_regions) + { + region->execute(); + } + + for (auto && region : my_regions) + { + region->process_output(false); + } + + for (auto && region : my_regions) + { + region->get_stk_mesh_bulk_data().update_field_data_states(); + } + } + + timer__.stop(); + print_performance_info(); +} + +void +Simulation::print_performance_info() const +{ + /* %TRACE% */ Trace trace__("krino::Simulation::print_timer_information()"); /* %TRACE% */ + + sierra::Env::outputP0() << sierra::Env::section_separator() << std::endl + << "Timing summary running on " << sierra::Env::parallel_size() << " processor" << (sierra::Env::parallel_size() == 1 ? "" : "s") << std::endl; + stk::diag::printTimersTable(sierra::Env::outputP0(), sierra::Diag::sierraTimer(), stk::diag::METRICS_CPU_TIME | stk::diag::METRICS_WALL_TIME, false, sierra::Env::parallel_comm()); + sierra::Env::outputP0() << std::endl << std::endl; + + { + size_t now, hwm; + stk::get_memory_usage(now, hwm); + // min, max, sum + size_t global_now[3] = {now,now,now}; + size_t global_hwm[3] = {hwm,hwm,hwm}; + + stk::all_reduce(sierra::Env::parallel_comm(), stk::ReduceSum<1>( &global_now[2] ) ); + stk::all_reduce(sierra::Env::parallel_comm(), stk::ReduceMin<1>( &global_now[0] ) ); + stk::all_reduce(sierra::Env::parallel_comm(), stk::ReduceMax<1>( &global_now[1] ) ); + + stk::all_reduce(sierra::Env::parallel_comm(), stk::ReduceSum<1>( &global_hwm[2] ) ); + stk::all_reduce(sierra::Env::parallel_comm(), stk::ReduceMin<1>( &global_hwm[0] ) ); + stk::all_reduce(sierra::Env::parallel_comm(), stk::ReduceMax<1>( &global_hwm[1] ) ); + + sierra::Env::outputP0() << "Memory Overview: " << std::endl; + + sierra::Env::outputP0() << " total (over all cores) current/high-water mark = " + << std::setw(15) << stk::human_bytes(global_now[2]) + << std::setw(15) << stk::human_bytes(global_hwm[2]) + << std::endl; + + sierra::Env::outputP0() << " min (over all cores) current/high-water mark = " + << std::setw(15) << stk::human_bytes(global_now[0]) + << std::setw(15) << stk::human_bytes(global_hwm[0]) + << std::endl; + + sierra::Env::outputP0() << " max (over all cores) current/high-water mark = " + << std::setw(15) << stk::human_bytes(global_now[1]) + << std::setw(15) << stk::human_bytes(global_hwm[1]) + << std::endl; + } +} + +} // namespace krino diff --git a/packages/krino/krino/region/Akri_Simulation.hpp b/packages/krino/krino/region/Akri_Simulation.hpp new file mode 100644 index 000000000000..62a0c300e12b --- /dev/null +++ b/packages/krino/krino/region/Akri_Simulation.hpp @@ -0,0 +1,68 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef Akri_Simulation_h +#define Akri_Simulation_h + +#include +#include +#include +#include +#include + +namespace krino{ + +class Region; + +class Simulation { +public: + static Simulation & build(const std::string & in_name); + Simulation(const std::string & in_name); // public to allow make_unique, but should be created using build() + ~Simulation(); + + void commit(); + void initialize() {} + void execute(); + + void add_region(Region * region); + + const std::string & get_name() const { return my_name; } + bool is_transient() const { return my_is_transient; } + double get_time_step() const { return my_time_step_size; } + double get_current_time() const { return my_current_time; } + double get_stop_time() const { return my_stop_time; } + int get_time_step_count() const { return my_step_count; } + + void set_time_step(const double in_time_step) { my_time_step_size = in_time_step; } + void set_current_time(const double in_time) { my_current_time = in_time; } + void set_stop_time(const double in_time) { my_is_transient = true; my_stop_time = in_time; } + + stk::diag::Timer & get_timer() const { return my_timer; } + void print_performance_info() const; + + static Simulation & get(); + static void reset() { the_simulation.reset(); } + +private: + static std::unique_ptr the_simulation; + +private: + std::string my_name; + mutable stk::diag::Timer my_timer; + bool my_is_transient; + double my_stop_time; + unsigned my_step_count; + double my_current_time; + double my_time_step_size; + std::vector> my_regions; +}; + +} // namespace krino + +#endif // Akri_Simulation_h + diff --git a/packages/krino/krino/region/Akri_Startup.cpp b/packages/krino/krino/region/Akri_Startup.cpp new file mode 100644 index 000000000000..2496ae35d651 --- /dev/null +++ b/packages/krino/krino/region/Akri_Startup.cpp @@ -0,0 +1,306 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino{ + +namespace { + +std::string +get_program_path(const char *program) +{ + // If we already have the full path, just return it + if (program[0] == '/') + return program; + + char full_path[PATH_MAX]; + if (strchr(program, '/') != nullptr) { + realpath(program, full_path); + return full_path; + } + + char *PATH = getenv("PATH"); + while (PATH && *PATH) { + // Get the character past the end of the next directory in PATH, i.e. + // either the '/' or the '\0' + char *end = strchr(PATH, ':'); + if (!end) { + end = PATH+strlen(PATH); + } + + // Set current = directory + '/' + program + strncpy(full_path, PATH, end-PATH); + full_path[end-PATH] = '/'; + strcpy(&full_path[end-PATH+1], program); + + // Check whether possible exists + if (access(full_path, X_OK) == 0) + return full_path; + + // Advance to the next directory + PATH = *end ? end+1 : end; + } + + // Not found; this shouldn't happen, but maybe the executable got deleted + // after it was invoked before we got here -- or we have some crazy + // parallel machine where the executable is inaccessible on the compute + // nodes despite it somehow having been loaded. No big deal, just return + // the non-absolute path. + return program; +} + +} // namespace + +Startup::Startup(int argc, char ** argv) + : my_flag_exit_early(false) +{ + if ( MPI_SUCCESS != MPI_Init( &argc , &argv ) ) { + throw std::runtime_error("MPI_Init failed"); + } + + stk::EnvData::instance().m_parallelComm = MPI_COMM_WORLD; + MPI_Comm_size(stk::EnvData::parallel_comm(), &stk::EnvData::instance().m_parallelSize); + MPI_Comm_rank(stk::EnvData::parallel_comm(), &stk::EnvData::instance().m_parallelRank); + + stk::register_message_type(stk::MSG_WARNING, 10000000, "Warning"); + stk::register_message_type(stk::MSG_DOOMED, 10000000, "Parser error"); + stk::register_message_type(stk::MSG_EXCEPTION, 1000000, "Exception"); + sierra::Diag::registerWriter("krinolog", krinolog, theDiagWriterParser()); + + // Register this so that -version will know this region is used. + register_product(); + + stk::set_report_handler(report_handler); + + setup_commandline_options(); + + // parse command line options + const stk::OptionsSpecification &desc = stk::get_options_specification(); + stk::ParsedOptions &parsedOptions = stk::get_parsed_options(); + stk::parse_command_line_args(argc, const_cast(argv), desc, parsedOptions); + + for (auto && writer : sierra::Diag::getWriterRegistry()) + { + if (parsedOptions.count(writer.first)) + { + writer.second.first->setPrintMask(writer.second.second->parse(parsedOptions[writer.first].as().c_str())); + } + } + + if ( parsedOptions.count("help") ) { + if (0 == stk::EnvData::parallel_rank()) + { + std::cerr << desc << std::endl; + } + my_flag_exit_early = true; + } + + if (parsedOptions.count("version")) { + if (0 == stk::EnvData::parallel_rank()) + { + std::cerr << "Version: Krino1.0" << std::endl; + } + my_flag_exit_early = true; + } + + int local_flag_exit_early = my_flag_exit_early; + MPI_Allreduce(&local_flag_exit_early, &my_flag_exit_early, 1, MPI_INT, MPI_MAX, stk::EnvData::parallel_comm()); + if (my_flag_exit_early) return; + + const std::string inputFileName = sierra::Env::get_param("input-deck"); + if (stk::EnvData::instance().m_inputFileRequired) + { + if(inputFileName == "") + { + throw std::runtime_error("No input file specified. An input file must be specified with the '-i' option"); + } + + stk::EnvData::setInputFileName(inputFileName); + } + + // setup logfile as inputFileName.log + std::string logFileName = parsedOptions["output-log"].as(); + if (logFileName == "") + { + int dotPos = inputFileName.rfind("."); + if ( -1 == dotPos ) { + // lacking extension + logFileName = inputFileName + ".log"; + } + else { + // with extension; swap with .log + logFileName = inputFileName.substr(0, dotPos) + ".log"; + } + } + + // set up output streams + const std::string output_description = + (0 == stk::EnvData::parallel_rank()) ? + "outfile=" + logFileName + " out>outfile+pout dout>out" : + "out>pout dout>out"; + std::string parallel_output_description = " pout>null"; + if (parsedOptions.count("pout")) + { + std::ostringstream s; + s << " poutfile=" << logFileName << "." << stk::EnvData::parallel_size() << "." << stk::EnvData::parallel_rank() << " pout>poutfile"; + parallel_output_description = s.str(); + } + stk::bind_output_streams(output_description+parallel_output_description); + stk::EnvData::instance().m_outputP0 = &sierra::out(); + + // output run info + const std::string program_path = get_program_path(argv[0]); + const char * build_date_time = __DATE__ " " __TIME__; + stk::EnvData::instance().m_executablePath = program_path; + + stk::EnvData::instance().m_productName = krino::get_product_name(); + stk::ProductRegistry::AttributeMap &product_attributes = stk::ProductRegistry::instance().getProductAttributeMap(sierra::Env::product_name()); + product_attributes[stk::ProductRegistry::BUILD_TIME] = build_date_time; + product_attributes[stk::ProductRegistry::EXECUTABLE] = program_path; + + sierra::Env::outputP0() + << sierra::Env::subsection_separator() + << std::endl + << "Executable Name = " << sierra::Env::product_name() << std::endl + << "Executable Version= " << stk::ProductRegistry::version() << std::endl + << "Executable Date = " << stk::ProductRegistry::instance().getProductAttribute(sierra::Env::product_name(), stk::ProductRegistry::BUILD_TIME) << std::endl + << "Executable File = " << sierra::Env::executable_file() << std::endl + << "Run Start Date = " << sierra::Env::startup_date() << std::endl << std::endl + << "Working Directory = " << sierra::Env::working_directory() << std::endl + << "Parsed Input File = " << sierra::Env::getInputFileName() << std::endl + << sierra::Env::subsection_separator() + << std::endl << std::endl ; +} + +void Startup::setup_commandline_options() +{ + stk::OptionsSpecification desc("Allowed options"); + desc.add_options() + ("help,h", "produce help message") + ("input-deck,i", "Analysis input file", stk::DefaultValue("")) + ("output-log,o", "Output log file path, one of : 'cout', 'cerr', or a file path", stk::DefaultValue("")) + ("aprepro,a", "Process (on) or don't process (off) input with aprepro. Default=on.", stk::ImplicitValue("on")) + ("define,D", "Define symbols for use in aprepro processing of input file", stk::ValueType()) + ("pout", "use separate output log file for each MPI process"); + + for (auto && writer : sierra::Diag::getWriterRegistry()) + { + std::ostringstream str; + str << "Diagnostic writer " << writer.first << std::endl; + writer.second.second->describe(str); + desc.add_options()(writer.first.c_str(), str.str().c_str(), stk::ValueType()); + } + + stk::get_options_specification().add(desc); +} + +Startup::~Startup() +{ + MPI_Finalize(); +} + +void Startup::handle_exception(const char * what, const bool is_parsing) +{ + krinolog << stk::diag::dendl; + krinolog << "Exception: " << what << stk::diag::dendl; + krinolog << stk::diag::Traceback::printTraceback(stk::diag::Traceback::snapshot()) << stk::diag::dendl; + if (is_parsing) + { + if (0 == stk::EnvData::parallel_rank()) + { + std::cerr << "*** Parser Error ***\n*** check log file for more information ***" << std::endl; + } + std::cout << std::flush; + std::cerr << std::flush; + + ::sleep(1); // Give the other processors a chance at + // catching up, seems to help hanging problems. + + MPI_Finalize(); + std::exit(1); + } + + if (0 == stk::EnvData::parallel_rank()) + { + std::cerr << "*** ABORT on P0 ***\n*** check log file for more information ***" << std::endl; + } + else + { + // give root proc chance to die first + ::sleep(2); + std::cerr << "*** ABORT on P" < +#include + +namespace krino{ + +class Startup +{ + public: + + Startup(int argc, char ** argv); + ~Startup(); + + bool exit_early() const { return my_flag_exit_early; } + void handle_exception(const char * what, const bool is_parsing); + + static void setup_commandline_options(); + static void report_handler(const char *message, int type); + +private: + int my_flag_exit_early; +}; + +} // namespace krino + +#endif // Akri_Startup_h diff --git a/packages/krino/krino/region/CMakeLists.txt b/packages/krino/krino/region/CMakeLists.txt new file mode 100644 index 000000000000..35b1a27d73b2 --- /dev/null +++ b/packages/krino/krino/region/CMakeLists.txt @@ -0,0 +1,19 @@ +SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + krino_region_lib + HEADERS ${HEADERS} + SOURCES ${SOURCES} + DEPLIBS krino_lib krino_adaptivity_interface_lib) + +INSTALL(FILES ${HEADERS} DESTINATION + ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/krino_region_lib) + diff --git a/packages/krino/krino/unit_tests/Akri_UnitMathUtils.cpp b/packages/krino/krino/unit_tests/Akri_UnitMathUtils.cpp new file mode 100644 index 000000000000..8df8c1a1f209 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_UnitMathUtils.cpp @@ -0,0 +1,64 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +namespace krino { + +void expect_root(const double goldRoot, const double xTol, const std::function & f) +{ + const unsigned maxIters = 100; + const auto result = find_root(f, 0., 1., f(0.), f(1.), maxIters, xTol); + ASSERT_TRUE(result.first); + EXPECT_NEAR(goldRoot, result.second, xTol); +} + +void expect_root_newton_raphson(const double goldRoot, const double guess, const double fTol, const std::function(const double)> & f) +{ + const unsigned maxIters = 100; + const auto result = find_root_newton_raphson(f, guess, maxIters, fTol); + ASSERT_TRUE(result.first); + const auto valueAndDeriv = f(result.second); + const double xTol = fTol / std::abs(valueAndDeriv.second); + EXPECT_NEAR(0., valueAndDeriv.first, fTol); + EXPECT_NEAR(goldRoot, result.second, xTol); +} + +TEST(find_root, givenPolynomialFunction_findRootWithinTolerance) +{ + const double tol = 1.e-5; + expect_root(0.25, tol, [](const double x){ return x-0.25; }); + expect_root(0.25, tol, [](const double x){ return x*x-0.25*0.25; }); + expect_root(0.25, tol, [](const double x){ return x*x*x-0.25*0.25*0.25; }); +} + + +TEST(find_root_newton_raphson, givenPolynomialFunctionWithCorrectJacobian_findRootWithinTolerance) +{ + const double tol = 1.e-5; + const double guess = 1.; + expect_root_newton_raphson(0.25, guess, tol, [](const double x){ std::cout << "Eval at " << x << std::endl; return std::make_pair(x-0.25, 1.); }); + expect_root_newton_raphson(0.25, guess, tol, [](const double x){ std::cout << "Eval at " << x << std::endl; return std::make_pair(x*x-0.25*0.25, 2.*x); }); + expect_root_newton_raphson(0.25, guess, tol, [](const double x){ std::cout << "Eval at " << x << std::endl; return std::make_pair(x*x*x-0.25*0.25*0.25, 3.*x*x); }); +} + +TEST(find_root_newton_raphson, givenPolynomialFunctionWithWRONGJacobian_findRootWithinTolerance) +{ + const double tol = 1.e-5; + const double guess = 1.; + const double error = 0.1; // Less than 1 to make function overshoot + expect_root_newton_raphson(0.25, guess, tol, [error](const double x){ std::cout << "Eval at " << x << std::endl; return std::make_pair(x-0.25, 1.*error); }); + expect_root_newton_raphson(0.25, guess, tol, [error](const double x){ std::cout << "Eval at " << x << std::endl; return std::make_pair(x*x-0.25*0.25, 2.*x*error); }); + expect_root_newton_raphson(0.25, guess, tol, [error](const double x){ std::cout << "Eval at " << x << std::endl; return std::make_pair(x*x*x-0.25*0.25*0.25, 3.*x*x*error); }); +} + +} + diff --git a/packages/krino/krino/unit_tests/Akri_UnitTestUtils.cpp b/packages/krino/krino/unit_tests/Akri_UnitTestUtils.cpp new file mode 100644 index 000000000000..fbb85740d585 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_UnitTestUtils.cpp @@ -0,0 +1,23 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +namespace krino { + +void expect_eq(const Vector3d & gold, const Vector3d & result, const double relativeTol=1.e-6) +{ + const double absoluteTol = relativeTol * (gold.length() + result.length()); + for (int i=0; i<3; ++i) + EXPECT_NEAR(gold[i], result[i], absoluteTol) <<"gold: " << gold << " actual:" << result; +} + +} + + diff --git a/packages/krino/krino/unit_tests/Akri_UnitTestUtils.hpp b/packages/krino/krino/unit_tests/Akri_UnitTestUtils.hpp new file mode 100644 index 000000000000..ebb0e3b7dbe0 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_UnitTestUtils.hpp @@ -0,0 +1,19 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_UNIT_TESTS_INCLUDE_AKRI_UNITTESTUTILS_H_ +#define KRINO_UNIT_TESTS_INCLUDE_AKRI_UNITTESTUTILS_H_ +#include + +namespace krino { + +void expect_eq(const Vector3d & gold, const Vector3d & result, const double relativeTol=1.e-6); + +} + +#endif /* KRINO_UNIT_TESTS_INCLUDE_AKRI_UNITTESTUTILS_H_ */ diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Analytic_CDMesh.cpp b/packages/krino/krino/unit_tests/Akri_Unit_Analytic_CDMesh.cpp new file mode 100644 index 000000000000..59ac23793422 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Analytic_CDMesh.cpp @@ -0,0 +1,276 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +static void expect_good_mesh(const stk::mesh::BulkData & mesh, const CDMesh & cdmesh) +{ + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(cdmesh.check_element_side_parts()); +} + +template +class AnalyticDecompositionFixture : public ::testing::Test +{ +public: + AnalyticDecompositionFixture() + : fixture(), cdfemSupport(CDFEM_Support::get(fixture.meta_data())) + { + AuxMetaData & aux_meta = AuxMetaData::get(fixture.meta_data()); + auto & vec_type = fixture.meta_data().spatial_dimension() == 2 ? FieldType::VECTOR_2D : FieldType::VECTOR_3D; + coord_field = aux_meta.register_field("coordinates", vec_type, stk::topology::NODE_RANK, 1u, 1u, fixture.meta_data().universal_part()); + cdfemSupport.set_coords_field(coord_field); + cdfemSupport.add_interpolation_field(coord_field); + + cdfemSupport.set_prolongation_model(INTERPOLATION); + } + + void setup_phase_support(const stk::mesh::PartVector & blocks) + { + PhaseTag p, n; + const LevelSet_Identifier id0(0); + p.add(id0, 1); + n.add(id0, -1); + PhaseVec named_phases{{"A", p}, {"B", n}}; + + Phase_Support & phase_support = Phase_Support::get(fixture.meta_data()); + Block_Surface_Connectivity block_surface_info; + phase_support.set_input_block_surface_connectivity(block_surface_info); + LevelSet * ls_ptr = nullptr; + phase_support.register_blocks_for_level_set(ls_ptr, blocks); + phase_support.decompose_blocks(blocks, named_phases, LS_Name_Generator()); + LS_Field lsField("LS", id0); + cdfemSupport.add_ls_field(lsField, nullptr); + } + + stk::mesh::Part & declare_input_block(const std::string & name, const stk::topology topo) + { + auto & block_part = fixture.meta_data().declare_part_with_topology(name, topo); + stk::io::put_io_part_attribute(block_part); + return block_part; + } + + void decompose_mesh(const InterfaceGeometry & interfaceGeometry) + { + NodeToCapturedDomainsMap nodesToCapturedDomains; + if (cdfemSupport.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + nodesToCapturedDomains = snap_as_much_as_possible_while_maintaining_quality(krino_mesh->stk_bulk(), krino_mesh->get_active_part(), cdfemSupport.get_interpolation_fields(), interfaceGeometry, cdfemSupport.get_global_ids_are_parallel_consistent()); + interfaceGeometry.prepare_to_process_elements(krino_mesh->stk_bulk(), nodesToCapturedDomains); + + if(!krino_mesh->my_old_mesh) + { + krino_mesh->my_old_mesh = std::make_shared(fixture.bulk_data(), std::shared_ptr()); + krino_mesh->my_old_mesh->generate_nonconformal_elements(); + } + + krino_mesh->generate_nonconformal_elements(); + if (cdfemSupport.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + krino_mesh->snap_nearby_intersections_to_nodes(interfaceGeometry, nodesToCapturedDomains); + krino_mesh->set_phase_of_uncut_elements(interfaceGeometry); + krino_mesh->triangulate(interfaceGeometry); + krino_mesh->my_old_mesh->stash_field_data(-1, *krino_mesh); + + + krino_mesh->decompose(); + krino_mesh->modify_mesh(); + krino_mesh->prolongation(); + } + + void commit() + { + krino_mesh = std::make_shared(fixture.bulk_data(), std::shared_ptr()); + } + + void write_results(const std::string & filename) + { + stk::io::write_mesh(filename, fixture.bulk_data()); + } + + void test_build_good_mesh(const bool doSnapping, + const InterfaceGeometry & interfaceGeometry, + const typename BoundingBoxMesh::BoundingBoxType & domain, + const double meshSize, + const std::string & filename = "") + { + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + if (parallel_size > 2) return; + + if (doSnapping) + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + else + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_NODE); + + auto & block1_part = aux_meta.get_part("block_1"); + setup_phase_support({&block1_part}); + + fixture.set_domain(domain, meshSize); + fixture.populate_mesh(); + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part(),&aux_meta.active_part()}); + + commit(); + + try + { + decompose_mesh(interfaceGeometry); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + expect_good_mesh(mesh, *krino_mesh); + + std::cout << log.get_log() << std::endl; + if (!filename.empty()) + write_results(filename); + } + + MESH_FIXTURE fixture; + FieldRef coord_field; + CDFEM_Support & cdfemSupport; + std::shared_ptr krino_mesh; + LogRedirecter log; +}; + +template +class SphereDecompositionFixture : public AnalyticDecompositionFixture +{ +public: + SphereDecompositionFixture() + { + mySphereGeometry.reset(new AnalyticSurfaceInterfaceGeometry(mySphere, AuxMetaData::get(this->fixture.meta_data()).active_part(), this->cdfemSupport, Phase_Support::get(this->fixture.meta_data()))); + } +protected: + const InterfaceGeometry & get_interface_geometry() const { return *mySphereGeometry; } + typename BoundingBoxMesh::BoundingBoxType get_domain() const + { + const Vector3d extents = (2 == this->fixture.meta_data().spatial_dimension()) ? Vector3d(1.,1.,0.) : Vector3d(1.,1.,1.); + typename BoundingBoxMesh::BoundingBoxType domain(-0.5*extents, 0.5*extents); + return domain; + } + double get_mesh_size() const { return 1./6.; } + Sphere mySphere{"test sphere", Vector3d::ZERO, 0.35}; + std::unique_ptr mySphereGeometry; +}; + +typedef SphereDecompositionFixture CDMeshSphereTestsBboxMesh2D; +TEST_F(CDMeshSphereTestsBboxMesh2D, Sphere_SnapMesh) +{ + const bool doSnap = true; + test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size()); //test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size(), "2DSnapSphere.e"); +} + +TEST_F(CDMeshSphereTestsBboxMesh2D, Sphere_CutMesh) +{ + const bool doSnap = false; + test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size()); //test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size(), "2DCutSphere.e"); +} + +typedef SphereDecompositionFixture CDMeshSphereTestsBboxMesh3D; +TEST_F(CDMeshSphereTestsBboxMesh3D, Sphere_SnapMesh) +{ + const bool doSnap = true; + test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size()); //test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size(), "3DSnapSphere.e"); +} + +TEST_F(CDMeshSphereTestsBboxMesh3D, Sphere_CutMesh) +{ + const bool doSnap = false; + test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size()); //test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size(), "3DCutSphere.e"); +} + +template +class CubeDecompositionFixture : public AnalyticDecompositionFixture +{ +public: + CubeDecompositionFixture() + { + const double x = 0.25; + const std::array cubeVerts = + {{ + {-x,-x,-x}, {+x,-x,-x}, {+x,+x,-x}, {-x,+x,-x}, + {-x,-x,+x}, {+x,-x,+x}, {+x,+x,+x}, {-x,+x,+x} + }}; + + const std::array,12> facetsVerts + {{ + {{0,4,7}}, {{0,7,3}}, + {{1,2,6}}, {{1,6,5}}, + {{0,1,5}}, {{0,5,4}}, + {{2,3,7}}, {{2,7,6}}, + {{0,3,2}}, {{0,2,1}}, + {{4,5,6}}, {{4,6,7}} + }}; + for (auto && facetVerts : facetsVerts) + { + std::unique_ptr facet = std::make_unique( cubeVerts[facetVerts[0]], cubeVerts[facetVerts[1]], cubeVerts[facetVerts[2]] ); + myCube.add( std::move(facet) ); + } + + myCubeGeometry.reset(new AnalyticSurfaceInterfaceGeometry(myCube, AuxMetaData::get(this->fixture.meta_data()).active_part(), this->cdfemSupport, Phase_Support::get(this->fixture.meta_data()))); + } +protected: + const InterfaceGeometry & get_interface_geometry() const { return *myCubeGeometry; } + typename BoundingBoxMesh::BoundingBoxType get_domain() const + { + const Vector3d extents = (2 == this->fixture.meta_data().spatial_dimension()) ? Vector3d(1.,1.,0.) : Vector3d(1.,1.,1.); + typename BoundingBoxMesh::BoundingBoxType domain(-0.5*extents, 0.5*extents); + return domain; + } + double get_mesh_size() const { return 1./6.; } + Faceted_Surface myCube{"test cube"}; + std::unique_ptr myCubeGeometry; +}; + +typedef CubeDecompositionFixture CDMeshCubeTestsBboxMesh2D; +TEST_F(CDMeshCubeTestsBboxMesh2D, Cube_SnapMesh) +{ + const bool doSnap = true; + test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size()); //test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size(), "2DSnapCube.e"); +} + +typedef CubeDecompositionFixture CDMeshCubeTestsBboxMesh3D; +TEST_F(CDMeshCubeTestsBboxMesh3D, Sphere_SnapMesh) +{ + const bool doSnap = true; + test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size()); //test_build_good_mesh(doSnap, get_interface_geometry(), get_domain(), get_mesh_size(), "3DSnapCube.e"); +} + +} // namespace krino + diff --git a/packages/krino/krino/unit_tests/Akri_Unit_CDFEM_Parent_Edge.cpp b/packages/krino/krino/unit_tests/Akri_Unit_CDFEM_Parent_Edge.cpp new file mode 100644 index 000000000000..5b912bda3b65 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_CDFEM_Parent_Edge.cpp @@ -0,0 +1,782 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include + +namespace krino +{ + +TEST(CDFEM_Parent_Edge_One_LS, Two_Nodes_No_Snapping) +{ + const InterfaceID iface(0,0); + std::vector > nodes_isovar(2); + nodes_isovar[0].resize(1); + nodes_isovar[1].resize(1); + nodes_isovar[0][0] = 1; + nodes_isovar[1][0] = 1; + + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); + + nodes_isovar[0][0] = -1; + nodes_isovar[1][0] = -1; + edge.find_crossings(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(-1, edge.get_crossing_sign(iface)); + + nodes_isovar[0][0] = -1; + nodes_isovar[1][0] = 2; + edge.find_crossings(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_DOUBLE_EQ(1./3., edge.get_crossing_position(iface)); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); +} + +TEST(CDFEM_Parent_Edge_One_LS, Three_Nodes_No_Snapping) +{ + std::vector > nodes_isovar(3); + nodes_isovar[0].resize(1); + nodes_isovar[1].resize(1); + nodes_isovar[2].resize(1); + nodes_isovar[0][0] = 1; + nodes_isovar[1][0] = 1; + nodes_isovar[2][0] = 1; + const InterfaceID iface(0,0); + + CDFEM_Parent_Edge edge({0.0,0.5,1.0}, nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); + + nodes_isovar[0][0] = -1; + nodes_isovar[1][0] = -1; + nodes_isovar[2][0] = -1; + edge.find_crossings(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(-1, edge.get_crossing_sign(iface)); + + nodes_isovar[0][0] = -1; + nodes_isovar[1][0] = 1; + nodes_isovar[2][0] = -1; + edge.find_crossings(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(-1, edge.get_crossing_sign(iface)); + + nodes_isovar[0][0] = 1; + nodes_isovar[1][0] = -1; + nodes_isovar[2][0] = 1; + edge.find_crossings(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); + + nodes_isovar[0][0] = -1; + nodes_isovar[1][0] = 2; + nodes_isovar[2][0] = 4; + edge.find_crossings(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + const InterfaceID interface_id(0,0); + EXPECT_TRUE(edge.have_crossing(interface_id)); + EXPECT_DOUBLE_EQ(1./6., edge.get_crossing_position(interface_id)); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); + + nodes_isovar[0][0] = -1; + nodes_isovar[1][0] = -1; + nodes_isovar[2][0] = 1; + edge.find_crossings(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(interface_id)); + EXPECT_DOUBLE_EQ(3./4., edge.get_crossing_position(interface_id)); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); +} + +TEST(CDFEM_Parent_Edge_Two_LS, Two_Nodes_No_Snapping) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const InterfaceID iface(0,1); + + std::vector > nodes_isovar(2); + nodes_isovar[0].resize(2); + nodes_isovar[1].resize(2); + + // crossing_sign convention, iface.first phase corresponds to (-) for one LS=one interface case + // iface.first lower everywhere + { + nodes_isovar[0][0] = 0; + nodes_isovar[0][1] = 1; + nodes_isovar[1][0] = 0; + nodes_isovar[1][1] = 1; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(0, edge.get_uncrossed_phase()); + } + + // iface.second lower everywhere + { + nodes_isovar[0][0] = 2; + nodes_isovar[0][1] = 1; + nodes_isovar[1][0] = 2; + nodes_isovar[1][1] = 1; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_EQ(1, edge.get_uncrossed_phase()); + } + + // first lower at node 0, second lower at node 1 + { + nodes_isovar[0][0] = 0.5; + nodes_isovar[0][1] = 1; + nodes_isovar[1][0] = 1.5; + nodes_isovar[1][1] = 1; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_DOUBLE_EQ(0.5, edge.get_crossing_position(iface)); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); + } + + // second lower at node 0, first lower at node 1 + { + nodes_isovar[0][0] = 1.5; + nodes_isovar[0][1] = 1; + nodes_isovar[1][0] = 0.5; + nodes_isovar[1][1] = 1; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_DOUBLE_EQ(0.5, edge.get_crossing_position(iface)); + EXPECT_EQ(-1, edge.get_crossing_sign(iface)); + } + + // Equal at node 0, first lower at node 1 + { + nodes_isovar[0][0] = 0.; + nodes_isovar[0][1] = 0.; + nodes_isovar[1][0] = 0; + nodes_isovar[1][1] = 1.; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_DOUBLE_EQ(0., edge.get_crossing_position(iface)); + EXPECT_EQ(-1, edge.get_crossing_sign(iface)); + } + + // Equal at node 0, second lower at node 1 + { + nodes_isovar[0][0] = 0.; + nodes_isovar[0][1] = 0.; + nodes_isovar[1][0] = 0; + nodes_isovar[1][1] = -1.; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface)); + } + // Equal at node 0, first lower at node 1 + { + nodes_isovar[0][0] = 0.; + nodes_isovar[0][1] = 1.; + nodes_isovar[1][0] = 0; + nodes_isovar[1][1] = 0.; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_DOUBLE_EQ(1., edge.get_crossing_position(iface)); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); + } + + // Equal at node 1, second lower at node 0 + { + nodes_isovar[0][0] = 0.; + nodes_isovar[0][1] = -1.; + nodes_isovar[1][0] = 0.; + nodes_isovar[1][1] = 0.; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface)); + } + + // (0, +delta) at node 0, (0, -) at node 1 + { + nodes_isovar[0][0] = 0.; + nodes_isovar[0][1] = 1.e-16; + nodes_isovar[1][0] = 0.; + nodes_isovar[1][1] = -1.; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_NEAR(0., edge.get_crossing_position(iface), 1.e-15); + EXPECT_EQ(1, edge.get_crossing_sign(iface)); + } + + // (0, -) at node 0, (0, +delta) at node 1 + { + nodes_isovar[0][0] = 0.; + nodes_isovar[0][1] = -1.; + nodes_isovar[1][0] = 0.; + nodes_isovar[1][1] = 1.e-16; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_NEAR(1., edge.get_crossing_position(iface), 1.e-15); + EXPECT_EQ(-1, edge.get_crossing_sign(iface)); + } +} + +TEST(CDFEM_Parent_Edge_Three_LS, Two_Nodes_No_Snapping) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const InterfaceID iface01(0,1); + const InterfaceID iface02(0,2); + const InterfaceID iface12(1,2); + + std::vector > nodes_isovar(2); + nodes_isovar[0].resize(3); + nodes_isovar[1].resize(3); + + { + nodes_isovar[0][0] = -0.01288; + nodes_isovar[0][1] = 0.021996; + nodes_isovar[0][2] = 0.01; + nodes_isovar[1][0] = 0.038335; + nodes_isovar[1][1] = 0.037250; + nodes_isovar[1][2] = 0.01; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface01)); + EXPECT_FALSE(edge.have_crossing(iface12)); + EXPECT_TRUE(edge.have_crossing(iface02)); + const double crossing_pos = (nodes_isovar[0][0] - nodes_isovar[0][2]) / + (nodes_isovar[0][0] - nodes_isovar[1][0] + nodes_isovar[1][2] - nodes_isovar[0][2]); + EXPECT_DOUBLE_EQ(crossing_pos, edge.get_crossing_position(iface02)); + EXPECT_EQ(1, edge.get_crossing_sign(iface02)); + } + + { + nodes_isovar[0][0] = 0.038335; + nodes_isovar[0][1] = 0.037250; + nodes_isovar[0][2] = 0.01; + nodes_isovar[1][0] = -0.01288; + nodes_isovar[1][1] = 0.021996; + nodes_isovar[1][2] = 0.01; + CDFEM_Parent_Edge edge(nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface01)); + EXPECT_FALSE(edge.have_crossing(iface12)); + EXPECT_TRUE(edge.have_crossing(iface02)); + const double crossing_pos = (nodes_isovar[0][0] - nodes_isovar[0][2]) / + (nodes_isovar[0][0] - nodes_isovar[1][0] + nodes_isovar[1][2] - nodes_isovar[0][2]); + EXPECT_DOUBLE_EQ(crossing_pos, edge.get_crossing_position(iface02)); + EXPECT_EQ(-1, edge.get_crossing_sign(iface02)); + } +} + +TEST(CDFEM_Parent_Edge_Three_LS, Three_Nodes_No_Snapping) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const InterfaceID iface01(0,1); + const InterfaceID iface02(0,2); + const InterfaceID iface12(1,2); + + std::vector > nodes_isovar(3); + nodes_isovar[0].resize(3); + nodes_isovar[1].resize(3); + nodes_isovar[2].resize(3); + + { + nodes_isovar[0][0] = 0.2; + nodes_isovar[0][1] = 0.0025391738062501383; + nodes_isovar[0][2] = 0.01; + nodes_isovar[1][0] = 0.19; + nodes_isovar[1][1] = -0.00048874386459929649; + nodes_isovar[1][2] = 0.01; + nodes_isovar[2][0] = 0.17; + nodes_isovar[2][1] = -0.0052539980592431739; + nodes_isovar[2][2] = 0.01; + CDFEM_Parent_Edge edge({0.0,0.5,1.0}, nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface01)); + EXPECT_FALSE(edge.have_crossing(iface12)); + EXPECT_FALSE(edge.have_crossing(iface02)); + } + + // Check that uncrossed phase is correct for 3 node problem with 2 crossings on the edge. + { + nodes_isovar[0][0] = 0.015; + nodes_isovar[0][1] = 1.; + nodes_isovar[0][2] = 0.01; + nodes_isovar[1][0] = 0.0095; + nodes_isovar[1][1] = 1.; + nodes_isovar[1][2] = 0.01; + nodes_isovar[2][0] = 0.015; + nodes_isovar[2][1] = 1.; + nodes_isovar[2][2] = 0.01; + CDFEM_Parent_Edge edge({0.0,0.5,1.0}, nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface01)); + EXPECT_FALSE(edge.have_crossing(iface12)); + EXPECT_FALSE(edge.have_crossing(iface02)); + EXPECT_EQ(2, edge.get_uncrossed_phase()); + } + + // Test edge that goes from phase 1-0-2 in the piecewise approximation, but there is no 0-2 in the linear version, + // so result is just 1-2. + { + nodes_isovar[0][0] = 0.72535; + nodes_isovar[0][1] = -0.844886; + nodes_isovar[0][2] = 0.10576; + nodes_isovar[1][0] = -0.58386; + nodes_isovar[1][1] = -0.931365; + nodes_isovar[1][2] = 0.7754522; + nodes_isovar[2][0] = -0.28731; + nodes_isovar[2][1] = 0.711750; + nodes_isovar[2][2] = -0.5794; + CDFEM_Parent_Edge edge({0.0,0.5,1.0}, nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface01)); + EXPECT_TRUE(edge.have_crossing(iface12)); + EXPECT_FALSE(edge.have_crossing(iface02)); + } + + // Test edge that goes from phase 1-0-2 in the piecewise approximation, with different locations than those + // given by a simple linear approximation. + { + nodes_isovar[0][0] = 0.25; + nodes_isovar[0][1] = 1.0; + nodes_isovar[0][2] = 0.0; + nodes_isovar[1][0] = 0.25; + nodes_isovar[1][1] = 1.0; + nodes_isovar[1][2] = 0.0; + nodes_isovar[2][0] = 0.25; + nodes_isovar[2][1] = 0.0; + nodes_isovar[2][2] = 1.0; + CDFEM_Parent_Edge edge({0.0,0.5,1.0}, nodes_isovar); + EXPECT_TRUE(edge.have_any_crossings()); + EXPECT_TRUE(edge.have_crossing(iface01)); + EXPECT_FALSE(edge.have_crossing(iface12)); + EXPECT_TRUE(edge.have_crossing(iface02)); + EXPECT_DOUBLE_EQ(0.625, edge.get_crossing_position(iface02)); + EXPECT_DOUBLE_EQ(0.875, edge.get_crossing_position(iface01)); + } + +} + +TEST(CDFEM_Parent_Edge_Two_LS, Two_Crossings_Same_Edge) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const InterfaceID iface01(0,1); + + std::vector > nodes_isovar(3); + nodes_isovar[0].resize(2); + nodes_isovar[1].resize(2); + nodes_isovar[2].resize(2); + + // Interface (0,1) has 2 crossings, one between each parent and the mid node. + // We will treat this as an uncrossed edge regardless of snapping. + nodes_isovar[0][0] = 0.02; + nodes_isovar[0][1] = 0.01; + nodes_isovar[1][0] = 0.; + nodes_isovar[1][1] = 0.01; + nodes_isovar[2][0] = 0.02; + nodes_isovar[2][1] = 0.01; + + // No snapping + { + CDFEM_Parent_Edge edge({0.0,0.5,1.0}, nodes_isovar); + EXPECT_FALSE(edge.have_any_crossings()); + EXPECT_FALSE(edge.have_crossing(iface01)); + } +} + +void expect_all_edge_segments_have_finite_or_zero_length(const CDFEM_Parent_Edge & edge, const double snapTol) +{ + std::vector crossingLocations; + for (auto && crossing : edge.get_crossings()) + { + crossingLocations.push_back(crossing.second); + } + std::sort(crossingLocations.begin(), crossingLocations.end()); + double previousCrossingLocation = 0.0; + for (auto && crossingLocation : crossingLocations) + { + const double intervalSize = crossingLocation- previousCrossingLocation; + EXPECT_TRUE(intervalSize == 0.0 || intervalSize >= snapTol) << "Found infinitesmal interval " << intervalSize << " on edge " << edge; + previousCrossingLocation = crossingLocation; + } + const double lastIntervalSize = 1.0 - previousCrossingLocation; + EXPECT_TRUE(lastIntervalSize == 0.0 || lastIntervalSize >= snapTol) << "Found infinitesmal interval " << lastIntervalSize << " on edge " << edge; +} + +TEST(CDFEM_Parent_Edge_Two_LS, LSPerPhaseInfinitesimalSnapTo0) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{-CDFEM_Parent_Edge::MinSize(),0.0},{1.0,0.0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + EXPECT_TRUE(edge.have_any_crossings()); + + expect_all_edge_segments_have_finite_or_zero_length(edge, CDFEM_Parent_Edge::MinSize()); +} + +TEST(CDFEM_Parent_Edge_Two_LS, LSPerPhaseInfinitesimalSnapTo1) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{1.0,0.0},{-CDFEM_Parent_Edge::MinSize(),0.0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + EXPECT_TRUE(edge.have_any_crossings()); + + expect_all_edge_segments_have_finite_or_zero_length(edge, CDFEM_Parent_Edge::MinSize()); +} + +void expect_all_fake_crossings_are_really_fake(const CDFEM_Parent_Edge & edge) +{ + EXPECT_TRUE(edge.all_fake_crossings_are_really_fake()); +} + +TEST(CDFEM_Parent_Edge_Three_LS, LSPerPhaseInfinitesimalSnapInMiddle) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{-1.0,1.+CDFEM_Parent_Edge::MinSize(),0.0},{1.+CDFEM_Parent_Edge::MinSize(),-1.0,0.0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + EXPECT_EQ(1u,edge.get_crossings().size()); + + expect_all_edge_segments_have_finite_or_zero_length(edge, CDFEM_Parent_Edge::MinSize()); + expect_all_fake_crossings_are_really_fake(edge); +} + +TEST(CDFEM_Parent_Edge_Three_LS, LSPerPhaseTieInMiddle) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{0.,-1.,1.},{0.,1.,-1.}}; + CDFEM_Parent_Edge edge(nodesIsovar); + EXPECT_EQ(1u,edge.get_crossings().size()); + + expect_all_edge_segments_have_finite_or_zero_length(edge, CDFEM_Parent_Edge::MinSize()); + expect_all_fake_crossings_are_really_fake(edge); +} + +TEST(CDFEM_Parent_Edge_Three_LS, UnderflowAtEndStillResultsInCorrectFakeCrossing) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{ -0.6, -0.6, 0.6 },{ -6e-16, -5.9e-16, -7e-16 }}; + CDFEM_Parent_Edge edge(nodesIsovar); + + expect_all_edge_segments_have_finite_or_zero_length(edge, CDFEM_Parent_Edge::MinSize()); + expect_all_fake_crossings_are_really_fake(edge); +} + +TEST(CDFEM_Parent_Edge_Three_LS, WithScaling_UnderflowAtEndStillResultsInCorrectFakeCrossing) +{ + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{ -0.6e8, -0.6e8, 0.6e8 },{ -6e-8, -5.9e-8, -7e-8 }}; + CDFEM_Parent_Edge edge(nodesIsovar); + + expect_all_edge_segments_have_finite_or_zero_length(edge, CDFEM_Parent_Edge::MinSize()); + expect_all_fake_crossings_are_really_fake(edge); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +TEST(CDFEM_Parent_Edge_Two_LS, SnapTo0) +{ + const double snapTol = 0.1; + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{-0.01,0.0},{1.0,0.0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + EXPECT_TRUE(edge.have_any_crossings()); + + edge.collapse_small_segments_while_preserving_topology(snapTol); + expect_all_edge_segments_have_finite_or_zero_length(edge, snapTol); +} + +TEST(CDFEM_Parent_Edge_Two_LS, SnapTo1) +{ + const double snapTol = 0.1; + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{1.0,0.0},{-0.01,0.0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.collapse_small_segments_while_preserving_topology(snapTol); + expect_all_edge_segments_have_finite_or_zero_length(edge, snapTol); +} + +TEST(CDFEM_Parent_Edge_Three_LS, SnapInMiddle) +{ + const double snapTol = 0.1; + Phase_Support::set_one_levelset_per_phase(true); + + const std::vector > nodesIsovar = {{-1.0,1.1,0.0},{1.1,-1.0,0.0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.collapse_small_segments_while_preserving_topology(snapTol); + expect_all_edge_segments_have_finite_or_zero_length(edge, snapTol); +} + +TEST(CDFEM_Parent_Edge_Three_LS, OneSnapInMiddleMakesOtherSnapUnnecessary) +{ + const double snapTol = 0.1; + Phase_Support::set_one_levelset_per_phase(true); + std::vector locs = {0.4,0.44,0.53}; + const double phi0At0 = -1.0; + const double phi0At1 = (0.-(1.-locs[0])*phi0At0)/locs[0]; + const double phi1At1 = -0.1; + const double phi1At0 = (0.-locs[1]*phi1At1)/(1.-locs[1]); + const double phi1AtLocs2 = (1.-locs[2])*phi1At0 + locs[2]*phi1At1; + const double phi2At1 = -1.0; + const double phi2At0 = (phi1AtLocs2-locs[2]*phi2At1)/(1.-locs[2]); + + const std::vector > nodesIsovar = {{phi0At0, phi1At0, phi2At0, 0.0}, {phi0At1, phi1At1, phi2At1, 0.0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.collapse_small_segments_while_preserving_topology(snapTol); + expect_all_edge_segments_have_finite_or_zero_length(edge, snapTol); + + const std::vector > oppositeNodesIsovar = {{phi0At1, phi1At1, phi2At1, 0.0}, {phi0At0, phi1At0, phi2At0, 0.0}}; + CDFEM_Parent_Edge oppositeEdge(oppositeNodesIsovar); + + oppositeEdge.collapse_small_segments_while_preserving_topology(snapTol); + expect_all_edge_segments_have_finite_or_zero_length(oppositeEdge, snapTol); + + for (auto && crossing : edge.get_crossings()) + EXPECT_DOUBLE_EQ(crossing.second, 1.-oppositeEdge.get_crossing_position(crossing.first)) << "Snapping opposite edges give different results " << edge << " compared to " << oppositeEdge; +} + +void expect_crossing_at_position_with_sign(const CDFEM_Parent_Edge & edge, const InterfaceID & iface, const double pos, const int sign) +{ + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_EQ(pos, edge.get_crossing_position(iface)); + EXPECT_EQ(sign, edge.get_crossing_sign(iface)); +} + +void expect_crossing_at_position_with_sign(const CDFEM_Parent_Edge & edge, const int ls, const double pos, const int sign) +{ + const InterfaceID iface(ls,ls); + EXPECT_TRUE(edge.have_crossing(iface)); + EXPECT_EQ(pos, edge.get_crossing_position(iface)); + EXPECT_EQ(sign, edge.get_crossing_sign(iface)); +} + +void expect_fake_crossing_at_position_with_sign(const CDFEM_Parent_Edge & edge, const InterfaceID & iface, const double pos, const int sign) +{ + const auto result = edge.get_crossing_position_and_sign(iface); + EXPECT_LE(0, std::get<0>(result)) << "Did not find expected fake crossing " << iface << " on edge " << edge; + EXPECT_EQ(pos, std::get<0>(result)); + EXPECT_EQ(sign, std::get<1>(result)); + EXPECT_TRUE(std::get<2>(result)) << "Found real crossing when fake one was expected for " << iface << " on edge " << edge; +} + +TEST(CDFEM_Parent_Edge_Snapping_One_LS, nodeDomainsAt0MovesNegCrossingTo0) +{ + Phase_Support::set_one_levelset_per_phase(false); + const std::vector > nodesIsovar = {{1}, {-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0}, {}); + + expect_crossing_at_position_with_sign(edge, 0, 0., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_One_LS, nodeDomainsAt0MovesPosCrossingTo0) +{ + Phase_Support::set_one_levelset_per_phase(false); + const std::vector > nodesIsovar = {{-1}, {1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0}, {}); + + expect_crossing_at_position_with_sign(edge, 0, 0., 1); +} + +TEST(CDFEM_Parent_Edge_Snapping_One_LS, nodeDomainsAt1MovesNegCrossingTo1) +{ + Phase_Support::set_one_levelset_per_phase(false); + const std::vector > nodesIsovar = {{1}, {-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({}, {0}); + + expect_crossing_at_position_with_sign(edge, 0, 1., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_One_LS, nodeDomainsAt1MovesPosCrossingTo1) +{ + Phase_Support::set_one_levelset_per_phase(false); + const std::vector > nodesIsovar = {{-1}, {1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({}, {0}); + + expect_crossing_at_position_with_sign(edge, 0, 1., 1); +} + +TEST(CDFEM_Parent_Edge_Snapping_One_LS, nodeDomainsAtBothEndsMovesNegCrossingToClosestAt0) +{ + Phase_Support::set_one_levelset_per_phase(false); + const std::vector > nodesIsovar = {{0.9}, {-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0}, {0}); + + expect_crossing_at_position_with_sign(edge, 0, 0., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_One_LS, nodeDomainsAtBothEndsMovesNegCrossingToClosestAt1) +{ + Phase_Support::set_one_levelset_per_phase(false); + const std::vector > nodesIsovar = {{1}, {-0.9}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0}, {0}); + + expect_crossing_at_position_with_sign(edge, 0, 1., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_One_LS, nodeDomainsAtBothEndsMovesPosCrossingToClosestAt0) +{ + Phase_Support::set_one_levelset_per_phase(false); + const std::vector > nodesIsovar = {{-0.9}, {1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0}, {0}); + + expect_crossing_at_position_with_sign(edge, 0, 0., 1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Two_LS_Per_Phase, nodeDomainsAt0MovesNegCrossingTo0) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{1,-1}, {-1,1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,1}, {}); + + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 0., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Two_LS_Per_Phase, nodeDomainsAt0MovesPosCrossingTo0) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{-1,1}, {1,-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,1}, {}); + + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 0., 1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Two_LS_Per_Phase, nodeDomainsAt1MovesNegCrossingTo1) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{1,-1}, {-1,1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({}, {0,1}); + + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 1., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Two_LS_Per_Phase, nodeDomainsAt1MovesPosCrossingTo1) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{-1,1}, {1,-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({}, {0,1}); + + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 1., 1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Two_LS_Per_Phase, nodeDomainsAtBothEndsMovesNegCrossingToClosestAt0) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{0.9,-0.9}, {-1,1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,1}, {0,1}); + + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 0., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Two_LS_Per_Phase, nodeDomainsAtBothEndsMovesNegCrossingToClosestAt1) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{1,-1}, {-0.9,0.9}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,1}, {0,1}); + + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 1., -1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Two_LS_Per_Phase, nodeDomainsAtBothEndsMovesPosCrossingToClosestAt0) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{-0.9,0.9}, {1,-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,1}, {0,1}); + + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 0., 1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Three_LS_Per_Phase, nodeDomainsMoveAllCrossingsTo0) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{-1,0,2}, {2,0,-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,1,2}, {}); + + expect_fake_crossing_at_position_with_sign(edge, InterfaceID(0,1), 0., 1); + expect_fake_crossing_at_position_with_sign(edge, InterfaceID(1,2), 0., 1); + expect_crossing_at_position_with_sign(edge, InterfaceID(0,2), 0., 1); +} + +TEST(CDFEM_Parent_Edge_Snapping_Three_LS_Per_Phase, nodeDomainsWithMiddlePhase2MoveAllCrossingsTo0) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{-1,2,0}, {2,-1,0}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,1,2}, {}); + + expect_fake_crossing_at_position_with_sign(edge, InterfaceID(0,2), 0., 1); + expect_fake_crossing_at_position_with_sign(edge, InterfaceID(1,2), 0., -1); + expect_crossing_at_position_with_sign(edge, InterfaceID(0,1), 0., 1); +} + + +TEST(CDFEM_Parent_Edge_Snapping_Three_LS_Per_Phase, nodeDomainsCenterPhaseMoveAllCrossingsTo0) +{ + Phase_Support::set_one_levelset_per_phase(true); + const std::vector > nodesIsovar = {{-1,0,2}, {2,0,-1}}; + CDFEM_Parent_Edge edge(nodesIsovar); + + edge.adjust_crossing_locations_based_on_node_captured_domains({0,2}, {}); + + expect_fake_crossing_at_position_with_sign(edge, InterfaceID(0,1), 0., 1); + expect_crossing_at_position_with_sign(edge, InterfaceID(0,2), 0., 1); +} + +} // namespace krino diff --git a/packages/krino/krino/unit_tests/Akri_Unit_CDMesh.cpp b/packages/krino/krino/unit_tests/Akri_Unit_CDMesh.cpp new file mode 100644 index 000000000000..efff1945a32a --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_CDMesh.cpp @@ -0,0 +1,3092 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace krino { + +class LevelSet; + +namespace { + template +void build_one_tet4_mesh(DECOMP_FIXTURE & fixture, + stk::mesh::Part & elem_part, + const int parallel_rank, const int parallel_size) +{ + stk::mesh::BulkData & mesh = fixture.fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + ASSERT_TRUE(parallel_size == 1 || parallel_size == 2); + ThrowRequire(elem_part.topology() == stk::topology::TETRAHEDRON_4); + ASSERT_EQ(stk::topology::ELEMENT_RANK, elem_part.primary_entity_rank()); + + mesh.modification_begin(); + { + stk::mesh::PartVector elem_parts(2); + elem_parts[1] = &aux_meta.active_part(); + + std::vector elem_nodes = {1, 2, 3, 4}; + + stk::mesh::Entity element; + if(parallel_rank == 0) + { + elem_parts[0] = &elem_part; + element = fixture.create_element(elem_parts, 1, elem_nodes); + } + } + mesh.modification_end(); + + if(parallel_rank == 0) + { + EXPECT_EQ(1u, stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(stk::topology::ELEMENT_RANK))); + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + ASSERT_TRUE(mesh.is_valid(node1)); + ASSERT_TRUE(mesh.is_valid(node2)); + ASSERT_TRUE(mesh.is_valid(node3)); + ASSERT_TRUE(mesh.is_valid(node4)); + + double * node1_coords = field_data(fixture.coord_field, node1); + double * node2_coords = field_data(fixture.coord_field, node2); + double * node3_coords = field_data(fixture.coord_field, node3); + double * node4_coords = field_data(fixture.coord_field, node4); + + node1_coords[0] = 1.; + node1_coords[1] = 1.; + node1_coords[2] = 1.; + + node2_coords[0] = -1.; + node2_coords[1] = 1.; + node2_coords[2] = -1.; + + node3_coords[0] = 1.; + node3_coords[1] = -1.; + node3_coords[2] = -1.; + + node4_coords[0] = -1.; + node4_coords[1] = -1.; + node4_coords[2] = 1.; + } +} + +template +void build_two_tet4_mesh_np2(DECOMP_FIXTURE & fixture, + stk::mesh::Part & elem1_part, stk::mesh::Part & elem2_part, stk::mesh::Part & surface_part, + const int parallel_rank, const int parallel_size, const bool add_side = true, + const bool build_all_on_P0 = false) +{ + stk::mesh::BulkData & mesh = fixture.fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + ASSERT_TRUE(parallel_size == 1 || parallel_size == 2); + ThrowRequire(elem1_part.topology() == stk::topology::TETRAHEDRON_4); + ASSERT_EQ(meta.side_rank(), surface_part.primary_entity_rank()); + ASSERT_EQ(stk::topology::ELEMENT_RANK, elem1_part.primary_entity_rank()); + ASSERT_EQ(stk::topology::ELEMENT_RANK, elem2_part.primary_entity_rank()); + + stk::mesh::Entity sideEntity; + mesh.modification_begin(); + { + stk::mesh::PartVector elem_parts(2); + elem_parts[1] = &aux_meta.active_part(); + + std::vector elem1_nodes = {1, 3, 2, 4}; + std::vector elem2_nodes = {1, 2, 3, 5}; + + stk::mesh::Entity element; + if(parallel_rank == 0) + { + elem_parts[0] = &elem1_part; + element = fixture.create_element(elem_parts, 1, elem1_nodes); + } + if((parallel_rank == 1 && !build_all_on_P0) || (parallel_rank == 0 && build_all_on_P0) || + parallel_size == 1) + { + elem_parts[0] = &elem2_part; + element = fixture.create_element(elem_parts, 2, elem2_nodes); + } + + if(parallel_size > 1 && !build_all_on_P0) + { + const int opp_rank = parallel_rank == 0 ? 1 : 0; + for(auto i=1; i <= 3; ++i) + { + const auto node = mesh.get_entity(stk::topology::NODE_RANK, i); + mesh.add_node_sharing(node, opp_rank); + } + } + + if(add_side && (parallel_rank == 0 || !build_all_on_P0)) + { + sideEntity = mesh.declare_solo_side(7, {&surface_part}); + for(auto i=1; i <= 3; ++i) + { + auto side_node = mesh.get_entity(stk::topology::NODE_RANK, i); + mesh.declare_relation(sideEntity, side_node, i-1); + } + attach_entity_to_elements(mesh, sideEntity); + } + } + mesh.modification_end(); + + if(parallel_rank == 0 || !build_all_on_P0) + { + EXPECT_EQ(2u, stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(stk::topology::ELEMENT_RANK))); + if(add_side) + { + EXPECT_EQ(1u, stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(meta.side_rank()))); + EXPECT_EQ(1u, stk::mesh::count_selected_entities(surface_part, mesh.buckets(meta.side_rank()))); + + EXPECT_EQ(2u, mesh.num_connectivity(sideEntity, stk::topology::ELEMENT_RANK)); + } + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + const auto node5 = mesh.get_entity(stk::topology::NODE_RANK, 5); + + ASSERT_TRUE(mesh.is_valid(node1)); + ASSERT_TRUE(mesh.is_valid(node2)); + ASSERT_TRUE(mesh.is_valid(node3)); + ASSERT_TRUE(mesh.is_valid(node4)); + ASSERT_TRUE(mesh.is_valid(node5)); + + double * node1_coords = field_data(fixture.coord_field, node1); + double * node2_coords = field_data(fixture.coord_field, node2); + double * node3_coords = field_data(fixture.coord_field, node3); + double * node4_coords = field_data(fixture.coord_field, node4); + double * node5_coords = field_data(fixture.coord_field, node5); + + node1_coords[0] = 0.; + node1_coords[1] = 0.; + node1_coords[2] = 0.; + + node2_coords[0] = 0.; + node2_coords[1] = 0.; + node2_coords[2] = 1.; + + node3_coords[0] = 0.; + node3_coords[1] = 1.; + node3_coords[2] = 0.; + + node4_coords[0] = -1.; + node4_coords[1] = 0.; + node4_coords[2] = 0.; + + node5_coords[0] = 1.; + node5_coords[1] = 0.; + node5_coords[2] = 0.; + } +} + +template +void build_two_tri3_mesh_np2(DECOMP_FIXTURE & fixture, + stk::mesh::Part & elem1_part, stk::mesh::Part & elem2_part, stk::mesh::Part & surface_part, + const int parallel_rank, const int parallel_size, const bool add_side = true, + const bool build_all_on_P0 = false) +{ + stk::mesh::BulkData & mesh = fixture.fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + ASSERT_TRUE(parallel_size == 1 || parallel_size == 2); + ThrowRequire(elem1_part.topology() == stk::topology::TRIANGLE_3_2D); + ASSERT_EQ(meta.side_rank(), surface_part.primary_entity_rank()); + ASSERT_EQ(stk::topology::ELEMENT_RANK, elem1_part.primary_entity_rank()); + ASSERT_EQ(stk::topology::ELEMENT_RANK, elem2_part.primary_entity_rank()); + + stk::mesh::Entity sideEntity; + mesh.modification_begin(); + { + stk::mesh::PartVector elem_parts(2); + elem_parts[1] = &aux_meta.active_part(); + + std::vector elem1_nodes = {2, 1, 3}; + std::vector elem2_nodes = {1, 2, 4}; + + stk::mesh::Entity element; + if(parallel_rank == 0) + { + elem_parts[0] = &elem1_part; + element = fixture.create_element(elem_parts, 1, elem1_nodes); + } + if((parallel_rank == 1 && !build_all_on_P0) || (parallel_rank == 0 && build_all_on_P0) || + parallel_size == 1) + { + elem_parts[0] = &elem2_part; + element = fixture.create_element(elem_parts, 2, elem2_nodes); + } + + if(parallel_size > 1 && !build_all_on_P0) + { + const int opp_rank = parallel_rank == 0 ? 1 : 0; + for(auto i=1; i <= 2; ++i) + { + const auto node = mesh.get_entity(stk::topology::NODE_RANK, i); + mesh.add_node_sharing(node, opp_rank); + } + } + + if(add_side && (parallel_rank == 0 || !build_all_on_P0)) + { + sideEntity = mesh.declare_solo_side(7, {&surface_part}); + for(auto i=1; i <= 2; ++i) + { + auto side_node = mesh.get_entity(stk::topology::NODE_RANK, i); + mesh.declare_relation(sideEntity, side_node, i-1); + } + attach_entity_to_elements(mesh, sideEntity); + } + } + mesh.modification_end(); + + if(parallel_rank == 0 || !build_all_on_P0) + { + EXPECT_EQ(2u, stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(stk::topology::ELEMENT_RANK))); + if(add_side) + { + EXPECT_EQ(1u, stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(meta.side_rank()))); + EXPECT_EQ(1u, stk::mesh::count_selected_entities(surface_part, mesh.buckets(meta.side_rank()))); + + EXPECT_EQ(2u, mesh.num_connectivity(sideEntity, stk::topology::ELEMENT_RANK)); + } + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + ASSERT_TRUE(mesh.is_valid(node1)); + ASSERT_TRUE(mesh.is_valid(node2)); + ASSERT_TRUE(mesh.is_valid(node3)); + ASSERT_TRUE(mesh.is_valid(node4)); + + double * node1_coords = field_data(fixture.coord_field, node1); + double * node2_coords = field_data(fixture.coord_field, node2); + double * node3_coords = field_data(fixture.coord_field, node3); + double * node4_coords = field_data(fixture.coord_field, node4); + + node1_coords[0] = 0.; + node1_coords[1] = 0.; + + node2_coords[0] = 0.; + node2_coords[1] = 1.; + + node3_coords[0] = 1.; + node3_coords[1] = 0.; + + node4_coords[0] = -1.; + node4_coords[1] = 0.; + } +} +} + +struct SingleLSPolicy +{ + void setup_ls_field(const bool is_death, stk::mesh::MetaData & meta, CDFEM_Support & cdfem_support) + { + AuxMetaData & aux_meta = AuxMetaData::get(meta); + Phase_Support::set_one_levelset_per_phase(false); + ls_isovar = aux_meta.declare_field("LS", FieldType::REAL, stk::topology::NODE_RANK, 1u); + cdfem_support.add_interpolation_field(ls_isovar); + + LevelSet * ls_ptr = nullptr; + ls_field = std::make_shared("LS", LevelSet_Identifier(0), ls_isovar, 0., ls_ptr); + if(is_death) + { + death_spec = std::unique_ptr(new CDFEM_Inequality_Spec("death_spec")); + } + cdfem_support.add_ls_field(*ls_field, death_spec.get()); + } + + void register_ls_on_blocks(const stk::mesh::PartVector & blocks, stk::mesh::MetaData & meta, + Block_Surface_Connectivity & block_surface_info, const bool register_fields) + { + AuxMetaData & aux_meta = AuxMetaData::get(meta); + PhaseTag p, n; + const LevelSet_Identifier id0(0); + p.add(id0, 1); + n.add(id0, -1); + PhaseVec named_phases{{"A", p}, {"B", n}}; + + if(death_spec) + { + death_spec->set_phases(p, n); + } + + Phase_Support & phase_support = Phase_Support::get(meta); + phase_support.set_input_block_surface_connectivity(block_surface_info); + LevelSet * ls_ptr = nullptr; + phase_support.register_blocks_for_level_set(ls_ptr, blocks); + phase_support.decompose_blocks(blocks, named_phases, LS_Name_Generator()); + + for(auto && b : blocks) + { + auto conformal_A = phase_support.find_conformal_io_part(*b, p); + auto conformal_B = phase_support.find_conformal_io_part(*b, n); + ThrowRequire(conformal_A != nullptr); + ThrowRequire(conformal_B != nullptr); + if(register_fields) + { + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, 1u, 1u, *b); + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, 1u, 1u, *conformal_A); + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, 1u, 1u, *conformal_B); + } + } + } + + FieldRef ls_isovar; + std::shared_ptr ls_field; + std::unique_ptr death_spec; +}; + +template +struct LSPerPhasePolicy +{ + void setup_ls_field(const bool is_death, stk::mesh::MetaData & meta, CDFEM_Support & cdfem_support) + { + AuxMetaData & aux_meta = AuxMetaData::get(meta); + Phase_Support::set_one_levelset_per_phase(true); + ThrowRequire(!is_death); + for(unsigned i=0; i < NUM_LS; ++i) + { + const std::string isovar_name = "LS" + std::to_string(i); + FieldRef ls_isovar = aux_meta.declare_field(isovar_name, FieldType::REAL, stk::topology::NODE_RANK, 1u); + ls_isovars.push_back(ls_isovar); + cdfem_support.add_interpolation_field(ls_isovar); + + LevelSet * ls_ptr = nullptr; + auto ls_field = std::make_shared(isovar_name, LevelSet_Identifier(i), ls_isovar, 0., ls_ptr); + ls_fields.push_back(ls_field); + cdfem_support.add_ls_field(*ls_field, nullptr); + } + } + + void register_ls_on_blocks(const stk::mesh::PartVector & blocks, stk::mesh::MetaData & meta, + Block_Surface_Connectivity & block_surface_info, const bool register_fields) + { + AuxMetaData & aux_meta = AuxMetaData::get(meta); + PhaseVec named_phases; + for(unsigned ls=0; ls < NUM_LS; ++ls) + { + PhaseTag tag; + tag.add(LevelSet_Identifier(ls), -1); + named_phases.push_back(NamedPhase("P" + std::to_string(ls), tag)); + } + + Phase_Support & phase_support = Phase_Support::get(meta); + phase_support.set_input_block_surface_connectivity(block_surface_info); + LevelSet * ls_ptr = nullptr; + phase_support.register_blocks_for_level_set(ls_ptr, blocks); + phase_support.decompose_blocks(blocks, named_phases, LS_Name_Generator()); + + for(auto && b : blocks) + { + for( unsigned ls=0; ls < NUM_LS; ++ls ) + { + auto conformal_part = phase_support.find_conformal_io_part(*b, named_phases[ls].tag()); + ThrowRequire(conformal_part != nullptr); + if(register_fields) + { + // Need to register every LS on every conformal part + for( unsigned ls2=0; ls2 < NUM_LS; ++ls2 ) + { + aux_meta.register_field(ls_isovars[ls2].name(), FieldType::REAL, stk::topology::NODE_RANK, 1u, 1u, *conformal_part); + } + aux_meta.register_field(ls_isovars[ls].name(), FieldType::REAL, stk::topology::NODE_RANK, 1u, 1u, *b); + } + } + } + } + + std::vector ls_isovars; + std::vector > ls_fields; +}; + +template +struct LSPerInterfacePolicy +{ + void setup_ls_field(const bool is_death, stk::mesh::MetaData & meta, CDFEM_Support & cdfem_support) + { + AuxMetaData & aux_meta = AuxMetaData::get(meta); + Phase_Support::set_one_levelset_per_phase(false); + ThrowRequire(!is_death); + for(unsigned i=0; i < NUM_LS; ++i) + { + const std::string isovar_name = "LS" + std::to_string(i); + FieldRef ls_isovar = aux_meta.declare_field(isovar_name, FieldType::REAL, stk::topology::NODE_RANK, 1u); + ls_isovars.push_back(ls_isovar); + cdfem_support.add_interpolation_field(ls_isovar); + + LevelSet * ls_ptr = nullptr; + auto ls_field = std::make_shared(isovar_name, LevelSet_Identifier(i), ls_isovar, 0., ls_ptr); + ls_fields.push_back(ls_field); + cdfem_support.add_ls_field(*ls_field, nullptr); + } + } + + void register_ls_on_blocks(const stk::mesh::PartVector & blocks, stk::mesh::MetaData & meta, + Block_Surface_Connectivity & block_surface_info, const bool register_fields) + { + AuxMetaData & aux_meta = AuxMetaData::get(meta); + PhaseVec named_phases; + const unsigned numPhases = 1<>ls)%2 == 0; + const int lsSign = lsIsNeg ? -1 : 1; + tag.add(LevelSet_Identifier(ls), lsSign); + phaseName += (lsIsNeg ? "-" : "+"); + } + named_phases.push_back(NamedPhase(phaseName, tag)); + } + + Phase_Support & phase_support = Phase_Support::get(meta); + phase_support.set_input_block_surface_connectivity(block_surface_info); + LevelSet * ls_ptr = nullptr; + phase_support.register_blocks_for_level_set(ls_ptr, blocks); + phase_support.decompose_blocks(blocks, named_phases, LS_Name_Generator()); + + if(register_fields) + { + for(auto && b : blocks) + { + for( unsigned ls=0; ls < NUM_LS; ++ls ) + { + aux_meta.register_field(ls_isovars[ls].name(), FieldType::REAL, stk::topology::NODE_RANK, 1u, 1u, *b); + + for (unsigned phase=0; phase ls_isovars; + std::vector > ls_fields; +}; + +template +class CompleteDecompositionFixture : public ::testing::Test +{ +public: + CompleteDecompositionFixture() + : fixture(), cdfemSupport(CDFEM_Support::get(fixture.meta_data())) + { + AuxMetaData & aux_meta = AuxMetaData::get(fixture.meta_data()); + auto & vec_type = fixture.meta_data().spatial_dimension() == 2 ? FieldType::VECTOR_2D : FieldType::VECTOR_3D; + coord_field = aux_meta.register_field("coordinates", vec_type, stk::topology::NODE_RANK, 1u, 1u, fixture.meta_data().universal_part()); + cdfemSupport.set_coords_field(coord_field); + cdfemSupport.add_interpolation_field(coord_field); + cdfemSupport.register_parent_node_ids_field(); + + cdfemSupport.set_prolongation_model(INTERPOLATION); + } + + void setup_ls_field(const bool is_death = false) + { + ls_policy.setup_ls_field(is_death, fixture.meta_data(), cdfemSupport); + } + + void register_ls_on_blocks(const stk::mesh::PartVector & blocks, const bool register_fields = true) + { + ls_policy.register_ls_on_blocks(blocks, fixture.meta_data(), block_surface_info, register_fields); + } + + stk::mesh::Part & declare_input_block(const std::string & name, const stk::topology topo) + { + auto & block_part = fixture.meta_data().declare_part_with_topology(name, topo); + stk::io::put_io_part_attribute(block_part); + return block_part; + } + + stk::mesh::Part & declare_input_surface(const std::string & name, const stk::topology topo, const std::set & touching_blocks) + { + auto & surface_part = fixture.meta_data().declare_part_with_topology(name, topo); + stk::io::put_io_part_attribute(surface_part); + + block_surface_info.add_surface(surface_part.mesh_meta_data_ordinal(), touching_blocks); + return surface_part; + } + + stk::mesh::PartVector declare_input_surfaces_touching_block(const unsigned numSurfaces, const stk::mesh::Part & touchingBlock) + { + const stk::topology topo = touchingBlock.topology().side_topology(); + stk::mesh::PartVector surfaces; + for (unsigned i=0; iget_active_part(), cdfemSupport, Phase_Support::get(fixture.meta_data())); + if (cdfemSupport.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + nodesToSnappedDomains = snap_as_much_as_possible_while_maintaining_quality(krino_mesh->stk_bulk(), krino_mesh->get_active_part(), cdfemSupport.get_interpolation_fields(), interfaceGeometry, cdfemSupport.get_global_ids_are_parallel_consistent()); + interfaceGeometry.prepare_to_process_elements(krino_mesh->stk_bulk(), nodesToSnappedDomains); + + if(!krino_mesh->my_old_mesh) + { + krino_mesh->my_old_mesh = std::make_shared(fixture.bulk_data(), std::shared_ptr()); + krino_mesh->my_old_mesh->generate_nonconformal_elements(); + } + + krino_mesh->generate_nonconformal_elements(); + if (cdfemSupport.get_cdfem_edge_degeneracy_handling() == SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE) + krino_mesh->snap_nearby_intersections_to_nodes(interfaceGeometry, nodesToSnappedDomains); + krino_mesh->set_phase_of_uncut_elements(interfaceGeometry); + krino_mesh->triangulate(interfaceGeometry); + krino_mesh->my_old_mesh->stash_field_data(-1, *krino_mesh); + + + krino_mesh->decompose(); + krino_mesh->modify_mesh(); + krino_mesh->prolongation(); + + if(krinolog.shouldPrint(LOG_DEBUG)) + { + krino_mesh->debug_output(); + } + } + + void debug_output() + { + krino_mesh->debug_output(); + } + + void commit() + { + krino_mesh = std::make_shared(fixture.bulk_data(), std::shared_ptr()); + } + + stk::mesh::Entity create_element(stk::mesh::PartVector & elem_parts, stk::mesh::EntityId elem_id, + std::vector elem_nodes) + { + auto elem = stk::mesh::declare_element( fixture.bulk_data(), elem_parts, elem_id, elem_nodes ); + { + const stk::mesh::Entity * const nodes = fixture.bulk_data().begin_nodes(elem); + for(unsigned i=0; i < elem_nodes.size(); ++i) + { + EXPECT_EQ(elem_nodes[i], fixture.bulk_data().identifier(nodes[i])); + if (!fixture.bulk_data().bucket(nodes[i]).member(cdfemSupport.get_active_part())) + fixture.bulk_data().change_entity_parts(nodes[i], stk::mesh::ConstPartVector{&cdfemSupport.get_active_part()}, {}); + } + } + return elem; + } + + void run_rebalance_with(const std::string& decomp_method) + { + /* This is a 2 processor test to confirm that we can rebalance a mesh with CDFEM cut elements, + * and then successfully cut the mesh again. We create an initial mesh with 2 tets both owned + * by P0, do a decomposition, rebalance so that 1 parent element should end up on each of P0 and + * P1, and then do a second decomposition. + */ + + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + if (parallel_size != 2) return; + + cdfemSupport.set_cdfem_edge_tol(0.1); + cdfemSupport.set_simplex_generation_method(CUT_QUADS_BY_GLOBAL_IDENTIFIER); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + register_ls_on_blocks({&block1_part, &block2_part}); + + FieldRef elem_weight_field = + aux_meta.register_field("element_weight", FieldType::REAL, stk::topology::ELEMENT_RANK, + 1u, 1, fixture.meta_data().universal_part()); + + commit(); + + const bool build_all_on_P0 = true; + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, + parallel_size, true, build_all_on_P0); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + if(parallel_rank == 0) + { + auto & node_buckets = mesh.buckets(stk::topology::NODE_RANK); + for(auto && b_ptr : node_buckets) + { + for(auto && node : *b_ptr) + { + const double * coords = field_data(coord_field, node); + double * ls_data = field_data(ls_policy.ls_isovar, node); + if(ls_data) *ls_data = coords[1]-0.5; + } + } + } + stk::mesh::communicate_field_data(mesh, {&ls_policy.ls_isovar.field(), &coord_field.field()}); + + ASSERT_NO_THROW(decompose_mesh()); + + if(parallel_rank == 1) + { + EXPECT_EQ(0u, stk::mesh::get_num_entities(mesh)); + } + + const stk::mesh::Selector parent_selector = krino_mesh->get_parent_part(); + if(parallel_rank == 0) + { + auto & elem_buckets = mesh.buckets(stk::topology::ELEMENT_RANK); + for(auto && b_ptr : elem_buckets) + { + const bool is_parent = parent_selector(*b_ptr); + for(auto && elem : *b_ptr) + { + double * weight = field_data(elem_weight_field, elem); + *weight = is_parent ? 1. : 0.; + } + } + } + + rebalance_utils::rebalance_mesh(mesh, + krino_mesh.get(), + elem_weight_field.name(), + coord_field.name(), + {fixture.meta_data().universal_part()}, + decomp_method); + + // Both procs should now own 1 parent element and 4 children + EXPECT_EQ(1u, stk::mesh::count_selected_entities( + fixture.meta_data().locally_owned_part() & parent_selector, + mesh.buckets(stk::topology::ELEMENT_RANK))); + EXPECT_EQ(4u, stk::mesh::count_selected_entities( + fixture.meta_data().locally_owned_part() & krino_mesh->get_child_part(), + mesh.buckets(stk::topology::ELEMENT_RANK))); + + krino_mesh = std::make_shared(mesh, krino_mesh); + + auto & node_buckets = mesh.buckets(stk::topology::NODE_RANK); + for(auto && b_ptr : node_buckets) + { + for(auto && node : *b_ptr) + { + const double * coords = field_data(coord_field, node); + double * ls_data = field_data(ls_policy.ls_isovar, node); + if(ls_data) *ls_data = coords[2]-0.5; + } + } + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + } + + MESH_FIXTURE fixture; + FieldRef coord_field; + CDFEM_Support & cdfemSupport; + std::shared_ptr krino_mesh; + Block_Surface_Connectivity block_surface_info; + LS_FIELD_POLICY ls_policy; + LogRedirecter log; +}; + +namespace { +template +void build_two_tri3_mesh_on_one_or_two_procs(CompleteDecompositionFixture & fixture, + stk::mesh::Part & block_part, const int parallel_rank) +{ + stk::mesh::BulkData & mesh = fixture.fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.fixture.meta_data(); + const int parallel_size = mesh.parallel_size(); + mesh.modification_begin(); + { + /* + * 4---3 + * |\ 2| + * | \ | + * |1 \| + * 1---2 + */ + stk::mesh::PartVector elem_parts; + elem_parts.push_back(&block_part); + elem_parts.push_back(&fixture.cdfemSupport.get_active_part()); + + std::vector elem1_nodes = {1, 2, 4}; + std::vector elem2_nodes = {2, 3, 4}; + + if(parallel_rank == 0) fixture.create_element(elem_parts, 1, elem1_nodes); + if(parallel_rank == 1 || parallel_size == 1) fixture.create_element(elem_parts, 2, elem2_nodes); + + if(parallel_size > 1) + { + const int opp_rank = parallel_rank == 0 ? 1 : 0; + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + mesh.add_node_sharing(node2, opp_rank); + mesh.add_node_sharing(node4, opp_rank); + } + } + mesh.modification_end(); + + EXPECT_EQ(2u, stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(stk::topology::ELEMENT_RANK))); + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + ASSERT_TRUE(mesh.is_valid(node1)); + ASSERT_TRUE(mesh.is_valid(node2)); + ASSERT_TRUE(mesh.is_valid(node3)); + ASSERT_TRUE(mesh.is_valid(node4)); + + double * node1_coords = field_data(fixture.coord_field, node1); + double * node2_coords = field_data(fixture.coord_field, node2); + double * node3_coords = field_data(fixture.coord_field, node3); + double * node4_coords = field_data(fixture.coord_field, node4); + + node1_coords[0] = 0.; + node1_coords[1] = 0.; + + node2_coords[0] = 1.; + node2_coords[1] = 0.; + + node3_coords[0] = 1.; + node3_coords[1] = 1.; + + node4_coords[0] = 0.; + node4_coords[1] = 1.; +} + +template +void build_two_tri3_mesh_on_one_or_two_procs_with_sides(CompleteDecompositionFixture & fixture, + stk::mesh::Part & blockPart, const stk::mesh::PartVector & sideParts, const int parallel_rank) +{ + build_two_tri3_mesh_on_one_or_two_procs(fixture, blockPart, parallel_rank); + + ThrowRequire(sideParts.size() == 4); + stk::mesh::BulkData & mesh = fixture.fixture.bulk_data(); + const int parallel_size = mesh.parallel_size(); + + mesh.modification_begin(); + if(parallel_rank == 0) + { + const auto element1 = mesh.get_entity(stk::topology::ELEMENT_RANK,1); + mesh.declare_element_side(element1, 0, stk::mesh::PartVector{sideParts[0]}); + mesh.declare_element_side(element1, 2, stk::mesh::PartVector{sideParts[3]}); + } + if(parallel_rank == 1 || parallel_size == 1) + { + const auto element2 = mesh.get_entity(stk::topology::ELEMENT_RANK,2); + mesh.declare_element_side(element2, 0, stk::mesh::PartVector{sideParts[1]}); + mesh.declare_element_side(element2, 1, stk::mesh::PartVector{sideParts[2]}); + } + mesh.modification_end(); +} + +void check_two_tri3_snapped_mesh_np2(CompleteDecompositionFixture & fixture, const int parallel_rank) +{ + stk::mesh::BulkData & mesh = fixture.fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + std::vector entities; + + // Should be no new nodes because of snapped interface, 3 nodes owned by P0, 1 by P1 + mesh.get_entities(stk::topology::NODE_RANK, meta.universal_part(), entities); + EXPECT_EQ(4u, entities.size()); + mesh.get_entities(stk::topology::NODE_RANK, aux_meta.active_part(), entities); + EXPECT_EQ(4u, entities.size()); + mesh.get_entities(stk::topology::NODE_RANK, aux_meta.active_part() & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_EQ(3u, entities.size()); + } + else + { + EXPECT_EQ(1u, entities.size()); + } + + // Should be 1 interface edge + mesh.get_entities(stk::topology::EDGE_RANK, aux_meta.active_part() & + aux_meta.get_part("surface_block_1_A_B") & aux_meta.get_part("surface_block_1_B_A"), entities); + EXPECT_EQ(1u, entities.size()); + + // Should be 2 coincident subelements, no parents + mesh.get_entities(stk::topology::ELEMENT_RANK, meta.universal_part(), entities); + EXPECT_EQ(2u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.active_part(), entities); + EXPECT_EQ(2u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, !aux_meta.active_part(), entities); + EXPECT_EQ(0u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B"), entities); + EXPECT_EQ(1u, entities.size()); +} + +} + +typedef CompleteDecompositionFixture CDMeshTests2D; +TEST_F(CDMeshTests2D, IsovariableNotDefinedOnDecomposedBlock) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}, false); + + commit(); + + EXPECT_ANY_THROW(krino_mesh->check_isovariable_field_existence_on_decomposed_blocks(true)); +} + +TEST_F(CDMeshTests2D, IsovariableNotDefinedOnBlock1) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}, false); + + auto & meta = fixture.meta_data(); + auto & aux_meta = AuxMetaData::get(meta); + + auto * A_part = meta.get_part("block_1_A"); + ASSERT_TRUE(A_part != nullptr); + auto * B_part = meta.get_part("block_1_B"); + ASSERT_TRUE(B_part != nullptr); + + // Catch the case where the field exists on both conformal parts, but not + // the initial un-decomposed part so we can't do the initial decomposition. + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, + 1u, 1u, *A_part); + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, + 1u, 1u, *B_part); + + commit(); + + EXPECT_ANY_THROW(krino_mesh->check_isovariable_field_existence_on_decomposed_blocks(true)); +} + +TEST_F(CDMeshTests2D, IsovariableOnlyOnBlock1SteadyState) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}, false); + + auto & meta = fixture.meta_data(); + auto & aux_meta = AuxMetaData::get(meta); + + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, + 1u, 1u, block_part); + + commit(); + + EXPECT_NO_THROW(krino_mesh->check_isovariable_field_existence_on_decomposed_blocks(false)); +} + +TEST_F(CDMeshTests2D, DeathIsovariableNotDefinedOnDecomposedBlock) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + const bool is_death = true; + setup_ls_field(is_death); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}, false); + + commit(); + + EXPECT_ANY_THROW(krino_mesh->check_isovariable_field_existence_on_decomposed_blocks(true)); +} + +TEST_F(CDMeshTests2D, DeathIsovariableNotDefinedOnDeadBlock) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + const bool is_death = true; + setup_ls_field(is_death); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}, false); + + auto & meta = fixture.meta_data(); + auto & aux_meta = AuxMetaData::get(meta); + auto * alive_part = meta.get_part("block_1_A"); + ASSERT_TRUE(alive_part != nullptr); + + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, + 1u, 1u, block_part); + aux_meta.register_field("LS", FieldType::REAL, stk::topology::NODE_RANK, + 1u, 1u, *alive_part); + + commit(); + + EXPECT_NO_THROW(krino_mesh->check_isovariable_field_existence_on_decomposed_blocks(true)); +} + +typedef CompleteDecompositionFixture CDMeshTests2D; +TEST_F(CDMeshTests2D, Single_Tri3_Decomposition) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + mesh.modification_begin(); + { + stk::mesh::PartVector elem_parts; + elem_parts.push_back(&block_part); + elem_parts.push_back(&aux_meta.active_part()); + std::vector node_ids = {1, 2, 3}; + + create_element(elem_parts, 1, node_ids); + } + mesh.modification_end(); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + + double * node1_coords = field_data(coord_field, node1); + double * node2_coords = field_data(coord_field, node2); + double * node3_coords = field_data(coord_field, node3); + + node1_coords[0] = 0.; + node1_coords[1] = 0.; + + node2_coords[0] = 1.; + node2_coords[1] = 0.; + + node3_coords[0] = 0.; + node3_coords[1] = 1.; + + *field_data(ls_policy.ls_isovar, node1) = -1; + *field_data(ls_policy.ls_isovar, node2) = 1; + *field_data(ls_policy.ls_isovar, node3) = -1; + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + std::vector entities; + + // Should have added 2 nodes at the cutting locations + mesh.get_entities(stk::topology::NODE_RANK, meta.universal_part(), entities); + EXPECT_EQ(5u, entities.size()); + mesh.get_entities(stk::topology::NODE_RANK, aux_meta.active_part(), entities); + EXPECT_EQ(5u, entities.size()); + + // Should be 1 interface edge + mesh.get_entities(stk::topology::EDGE_RANK, aux_meta.active_part() & + aux_meta.get_part("surface_block_1_A_B") & aux_meta.get_part("surface_block_1_B_A"), entities); + EXPECT_EQ(1u, entities.size()); + + // Should be 3 conformal elements plus the parent element + mesh.get_entities(stk::topology::ELEMENT_RANK, meta.universal_part(), entities); + EXPECT_EQ(4u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.active_part(), entities); + EXPECT_EQ(3u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, !aux_meta.active_part(), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B"), entities); + EXPECT_EQ(2u, entities.size()); +} + +TEST_F(CDMeshTests2D, Two_Tri3_Snapped_Interface_Decomposition_NP2) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size != 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + build_two_tri3_mesh_on_one_or_two_procs(*this, block_part, parallel_rank); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + { + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + *field_data(ls_policy.ls_isovar, node1) = -1; + *field_data(ls_policy.ls_isovar, node2) = 0; + *field_data(ls_policy.ls_isovar, node3) = 1; + *field_data(ls_policy.ls_isovar, node4) = 0; + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + check_two_tri3_snapped_mesh_np2(*this, parallel_rank); + + std::vector entities; + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_TRUE(entities.empty()); + } + else + { + EXPECT_EQ(1u, entities.size()); + } + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_EQ(1u, entities.size()); + } + else + { + EXPECT_TRUE(entities.empty()); + } + + krino_mesh = std::make_shared(mesh, krino_mesh); + } + + // Swap the A and B elements + { + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + *field_data(ls_policy.ls_isovar, node1) = 1; + *field_data(ls_policy.ls_isovar, node2) = 0; + *field_data(ls_policy.ls_isovar, node3) = -1; + *field_data(ls_policy.ls_isovar, node4) = 0; + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + check_two_tri3_snapped_mesh_np2(*this, parallel_rank); + + std::vector entities; + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_EQ(1u, entities.size()); + } + else + { + EXPECT_TRUE(entities.empty()); + } + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_TRUE(entities.empty()); + } + else + { + EXPECT_EQ(1u, entities.size()); + } + } +} + +TEST_F(CDMeshTests2D, Two_Tri3_Death_Snapped_Interface_Decomposition_NP2) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size != 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + setup_ls_field(true); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + build_two_tri3_mesh_on_one_or_two_procs(*this, block_part, parallel_rank); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + { + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + *field_data(ls_policy.ls_isovar, node1) = -1; + *field_data(ls_policy.ls_isovar, node2) = 0; + *field_data(ls_policy.ls_isovar, node3) = 1; + *field_data(ls_policy.ls_isovar, node4) = 0; + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + check_two_tri3_snapped_mesh_np2(*this, parallel_rank); + + std::vector entities; + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_TRUE(entities.empty()); + } + else + { + EXPECT_EQ(1u, entities.size()); + } + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_EQ(1u, entities.size()); + } + else + { + EXPECT_TRUE(entities.empty()); + } + } +} + +TEST_F(CDMeshTests2D, Two_Tri3_Death_Snapped_Interface_Decomposition_NP2_Opposite_Ownership) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size != 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + setup_ls_field(true); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + build_two_tri3_mesh_on_one_or_two_procs(*this, block_part, parallel_rank); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + { + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + *field_data(ls_policy.ls_isovar, node1) = 1; + *field_data(ls_policy.ls_isovar, node2) = 0; + *field_data(ls_policy.ls_isovar, node3) = -1; + *field_data(ls_policy.ls_isovar, node4) = 0; + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + check_two_tri3_snapped_mesh_np2(*this, parallel_rank); + + std::vector entities; + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_EQ(1u, entities.size()); + } + else + { + EXPECT_TRUE(entities.empty()); + } + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B") & meta.locally_owned_part(), entities); + if(parallel_rank == 0) + { + EXPECT_TRUE(entities.empty()); + } + else + { + EXPECT_EQ(1u, entities.size()); + } + } +} + +TEST_F(CDMeshTests2D, Two_Tri3_Periodic) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + cdfemSupport.set_cdfem_edge_tol(0.1); + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + AuxMetaData & aux_meta = AuxMetaData::get(fixture.meta_data()); + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + build_two_tri3_mesh_on_one_or_two_procs(*this, block_part, parallel_rank); + + stk::mesh::create_exposed_block_boundary_sides(mesh, fixture.meta_data().universal_part(), {&aux_meta.exposed_boundary_part()}); + + { + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + const auto node4 = mesh.get_entity(stk::topology::NODE_RANK, 4); + + *field_data(ls_policy.ls_isovar, node1) = -0.99; + *field_data(ls_policy.ls_isovar, node2) = -0.8; + *field_data(ls_policy.ls_isovar, node3) = 0.2; + *field_data(ls_policy.ls_isovar, node4) = 0.01; + + krino_mesh->add_periodic_node_pair(node3, node4); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + // Periodic constraint should cause interface to snap to both 3 and 4 so no elements + // get cut. + std::vector entities; + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A"), entities); + EXPECT_EQ(0u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B"), entities); + EXPECT_EQ(2u, entities.size()); + } +} + +TEST_F(CDMeshTests2D, Two_Tri3_PeriodicParallelNonSharedNode) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size == 1 || (parallel_size % 2) != 0) return; + + cdfemSupport.set_cdfem_edge_tol(0.1); + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + const stk::mesh::EntityId starting_id = 1 + 3*parallel_rank; + mesh.modification_begin(); + { + stk::mesh::PartVector elem_parts; + elem_parts.push_back(&block_part); + elem_parts.push_back(&cdfemSupport.get_active_part()); + + std::vector elem_nodes = {starting_id, starting_id + 1, starting_id + 2}; + + create_element(elem_parts, parallel_rank + 1, elem_nodes); + } + mesh.modification_end(); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + EXPECT_EQ(1u, stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(stk::topology::ELEMENT_RANK))); + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, starting_id); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, starting_id + 1); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, starting_id + 2); + + ASSERT_TRUE(mesh.is_valid(node1)); + ASSERT_TRUE(mesh.is_valid(node2)); + ASSERT_TRUE(mesh.is_valid(node3)); + + double * node1_coords = field_data(coord_field, node1); + double * node2_coords = field_data(coord_field, node2); + double * node3_coords = field_data(coord_field, node3); + + const double x0 = 1.1 * parallel_rank; + node1_coords[0] = x0; + node1_coords[1] = 0.; + + node2_coords[0] = x0 + 1.; + node2_coords[1] = 0.; + + node3_coords[0] = x0 + 1.; + node3_coords[1] = 1.; + + // Going to add "periodic" constraint between node1 on P0 and P1, so ghost those nodes appropriately + mesh.modification_begin(); + auto & ghosting = mesh.create_ghosting("test_ghosting"); + mesh.modification_end(); + stk::mesh::EntityProcVec add_send; + const int mod = parallel_rank % 2; + if(mod == 0) + { + add_send.push_back(std::make_pair(node1, parallel_rank + 1)); + } + else + { + add_send.push_back(std::make_pair(node1, parallel_rank - 1)); + } + mesh.modification_begin(); + mesh.change_ghosting(ghosting, add_send); + mesh.modification_end(); + + ASSERT_EQ(4u, + stk::mesh::count_selected_entities(meta.universal_part(), mesh.buckets(stk::topology::NODE_RANK))); + { + // Procs with mod == 0 will setup isovars so that the 1-2 edge has a crossing within the snap tolerance + // of node 1. Procs with mod == 1 will have the crossing outside the snap tolerance + *field_data(ls_policy.ls_isovar, node1) = (mod == 0) ? -0.01 : -1.; + *field_data(ls_policy.ls_isovar, node2) = 1.; + *field_data(ls_policy.ls_isovar, node3) = 1.; + + auto other_node1 = mesh.get_entity(stk::topology::NODE_RANK, + (mod == 0) ? starting_id + 3 : starting_id - 3); + krino_mesh->add_periodic_node_pair(node1, other_node1); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + // Periodic constraint should cause interface to snap to node1 so the element is entirely in the + // A phase. + std::vector entities; + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B"), entities); + EXPECT_EQ(0u, entities.size()); + } +} + +void set_level_set(const stk::mesh::BulkData & mesh, FieldRef lsField, const std::vector & nodeIds, const std::vector & nodeLS) +{ + ThrowRequire(nodeIds.size() == nodeLS.size()); + for (size_t i=0; i(lsField, node) = nodeLS[i]; + } +} + +TEST_F(CDMeshTests2D, Two_Tri3_Check_Compatibility_When_Snapping) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + AuxMetaData & aux_meta = AuxMetaData::get(fixture.meta_data()); + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + build_two_tri3_mesh_on_one_or_two_procs(*this, block_part, parallel_rank); + + stk::mesh::create_exposed_block_boundary_sides(mesh, fixture.meta_data().universal_part(), {&aux_meta.exposed_boundary_part()}); + + set_level_set(mesh, ls_policy.ls_isovar, {1,2,3,4} , {-1., -1., 1.e20, 1.}); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + std::vector entities; + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_A"), entities); + EXPECT_EQ(2u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_B"), entities); + EXPECT_EQ(1u, entities.size()); + + //fixture.write_results("Two_Tri3_Check_Compatibility_When_Snapping.e"); +} + +void set_level_sets(const stk::mesh::BulkData & mesh, std::vector & lsFields, const std::vector & nodeIds, const std::vector> & nodeLS) +{ + ThrowRequire(nodeIds.size() == nodeLS.size()); + const size_t numFields = lsFields.size(); + for (size_t i=0; i(lsFields[j], node) = nodeLS[i][j]; + } +} + +typedef CompleteDecompositionFixture > CDMeshTests2DLSPerPhase; +TEST_F(CDMeshTests2DLSPerPhase, Two_Tri3_Check_Compatibility_When_Snapping) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + AuxMetaData & aux_meta = AuxMetaData::get(fixture.meta_data()); + + aux_meta.set_assert_32bit_flag(); + aux_meta.clear_force_64bit_flag(); + + setup_ls_field(); + + auto & blockPart = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&blockPart}); + const auto & sideParts = declare_input_surfaces_touching_block(4, blockPart); + + commit(); + + build_two_tri3_mesh_on_one_or_two_procs_with_sides(*this, blockPart, sideParts, parallel_rank); + + stk::mesh::create_exposed_block_boundary_sides(mesh, fixture.meta_data().universal_part(), {&aux_meta.exposed_boundary_part()}); + + const double eps = 1.e-13; + set_level_sets(mesh, ls_policy.ls_isovars, {1,2,3,4} , {{-1.,1.,1.+eps}, {-1.,1.,1.+eps}, {1.e2,1.,-1.}, {1.,-1.,-1.+eps}}); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + //std::cout << log.get_log() << std::endl; + + std::vector entities; + mesh.get_entities(stk::topology::NODE_RANK, fixture.meta_data().universal_part(), entities); + EXPECT_EQ(7u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_P0"), entities); + EXPECT_EQ(3u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_P1"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_P2"), entities); + EXPECT_EQ(2u, entities.size()); + + //fixture.write_results("Two_Tri3_Check_Compatibility_When_Snapping_LSPerPhase.e"); +} + +typedef CompleteDecompositionFixture CDMeshTests3D; +TEST_F(CDMeshTests3D, Write_Results_No_Side) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + commit(); + + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, parallel_size, false); + + fixture.write_results("Write_Results_No_Side.e"); +} + +TEST_F(CDMeshTests3D, Write_Results_With_Side) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + commit(); + + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, parallel_size); + + fixture.write_results("Write_Results_With_Side.e"); +} + +namespace +{ +void randomize_ls_field(const stk::mesh::BulkData & mesh, + const FieldRef & field, + std::mt19937 & mt, + std::uniform_real_distribution & dist) +{ + auto & node_buckets = mesh.buckets(stk::topology::NODE_RANK); + for(auto && b_ptr : node_buckets) + { + for(auto && node : *b_ptr) + { + double * ls_data = field_data(field, node); + if(ls_data) *ls_data = dist(mt); + } + } +} +void set_ls_field_on_part(const stk::mesh::BulkData & mesh, + const stk::mesh::Part & part, + const FieldRef & ls_field, + const double ls_value) +{ + auto & node_buckets = mesh.get_buckets(stk::topology::NODE_RANK, part); + for(auto && b_ptr : node_buckets) + { + for(auto && node : *b_ptr) + { + double * ls_data = field_data(ls_field, node); + if(ls_data) *ls_data = ls_value; + } + } +} + +} + +TEST_F(CDMeshTests3D, Random_TwoTet4_InternalSideset) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + + if (parallel_size > 2) return; + + // Use a large snap tolerance to make snapping more common since it is a frequent source + // of parallel bugs + cdfemSupport.set_cdfem_edge_tol(0.1); + cdfemSupport.set_simplex_generation_method(CUT_QUADS_BY_GLOBAL_IDENTIFIER); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + register_ls_on_blocks({&block1_part, &block2_part}); + + commit(); + + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, parallel_size); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 5000; +#else + const int num_cases = 1000; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + randomize_ls_field(mesh, ls_policy.ls_isovar, mt, dist); + stk::mesh::communicate_field_data(mesh, {&ls_policy.ls_isovar.field(), &coord_field.field()}); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + if(HasNonfatalFailure()) + { + std::cout << "Failure on iteration " << i << std::endl; + krino_mesh->debug_output(); + std::cout << log.get_log() << std::endl; + std::ostringstream fname; + fname << "Random_TwoTet4_InternalSideset_iter_" << i << ".e"; + fixture.write_results(fname.str()); + ASSERT_TRUE(false); + } + + krino_mesh = std::make_shared(mesh, krino_mesh); + } +} + +typedef CompleteDecompositionFixture CDMeshTestsBboxMesh2D; +TEST_F(CDMeshTestsBboxMesh2D, Random_SnapMesh) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + if (parallel_size > 2) return; + + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + const double approxMinRelativeSize = 0.25; + + setup_ls_field(); + + auto & block1_part = aux_meta.get_part("block_1"); + + register_ls_on_blocks({&block1_part}); + + typename BoundingBoxMesh::BoundingBoxType domain(Vector3d::ZERO, Vector3d(1.,1.,0.)); + const double mesh_size = 1./3.; + fixture.set_domain(domain, mesh_size); + fixture.populate_mesh(); + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part(),&aux_meta.active_part()}); + + double overallMinEdgeLength = std::numeric_limits::max(); + double overallMaxEdgeLength = -std::numeric_limits::max(); + double overallMinVolume = std::numeric_limits::max(); + double overallMaxVolume = -std::numeric_limits::max(); + const double expectedMinLength = mesh_size*approxMinRelativeSize; + const double expectedMinVol = std::pow(mesh_size*approxMinRelativeSize, 2.) / 2.; + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 5000; +#else + const int num_cases = 1000; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + MeshClone::stash_or_restore_mesh(mesh, 0); // restore original uncut mesh + commit(); // new krino_mesh each time + + randomize_ls_field(mesh, ls_policy.ls_isovar, mt, dist); + set_ls_field_on_part(mesh, aux_meta.exposed_boundary_part(), ls_policy.ls_isovar, 1.); + stk::mesh::communicate_field_data(mesh, {&ls_policy.ls_isovar.field(), &coord_field.field()}); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + double minEdgeLength, maxEdgeLength, minVolume, maxVolume; + compute_element_quality(mesh, minEdgeLength, maxEdgeLength, minVolume, maxVolume); + overallMinEdgeLength = std::min(overallMinEdgeLength, minEdgeLength); + overallMaxEdgeLength = std::max(overallMaxEdgeLength, maxEdgeLength); + overallMinVolume = std::min(overallMinVolume, minVolume); + overallMaxVolume = std::max(overallMaxVolume, maxVolume); + + bool failedQuality = false; + if (minVolume < 0.5*expectedMinVol) + { + failedQuality = true; + std::cout << "Failed quality requirements: minEdgeLength=" << minEdgeLength + << ", maxEdgeLength=" << maxEdgeLength + << ", minVolume=" << minVolume + << ", maxVolume=" << maxVolume << std::endl; + } + + if(HasNonfatalFailure() || failedQuality) + { + std::cout << "Failure on iteration " << i << std::endl; + krino_mesh->debug_output(); + std::cout << log.get_log() << std::endl; + std::ostringstream fname; + fname << "Random_SnapTri3_iter_" << i << ".e"; + SimpleStkFixture::write_results(fname.str(), mesh); + ASSERT_TRUE(false); + } + } + std::cout << "Expected quality: minEdgeLength~=" << expectedMinLength << ", minVolume~=" << expectedMinVol << std::endl; + std::cout << "Quality results: minEdgeLength=" << overallMinEdgeLength + << ", maxEdgeLength=" << overallMaxEdgeLength + << ", minVolume=" << overallMinVolume + << ", maxVolume=" << overallMaxVolume << std::endl; +} + +typedef CompleteDecompositionFixture> CDMeshTestsBboxMesh2DLSPerPhase; +TEST_F(CDMeshTestsBboxMesh2DLSPerPhase, Random_SnapMesh) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + if (parallel_size > 2) return; + + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + const double approxMinRelativeSize = 0.25; + + setup_ls_field(); + + auto & block1_part = aux_meta.get_part("block_1"); + + register_ls_on_blocks({&block1_part}); + + typename BoundingBoxMesh::BoundingBoxType domain(Vector3d::ZERO, Vector3d(1.,1.,0.)); + const double mesh_size = 1./3.; + fixture.set_domain(domain, mesh_size); + fixture.populate_mesh(); + fixture.create_domain_sides(); + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part(),&aux_meta.active_part()}); + + const auto & ls_isovars = ls_policy.ls_isovars; + std::vector sync_fields = {&coord_field.field()}; + for(auto && isovar : ls_isovars) sync_fields.push_back(&isovar.field()); + + double overallMinEdgeLength = std::numeric_limits::max(); + double overallMaxEdgeLength = -std::numeric_limits::max(); + double overallMinVolume = std::numeric_limits::max(); + double overallMaxVolume = -std::numeric_limits::max(); + const double expectedMinLength = mesh_size*approxMinRelativeSize; + const double expectedMinVol = std::pow(mesh_size*approxMinRelativeSize, 2.) / 2.; + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 5000; +#else + const int num_cases = 1000; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + MeshClone::stash_or_restore_mesh(mesh, 0); // restore original uncut mesh + commit(); // new krino_mesh each time + + for(auto && isovar : ls_isovars) + { + randomize_ls_field(mesh, isovar, mt, dist); + } + stk::mesh::communicate_field_data(mesh, sync_fields); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << log.get_log() << std::endl; + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + double minEdgeLength, maxEdgeLength, minVolume, maxVolume; + compute_element_quality(mesh, minEdgeLength, maxEdgeLength, minVolume, maxVolume); + overallMinEdgeLength = std::min(overallMinEdgeLength, minEdgeLength); + overallMaxEdgeLength = std::max(overallMaxEdgeLength, maxEdgeLength); + overallMinVolume = std::min(overallMinVolume, minVolume); + overallMaxVolume = std::max(overallMaxVolume, maxVolume); + + if(HasNonfatalFailure()) + { + std::cout << "Failure on iteration " << i << std::endl; + krino_mesh->debug_output(); + std::cout << log.get_log() << std::endl; + std::ostringstream fname; + fname << "Random_SnapTri3_iter_" << i << ".e"; + SimpleStkFixture::write_results(fname.str(), mesh); + ASSERT_TRUE(false); + } + } + std::cout << "Expected quality: minEdgeLength~=" << expectedMinLength << ", minVolume~=" << expectedMinVol << std::endl; + std::cout << "Quality results: minEdgeLength=" << overallMinEdgeLength + << ", maxEdgeLength=" << overallMaxEdgeLength + << ", minVolume=" << overallMinVolume + << ", maxVolume=" << overallMaxVolume << std::endl; +} + +typedef CompleteDecompositionFixture CDMeshTestsBboxMesh3D; +TEST_F(CDMeshTestsBboxMesh3D, Random_SnapMesh) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + if (parallel_size > 2) return; + + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + const double approxMinRelativeSize = 0.25; + + setup_ls_field(); + + auto & block1_part = aux_meta.get_part("block_1"); + + register_ls_on_blocks({&block1_part}); + + typename BoundingBoxMesh::BoundingBoxType domain(Vector3d::ZERO, Vector3d(1.,1.,1.)); + const double mesh_size = 1./3.; + fixture.set_domain(domain, mesh_size); + fixture.populate_mesh(); + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part(),&aux_meta.active_part()}); + + double overallMinEdgeLength = std::numeric_limits::max(); + double overallMaxEdgeLength = -std::numeric_limits::max(); + double overallMinVolume = std::numeric_limits::max(); + double overallMaxVolume = -std::numeric_limits::max(); + const double expectedMinLength = mesh_size*approxMinRelativeSize; + const double expectedMinVol = std::pow(mesh_size*approxMinRelativeSize, 3.) / 6.; + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 1000; +#else + const int num_cases = 250; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%250 == 0) std::cout << "Testing random configuration " << i << std::endl; + + MeshClone::stash_or_restore_mesh(mesh, 0); // restore original uncut mesh + commit(); // new krino_mesh each time + + randomize_ls_field(mesh, ls_policy.ls_isovar, mt, dist); + set_ls_field_on_part(mesh, aux_meta.exposed_boundary_part(), ls_policy.ls_isovar, 1.); + stk::mesh::communicate_field_data(mesh, {&ls_policy.ls_isovar.field(), &coord_field.field()}); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + double minEdgeLength, maxEdgeLength, minVolume, maxVolume; + compute_element_quality(mesh, minEdgeLength, maxEdgeLength, minVolume, maxVolume); + overallMinEdgeLength = std::min(overallMinEdgeLength, minEdgeLength); + overallMaxEdgeLength = std::max(overallMaxEdgeLength, maxEdgeLength); + overallMinVolume = std::min(overallMinVolume, minVolume); + overallMaxVolume = std::max(overallMaxVolume, maxVolume); + + bool failedQuality = false; + if (minVolume < 0.5*expectedMinVol) + { + failedQuality = true; + std::cout << "Failed quality requirements: minEdgeLength=" << minEdgeLength + << ", maxEdgeLength=" << maxEdgeLength + << ", minVolume=" << minVolume + << ", maxVolume=" << maxVolume << std::endl; + } + + if(HasNonfatalFailure() || failedQuality) + { + std::cout << "Failure on iteration " << i << std::endl; + krino_mesh->debug_output(); + std::cout << log.get_log() << std::endl; + std::ostringstream fname; + fname << "Random_SnapTet4_iter_" << i << ".e"; + SimpleStkFixture::write_results(fname.str(), mesh); + ASSERT_TRUE(false); + } + } + std::cout << "Expected quality: minEdgeLength~=" << expectedMinLength << ", minVolume~=" << expectedMinVol << std::endl; + std::cout << "Actual quality results: minEdgeLength=" << overallMinEdgeLength + << ", maxEdgeLength=" << overallMaxEdgeLength + << ", minVolume=" << overallMinVolume + << ", maxVolume=" << overallMaxVolume << std::endl; +} + +typedef CompleteDecompositionFixture CDMeshTestsBboxMesh3DBCC; +TEST_F(CDMeshTestsBboxMesh3DBCC, Random_SnapMesh) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + if (parallel_size > 2) return; + + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + const double approxMinRelativeSize = 0.25; + + setup_ls_field(); + + auto & block1_part = aux_meta.get_part("block_1"); + + register_ls_on_blocks({&block1_part}); + + typename BoundingBoxMesh::BoundingBoxType domain(Vector3d::ZERO, Vector3d(1.,1.,1.)); + const double mesh_size = 1./3.; + fixture.set_domain(domain, mesh_size); + fixture.set_mesh_structure_type(BCC_BOUNDING_BOX_MESH); + fixture.populate_mesh(); + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part(),&aux_meta.active_part()}); + + const double BCCsize = std::sqrt(3.)/2.*mesh_size; + double overallMinEdgeLength = std::numeric_limits::max(); + double overallMaxEdgeLength = -std::numeric_limits::max(); + double overallMinVolume = std::numeric_limits::max(); + double overallMaxVolume = -std::numeric_limits::max(); + const double expectedMinLength = BCCsize*approxMinRelativeSize; + const double expectedMinVol = std::pow(BCCsize*approxMinRelativeSize, 3.) / (6.*std::sqrt(2.)); + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 1000; +#else + const int num_cases = 250; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%250 == 0) std::cout << "Testing random configuration " << i << std::endl; + + MeshClone::stash_or_restore_mesh(mesh, 0); // restore original uncut mesh + commit(); // new krino_mesh each time + + randomize_ls_field(mesh, ls_policy.ls_isovar, mt, dist); + set_ls_field_on_part(mesh, aux_meta.exposed_boundary_part(), ls_policy.ls_isovar, 1.); + stk::mesh::communicate_field_data(mesh, {&ls_policy.ls_isovar.field(), &coord_field.field()}); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + double minEdgeLength, maxEdgeLength, minVolume, maxVolume; + compute_element_quality(mesh, minEdgeLength, maxEdgeLength, minVolume, maxVolume); + overallMinEdgeLength = std::min(overallMinEdgeLength, minEdgeLength); + overallMaxEdgeLength = std::max(overallMaxEdgeLength, maxEdgeLength); + overallMinVolume = std::min(overallMinVolume, minVolume); + overallMaxVolume = std::max(overallMaxVolume, maxVolume); + + bool failedQuality = false; + if (minVolume < 0.5*expectedMinVol) + { + failedQuality = true; + std::cout << "Failed quality requirements: minEdgeLength=" << minEdgeLength + << ", maxEdgeLength=" << maxEdgeLength + << ", minVolume=" << minVolume + << ", maxVolume=" << maxVolume << std::endl; + } + + if(HasNonfatalFailure() || failedQuality) + { + std::cout << "Failure on iteration " << i << std::endl; + krino_mesh->debug_output(); + std::cout << log.get_log() << std::endl; + std::ostringstream fname; + fname << "Random_SnapTet4_iter_" << i << ".e"; + SimpleStkFixture::write_results(fname.str(), mesh); + ASSERT_TRUE(false); + } + } + std::cout << "Expected quality: minEdgeLength~=" << expectedMinLength << ", minVolume~=" << expectedMinVol << std::endl; + std::cout << "Actual quality results: minEdgeLength=" << overallMinEdgeLength + << ", maxEdgeLength=" << overallMaxEdgeLength + << ", minVolume=" << overallMinVolume + << ", maxVolume=" << overallMaxVolume << std::endl; +} + +TEST_F(CDMeshTests2DLSPerPhase, Tri3_3LS_SnappedTriplePoint) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + cdfemSupport.set_cdfem_edge_tol(0.15); + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + mesh.modification_begin(); + { + stk::mesh::PartVector elem_parts; + elem_parts.push_back(&block_part); + elem_parts.push_back(&aux_meta.active_part()); + std::vector node_ids = {1, 2, 3}; + + create_element(elem_parts, 1, node_ids); + } + mesh.modification_end(); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + + double * node1_coords = field_data(coord_field, node1); + double * node2_coords = field_data(coord_field, node2); + double * node3_coords = field_data(coord_field, node3); + + node1_coords[0] = 0.; + node1_coords[1] = 0.; + + node2_coords[0] = 1.; + node2_coords[1] = 0.; + + node3_coords[0] = 0.; + node3_coords[1] = 1.; + + const auto & ls_isovars = ls_policy.ls_isovars; + *field_data(ls_isovars[0], node1) = 0.; + *field_data(ls_isovars[0], node2) = 0.; + *field_data(ls_isovars[0], node3) = 0.; + + *field_data(ls_isovars[1], node1) = 0.1; + *field_data(ls_isovars[1], node2) = -0.2; + *field_data(ls_isovars[1], node3) = -0.01; + + *field_data(ls_isovars[2], node1) = 0.2; + *field_data(ls_isovars[2], node2) = -0.25; + *field_data(ls_isovars[2], node3) = -0.005; + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + std::vector entities; + + // This assumes that the element is cut first by the (0,1) interface, + // then the (0, 2) virtual interface, and then the (1, 2) interface. + // THere are other valid decompositions if the element is cut in a different order. + // Should have added 3 nodes at the cutting locations + mesh.get_entities(stk::topology::NODE_RANK, meta.universal_part(), entities); + EXPECT_EQ(5u, entities.size()); + mesh.get_entities(stk::topology::NODE_RANK, aux_meta.active_part(), entities); + EXPECT_EQ(5u, entities.size()); + + mesh.get_entities(stk::topology::EDGE_RANK, aux_meta.active_part() & + aux_meta.get_part("surface_block_1_P0_P1") & aux_meta.get_part("surface_block_1_P1_P0"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::EDGE_RANK, aux_meta.active_part() & + aux_meta.get_part("surface_block_1_P1_P2") & aux_meta.get_part("surface_block_1_P2_P1"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::EDGE_RANK, aux_meta.active_part() & + aux_meta.get_part("surface_block_1_P0_P2") & aux_meta.get_part("surface_block_1_P2_P0"), entities); + EXPECT_EQ(0u, entities.size()); + + // Should be 3 conformal elements plus the parent element + mesh.get_entities(stk::topology::ELEMENT_RANK, meta.universal_part(), entities); + EXPECT_EQ(4u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.active_part(), entities); + EXPECT_EQ(3u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, !aux_meta.active_part(), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_P0"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_P1"), entities); + EXPECT_EQ(1u, entities.size()); + mesh.get_entities(stk::topology::ELEMENT_RANK, aux_meta.get_part("block_1_P2"), entities); + EXPECT_EQ(1u, entities.size()); +} + +TEST_F(CDMeshTests2DLSPerPhase, Tri3_3LS_TriplePointDebug) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + + if (parallel_size > 1) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + cdfemSupport.set_cdfem_edge_tol(0.01); + + setup_ls_field(); + + auto & block_part = declare_input_block("block_1", stk::topology::TRIANGLE_3_2D); + register_ls_on_blocks({&block_part}); + + commit(); + + mesh.modification_begin(); + { + stk::mesh::PartVector elem_parts; + elem_parts.push_back(&block_part); + elem_parts.push_back(&aux_meta.active_part()); + std::vector node_ids = {1, 2, 3}; + + create_element(elem_parts, 1, node_ids); + } + mesh.modification_end(); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto node1 = mesh.get_entity(stk::topology::NODE_RANK, 1); + const auto node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + const auto node3 = mesh.get_entity(stk::topology::NODE_RANK, 3); + + double * node1_coords = field_data(coord_field, node1); + double * node2_coords = field_data(coord_field, node2); + double * node3_coords = field_data(coord_field, node3); + + node1_coords[0] = 0.; + node1_coords[1] = 0.; + + node2_coords[0] = 1.; + node2_coords[1] = 0.; + + node3_coords[0] = 0.; + node3_coords[1] = 1.; + + const auto & ls_isovars = ls_policy.ls_isovars; + *field_data(ls_isovars[0], node1) = -0.2; + *field_data(ls_isovars[1], node1) = 0.1; + *field_data(ls_isovars[2], node1) = 0.6; + + *field_data(ls_isovars[0], node2) = 0.7; + *field_data(ls_isovars[1], node2) = -0.5; + *field_data(ls_isovars[2], node2) = 0.4; + + *field_data(ls_isovars[0], node3) = 0.1; + *field_data(ls_isovars[1], node3) = 0.3; + *field_data(ls_isovars[2], node3) = -0.5; + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + // Test output, but remove output unless actually debugging. + fixture.write_results("debug_2d.e"); + std::remove("debug_2d.e"); +} + +typedef CompleteDecompositionFixture > CDMeshTests2DLSPerPhase; +TEST_F(CDMeshTests2DLSPerPhase, Random_TwoTri3_InternalSideset_Snap) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + + setup_ls_field(); + + const stk::topology tri3 = stk::topology::TRIANGLE_3_2D; + auto & block1_part = declare_input_block("block_1", tri3); + auto & block2_part = declare_input_block("block_2", tri3); + auto & surface_part = declare_input_surface("surface_1", tri3.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + register_ls_on_blocks({&block1_part, &block2_part}); + + commit(); + + build_two_tri3_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, parallel_size); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto & ls_isovars = ls_policy.ls_isovars; + std::vector sync_fields = {&coord_field.field()}; + for(auto && isovar : ls_isovars) sync_fields.push_back(&isovar.field()); + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 5000; +#else + const int num_cases = 1000; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + MeshClone::stash_or_restore_mesh(mesh, 0); // restore original uncut mesh + commit(); // new krino_mesh each time + + for(auto && isovar : ls_isovars) + { + randomize_ls_field(mesh, isovar, mt, dist); + } + stk::mesh::communicate_field_data(mesh, sync_fields); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << log.get_log() << std::endl; + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + if (false) + { + std::ostringstream fname; + fname << "Random_TwoTri3_InternalSideset_Snap_iter_" << i << ".e"; + fixture.write_results(fname.str()); + } + + if(HasNonfatalFailure()) + { + std::cout << "Failure on iteration " << i << std::endl; + std::ostringstream fname; + fname << "Random_TwoTri3_InternalSideset_Snap_iter_" << i << ".e"; + fixture.write_results(fname.str()); + ASSERT_TRUE(false); + } + + krino_mesh = std::make_shared(mesh, krino_mesh); + } +} + +typedef CompleteDecompositionFixture > CDMeshTests3DLSPerInterface; +TEST_F(CDMeshTests3DLSPerInterface, Random_OneTet4) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + aux_meta.set_assert_32bit_flag(); + aux_meta.clear_force_64bit_flag(); + + // Use a large snap tolerance to make snapping more common since it is a frequent source + // of parallel bugs + cdfemSupport.set_cdfem_edge_tol(0.1); + cdfemSupport.set_simplex_generation_method(CUT_QUADS_BY_GLOBAL_IDENTIFIER); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + + register_ls_on_blocks({&block1_part}); + + commit(); + + build_one_tet4_mesh(*this, block1_part, parallel_rank, parallel_size); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto & ls_isovars = ls_policy.ls_isovars; + std::vector sync_fields = {&coord_field.field()}; + for(auto && isovar : ls_isovars) sync_fields.push_back(&isovar.field()); + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 5000; +#else + const int num_cases = 1000; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + MeshClone::stash_or_restore_mesh(mesh, 0); // restore original uncut mesh + commit(); // new krino_mesh each time + + for(auto && isovar : ls_isovars) + { + randomize_ls_field(mesh, isovar, mt, dist); + } + stk::mesh::communicate_field_data(mesh, sync_fields); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << log.get_log() << std::endl; + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + if(HasNonfatalFailure()) + { + std::cout << log.get_log() << std::endl; + std::cout << "Failure on iteration " << i << std::endl; + std::ostringstream fname; + fname << "Random_TwoTet4_InternalSideset_iter_" << i << ".e"; + fixture.write_results(fname.str()); + ASSERT_TRUE(false); + } + + krino_mesh = std::make_shared(mesh, krino_mesh); + } +} + +typedef CompleteDecompositionFixture > CDMeshTests3DLSPerInterface; +TEST_F(CDMeshTests3DLSPerInterface, OneTet4_CutBasedOnNearestEdgeCut) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 1) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + aux_meta.set_assert_32bit_flag(); + aux_meta.clear_force_64bit_flag(); + + // Use a large snap tolerance to make snapping more common since it is a frequent source + // of parallel bugs + cdfemSupport.set_cdfem_edge_tol(0.001); + cdfemSupport.set_simplex_generation_method(CUT_QUADS_BY_NEAREST_EDGE_CUT); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + + register_ls_on_blocks({&block1_part}); + + commit(); + + build_one_tet4_mesh(*this, block1_part, parallel_rank, parallel_size); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const std::array nodes = {{ mesh.get_entity(stk::topology::NODE_RANK, 1), mesh.get_entity(stk::topology::NODE_RANK, 2), mesh.get_entity(stk::topology::NODE_RANK, 3), mesh.get_entity(stk::topology::NODE_RANK, 4) }}; + const auto & ls_isovars = ls_policy.ls_isovars; + *field_data(ls_isovars[0], nodes[0]) = -1; + *field_data(ls_isovars[0], nodes[1]) = 1.; + *field_data(ls_isovars[0], nodes[2]) = 2.; + *field_data(ls_isovars[0], nodes[3]) = 3.; + + commit(); + decompose_mesh(); + + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + EXPECT_EQ(1u+1u, mesh.num_elements(nodes[0])); + EXPECT_EQ(1u+1u, mesh.num_elements(nodes[1])); + EXPECT_EQ(2u+1u, mesh.num_elements(nodes[2])); + EXPECT_EQ(3u+1u, mesh.num_elements(nodes[3])); + + // regression test + const ScaledJacobianQualityMetric qualityMetric; + const double quality = determine_quality(mesh, krino_mesh->get_active_part(), qualityMetric); + const double goldQuality = 0.21; + EXPECT_GT(quality, goldQuality); +} + +typedef CompleteDecompositionFixture > CDMeshTests3DLSPerPhase; +TEST_F(CDMeshTests3DLSPerPhase, Random_TwoTet4_InternalSideset) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + // Use a large snap tolerance to make snapping more common since it is a frequent source + // of parallel bugs + cdfemSupport.set_cdfem_edge_tol(0.1); + cdfemSupport.set_simplex_generation_method(CUT_QUADS_BY_GLOBAL_IDENTIFIER); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + register_ls_on_blocks({&block1_part, &block2_part}); + + commit(); + + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, parallel_size); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto & ls_isovars = ls_policy.ls_isovars; + std::vector sync_fields = {&coord_field.field()}; + for(auto && isovar : ls_isovars) sync_fields.push_back(&isovar.field()); + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 5000; +#else + const int num_cases = 1000; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + for(auto && isovar : ls_isovars) + { + randomize_ls_field(mesh, isovar, mt, dist); + } + stk::mesh::communicate_field_data(mesh, sync_fields); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << log.get_log() << std::endl; + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + if(HasNonfatalFailure()) + { + std::cout << "Failure on iteration " << i << std::endl; + std::ostringstream fname; + fname << "Random_TwoTet4_InternalSideset_iter_" << i << ".e"; + fixture.write_results(fname.str()); + ASSERT_TRUE(false); + } + + krino_mesh = std::make_shared(mesh, krino_mesh); + } +} + +TEST_F(CDMeshTests3DLSPerPhase, Random_TwoTet4_InternalSideset_Snap) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + cdfemSupport.set_cdfem_edge_degeneracy_handling(SNAP_TO_INTERFACE_WHEN_QUALITY_ALLOWS_THEN_SNAP_TO_NODE); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + register_ls_on_blocks({&block1_part, &block2_part}); + + commit(); + + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, parallel_size); + + stk::mesh::create_exposed_block_boundary_sides(mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto & ls_isovars = ls_policy.ls_isovars; + std::vector sync_fields = {&coord_field.field()}; + for(auto && isovar : ls_isovars) sync_fields.push_back(&isovar.field()); + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); +#ifdef NDEBUG + const int num_cases = 5000; +#else + const int num_cases = 1000; +#endif + for(int i=0; i < num_cases; ++i) + { + if (i%1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + MeshClone::stash_or_restore_mesh(mesh, 0); // restore original uncut mesh + commit(); // new krino_mesh each time + + for(auto && isovar : ls_isovars) + { + randomize_ls_field(mesh, isovar, mt, dist); + } + stk::mesh::communicate_field_data(mesh, sync_fields); + + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << log.get_log() << std::endl; + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + EXPECT_TRUE(check_induced_parts(mesh)); + EXPECT_TRUE(check_face_and_edge_ownership(mesh)); + EXPECT_TRUE(check_face_and_edge_relations(mesh)); + EXPECT_TRUE(check_shared_entity_nodes(mesh)); + EXPECT_TRUE(krino_mesh->check_element_side_parts()); + + if(HasNonfatalFailure()) + { + std::cout << "Failure on iteration " << i << std::endl; + std::ostringstream fname; + fname << "Random_TwoTet4_InternalSideset_iter_" << i << ".e"; + fixture.write_results(fname.str()); + ASSERT_TRUE(false); + } + + krino_mesh = std::make_shared(mesh, krino_mesh); + } +} + +TEST_F(CDMeshTests3DLSPerPhase, RestoreAfterFailedStep) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + + if (parallel_size > 2) return; + + stk::mesh::BulkData & mesh = fixture.bulk_data(); + stk::mesh::MetaData & meta = fixture.meta_data(); + AuxMetaData & aux_meta = AuxMetaData::get(meta); + + // Use a large snap tolerance to make snapping more common since it is a frequent source + // of parallel bugs + cdfemSupport.set_cdfem_edge_tol(0.1); + cdfemSupport.set_simplex_generation_method(CUT_QUADS_BY_GLOBAL_IDENTIFIER); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", + tet4.side_topology(), + {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + register_ls_on_blocks({&block1_part, &block2_part}); + + commit(); + + build_two_tet4_mesh_np2( + *this, block1_part, block2_part, surface_part, parallel_rank, parallel_size); + + stk::mesh::create_exposed_block_boundary_sides( + mesh, meta.universal_part(), {&aux_meta.exposed_boundary_part()}); + + const auto & ls_isovars = ls_policy.ls_isovars; + std::vector sync_fields = {&coord_field.field()}; + for (auto && isovar : ls_isovars) + sync_fields.push_back(&isovar.field()); + + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution dist(-1., 1.); + for (int i = 0; i < 100; ++i) + { + if (i % 1000 == 0) std::cout << "Testing random configuration " << i << std::endl; + + // We want to test restoring the mesh after failed time steps for a variety of + // random decompositions: + // 1) Do a random decomposition and stash the mesh to act like a successful time + // step. + // 2) Do a second random decomposition to simulate the changes of a time step that + // will fail. + // 3) Restore the mesh to the stashed mesh, and rebuild the CDMesh, confirm that + // does not fail. + + auto randomize_all_fields = [&]() + { + for (auto && isovar : ls_isovars) + { + randomize_ls_field(mesh, isovar, mt, dist); + } + stk::mesh::communicate_field_data(mesh, sync_fields); + }; + + auto run_decomp = [&]() + { + try + { + decompose_mesh(); + } + catch (const std::exception & exception) + { + std::cout << log.get_log() << std::endl; + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + }; + // Step 1. + randomize_all_fields(); + run_decomp(); + krino::MeshClone::stash_or_restore_mesh(mesh, i); + krino_mesh = std::make_shared(mesh, krino_mesh); + + // Step 2. + randomize_all_fields(); + run_decomp(); + krino::MeshClone::stash_or_restore_mesh(mesh, i); + + // Step 3. + ASSERT_NO_THROW(krino_mesh->rebuild_after_rebalance()); + krino_mesh = std::make_shared(mesh, krino_mesh); + } +} + +TEST_F(CDMeshTests3D, Rebalance_with_rcb) +{ + run_rebalance_with("rcb"); +} + +TEST_F(CDMeshTests3D, Rebalance_with_parmetis) +{ + run_rebalance_with("parmetis"); +} + +typedef CompleteDecompositionFixture NonconformalAdaptivityTest; +TEST_F(NonconformalAdaptivityTest, InternalSidePositivePermutationNonOwnedElement) +{ + /* This tests that percept can correctly handle an internal side where the element + * with the same owning proc as the side has a negative permutation and the second + * connected element (with a different owning processor) has a positive permutation. + * In serial it just tests use of the adaptivity interface. + */ + + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + stk::mesh::BulkData & mesh = fixture.bulk_data(); + + if (parallel_size > 2) return; + + cdfemSupport.set_cdfem_edge_tol(0.1); + cdfemSupport.set_simplex_generation_method(CUT_QUADS_BY_GLOBAL_IDENTIFIER); + + setup_ls_field(); + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + register_ls_on_blocks({&block1_part, &block2_part}); + + auto & aux_meta = AuxMetaData::get(fixture.meta_data()); + FieldRef marker_field = + aux_meta.register_field("refine_marker", FieldType::INTEGER, stk::topology::ELEMENT_RANK, + 1u, 1, fixture.meta_data().universal_part()); + + auto & meta = fixture.meta_data(); + auto & active_part = aux_meta.active_part(); + stk::diag::TimerSet enabledTimerSet(0); + stk::diag::Timer root_timer = createRootTimer("test", enabledTimerSet); + HAdapt::setup(meta, active_part, root_timer); + + commit(); + + const bool build_all_on_P0 = false; + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_part, parallel_rank, + parallel_size, true, build_all_on_P0); + + auto elem_1 = mesh.get_entity(stk::topology::ELEMENT_RANK, 1u); + int & elem_1_marker = *field_data(marker_field, elem_1); + elem_1_marker = 1; + + if(parallel_size == 2) + { + stk::mesh::EntityProcVec changes; + if(parallel_rank == 0) + { + changes.push_back(std::make_pair(mesh.get_entity(stk::topology::FACE_RANK, 7), 1)); + } + mesh.change_entity_owner(changes); + } + + EXPECT_NO_THROW(HAdapt::do_adaptive_refinement(meta, marker_field.name())); +} + +typedef CompleteDecompositionFixture MeshCloneTest; +TEST_F(MeshCloneTest, FaceOwnershipAndPartChangeBetweenClones) +{ + /* + * This test regression tests a bug where a shared face both changed parts and + * parallel owners between calls to MeshClone::stash_or_restore_mesh(), leading to a + * throw when copying field data to the clone mesh because the parts of the face were not + * updated on the clone. + */ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + const int parallel_rank = stk::parallel_machine_rank(pm); + stk::mesh::BulkData & mesh = fixture.bulk_data(); + + if (parallel_size != 2) return; + + const stk::topology tet4 = stk::topology::TETRAHEDRON_4; + auto & block1_part = declare_input_block("block_1", tet4); + auto & block2_part = declare_input_block("block_2", tet4); + auto & surface_1_part = declare_input_surface("surface_1", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + auto & surface_2_part = declare_input_surface("surface_2", tet4.side_topology(), {block1_part.mesh_meta_data_ordinal(), block2_part.mesh_meta_data_ordinal()}); + + auto & aux_meta = AuxMetaData::get(fixture.meta_data()); + aux_meta.register_field("side_field", FieldType::REAL, stk::topology::FACE_RANK, + 1u, 1u, surface_1_part); + + commit(); + + build_two_tet4_mesh_np2(*this, block1_part, block2_part, surface_1_part, parallel_rank, parallel_size); + + // Stash the initial mesh. + ASSERT_NO_THROW(MeshClone::stash_or_restore_mesh(mesh, 0)); + + // Change the parallel owner of the shared face, then change its surface part from surface_1 to + // surface_2. + const auto side_1 = mesh.get_entity(stk::topology::FACE_RANK, 7); + const auto current_owner = mesh.parallel_owner_rank(side_1); + stk::mesh::EntityProcVec owner_changes; + if(parallel_rank == current_owner) + { + owner_changes.emplace_back(side_1, (current_owner + 1) % 2); + } + mesh.change_entity_owner(owner_changes); + ASSERT_TRUE(mesh.is_valid(side_1)); + const auto new_owner = mesh.parallel_owner_rank(side_1); + ASSERT_NE(new_owner, current_owner); + + mesh.modification_begin(); + if(new_owner == parallel_rank) + { + mesh.change_entity_parts(side_1, stk::mesh::ConstPartVector{&surface_2_part}, stk::mesh::ConstPartVector{&surface_1_part}); + } + mesh.modification_end(); + + // Confirm that updating the stash mesh succeeds + try + { + MeshClone::stash_or_restore_mesh(mesh, 1); + } + catch (const std::exception & exception) + { + std::cout << "Decomposing mesh failed with exception:\n"; + std::cout << exception.what() << "\n"; + ASSERT_TRUE(false); + } + + // Change the shared face part back to surface_1, then restore from the stash and + // confirm that the parts of the face are correct. + mesh.modification_begin(); + if(new_owner == parallel_rank) + { + mesh.change_entity_parts(side_1, stk::mesh::ConstPartVector{&surface_1_part}, stk::mesh::ConstPartVector{&surface_2_part}); + } + mesh.modification_end(); + ASSERT_TRUE(mesh.bucket(side_1).member(surface_1_part)); + MeshClone::stash_or_restore_mesh(mesh, 1); + const auto side_1_new = mesh.get_entity(stk::topology::FACE_RANK, 7); + EXPECT_TRUE(mesh.bucket(side_1_new).member(surface_2_part)); + EXPECT_FALSE(mesh.bucket(side_1_new).member(surface_1_part)); +} + +} diff --git a/packages/krino/krino/unit_tests/Akri_Unit_ContourElement.cpp b/packages/krino/krino/unit_tests/Akri_Unit_ContourElement.cpp new file mode 100644 index 000000000000..372f6cdbb820 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_ContourElement.cpp @@ -0,0 +1,83 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + +#include + +namespace krino { + +TEST(SingleElementFixture, LS_Element_Tet4) +{ + stk::topology tet4 = stk::topology::TETRAHEDRON_4; + SingleElementFixture test_fixture(tet4); + + test_fixture.generate_mesh(); + stk::mesh::MetaData & meta = test_fixture.stk_fixture.meta_data(); + stk::mesh::BulkData & bulk = test_fixture.stk_fixture.bulk_data(); + + const unsigned ndim = meta.spatial_dimension(); + const stk::mesh::FieldBase & coords_field = test_fixture.coord_field.field(); + const stk::mesh::FieldBase & scalar_field = test_fixture.scalar_field.field(); + + stk::mesh::Entity elem = test_fixture.my_elem; + const stk::mesh::Entity* elem_nodes = bulk.begin_nodes(elem); + const unsigned npe = bulk.num_nodes(elem); + + // Set coordinates + const double coords[4][3] = {{0.,0.,0.},{1.,0.,0.},{0.,1.,0.},{0.,0.,4.}}; + for (unsigned node=0; node(stk::mesh::field_data(coords_field, elem_nodes[node])); + for (unsigned dim=0; dim(stk::mesh::field_data(scalar_field, elem_nodes[node])); + *scalar_data = isovar[node]; + } + + // + // Create facets on 0 isosurface + // + + const double isoval = 0.0; + krino::ContourElement ls_elem( bulk, elem, coords_field, scalar_field, isoval ); + const double length_scale = 1.0; // Used for snapping facets to vertices of element when distance is small compared to length_scale + ls_elem.compute_subelement_decomposition(length_scale); + + Faceted_Surface faceted_surface("tmp"); + ls_elem.build_subelement_facets(faceted_surface); + const auto & facets = faceted_surface.get_facets(); + + ASSERT_EQ(1u, facets.size()); + + Facet & facet = *facets[0]; + + EXPECT_EQ(2.0, facet.facet_vertex(0)[2]); + EXPECT_EQ(2.0, facet.facet_vertex(1)[2]); + EXPECT_EQ(2.0, facet.facet_vertex(2)[2]); + + const Vector3d normal = facet.facet_normal(); + const double area = facet.facet_area(); + + EXPECT_EQ(0.0, normal[0]); + EXPECT_EQ(0.0, normal[1]); + EXPECT_EQ(1.0, normal[2]); + + EXPECT_EQ(0.125, area); +} + +} diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Element.cpp b/packages/krino/krino/unit_tests/Akri_Unit_Element.cpp new file mode 100644 index 000000000000..d799a4402563 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Element.cpp @@ -0,0 +1,718 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +template +class Mesh_Element_Fixture : public ::testing::Test +{ +public: + Mesh_Element_Fixture() : + elem_fixture(static_cast(TOPO)), + krino_mesh(elem_fixture.stk_fixture.bulk_data(), std::shared_ptr()), + interfaceGeometry(krino_mesh.get_active_part(),krino_mesh.get_cdfem_support(), krino_mesh.get_phase_support()) + { + elem_fixture.generate_mesh(); + check_entity_counts(); + Phase_Support::get(stk_meta()).add_decomposed_part(stk_meta().universal_part()); + Phase_Support::set_one_levelset_per_phase(false); + const NodeToCapturedDomainsMap nodesToCapturedDomains; + interfaceGeometry.prepare_to_process_elements(krino_mesh.stk_bulk(), nodesToCapturedDomains); + } + virtual ~Mesh_Element_Fixture() {}; + void check_entity_counts() + { + std::vector entities; + stk::mesh::get_entities(stk_bulk(), stk::topology::ELEMENT_RANK, entities); + ASSERT_EQ(1u, entities.size()); + stk::mesh::get_entities(stk_bulk(), stk::topology::NODE_RANK, entities); + ASSERT_EQ(elem_fixture.my_topology.num_nodes(), entities.size()); + } + stk::mesh::BulkData & stk_bulk() { return elem_fixture.stk_fixture.bulk_data(); } + stk::mesh::MetaData & stk_meta() { return elem_fixture.stk_fixture.meta_data(); } + stk::mesh::Entity elem() { return elem_fixture.my_elem; } + + unsigned node_ordinal(const stk::mesh::Entity node) + { + const stk::mesh::Entity * const elem_nodes = stk_bulk().begin_nodes(elem()); + const unsigned num_nodes = stk_bulk().num_nodes(elem()); + const stk::mesh::Entity * found = std::find(elem_nodes, elem_nodes+num_nodes, node); + return found - elem_nodes; + } + + void generate_nonconformal_elements() { + krino_mesh.generate_nonconformal_elements(); + } + void triangulate() + { + Mesh_Element & meshElem = get_mesh_element(); + meshElem.create_cutter(krino_mesh, interfaceGeometry); // update cutter with for edges that now have crossings found + get_mesh_element().create_cutter(krino_mesh, interfaceGeometry); + krino_mesh.triangulate(interfaceGeometry); + } + + LevelSetElementCutter & get_cutter() + { + Mesh_Element & meshElem = get_mesh_element(); + meshElem.create_cutter(krino_mesh, interfaceGeometry); + LevelSetElementCutter * cutter = dynamic_cast(meshElem.get_cutter()); + ThrowRequire(cutter); + return *cutter; + } + + void find_edge_crossings(const std::vector & node_LS_values) + { + std::vector > nodesIsovar(2); + LevelSetElementCutter & cutter = get_cutter(); + for (unsigned iEdge=0; iEdge > & node_LS_values) + { + std::vector > nodesIsovar(2); + LevelSetElementCutter & cutter = get_cutter(); + for (unsigned iEdge=0; iEdge & node_LS_values) + { + generate_nonconformal_elements(); + find_edge_crossings(node_LS_values); + triangulate(); + } + + void generate_mesh_element_and_cutter(const std::vector > & node_LS_values) + { + generate_nonconformal_elements(); + find_edge_crossings(node_LS_values); + triangulate(); + } + + Mesh_Element & get_mesh_element() { + return *krino_mesh.elements.front(); + } + + std::array get_edge_nodes(const unsigned edgeOrdinal) + { + const auto & nodes = get_mesh_element().get_nodes(); + const unsigned * edge_node_ordinals = get_edge_node_ordinals(elem_fixture.my_topology, edgeOrdinal); + return {{nodes[edge_node_ordinals[0]], nodes[edge_node_ordinals[1]]}}; + } + + Segment3d get_edge_segment(const unsigned edge_ord) + { + const auto & edgeNodes = get_edge_nodes(edge_ord); + return Segment3d(get_mesh_element().get_node_parametric_coords(edgeNodes[0]), + get_mesh_element().get_node_parametric_coords(edgeNodes[1])); + } + + SingleElementFixture elem_fixture; + CDMesh krino_mesh; + LevelSetInterfaceGeometry interfaceGeometry; +}; + +typedef Mesh_Element_Fixture Mesh_Element_Tri3; +TEST_F(Mesh_Element_Tri3, generate) +{ + generate_nonconformal_elements(); + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_EQ(elem(), mesh_elem.entity()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + ASSERT_EQ(3u, mesh_nodes.size()); + const stk::mesh::Entity * const elem_nodes = stk_bulk().begin_nodes(elem()); + for(unsigned i=0; i < mesh_nodes.size(); ++i) + { + EXPECT_EQ(mesh_nodes[i]->entity(), elem_nodes[i]); + } +} + +typedef Mesh_Element_Tri3 Mesh_Element_Tri3_One_LS; +TEST_F(Mesh_Element_Tri3_One_LS, All_Nodes_Positive) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + const std::vector node_LS_values(3, 1.); + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_FALSE(mesh_elem.have_interface()); + + for(unsigned i=0; i < mesh_elem.topology().num_edges(); ++i) + { + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(InterfaceID(0,0), get_edge_segment(i))); + } +} + +TEST_F(Mesh_Element_Tri3_One_LS, All_Nodes_Negative) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + const std::vector node_LS_values(3, -1.); + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_FALSE(mesh_elem.have_interface()); + + for(unsigned i=0; i < mesh_elem.topology().num_edges(); ++i) + { + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(InterfaceID(0,0), get_edge_segment(i))); + } +} + +TEST_F(Mesh_Element_Tri3_One_LS, Node0_Pos_Node1_2_Neg) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + std::vector node_LS_values(3); + node_LS_values[0] = 1.; + node_LS_values[1] = -0.5; + node_LS_values[2] = -0.5; + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + EXPECT_EQ(3u, mesh_nodes.size()); + EXPECT_EQ(+1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[2]->get_node_sign()); + + // NOTE: Edge 0 is from nodes 0->1, Edge 1 is 1->2, Edge 2 is 2->0 + EXPECT_DOUBLE_EQ(2./3., mesh_elem.interface_crossing_position(iface, get_edge_segment(0))); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(1))); + EXPECT_DOUBLE_EQ(1./3., mesh_elem.interface_crossing_position(iface, get_edge_segment(2))); +} + +TEST_F(Mesh_Element_Tri3_One_LS, Node0_Snapped_Node1_2_Pos) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + std::vector node_LS_values(3); + node_LS_values[0] = 0; + node_LS_values[1] = 1.; + node_LS_values[2] = 1.; + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_FALSE(mesh_elem.have_interface()); + + // NOTE: Edge 0 is from nodes 0->1, Edge 1 is 1->2, Edge 2 is 2->0 + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(0))); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(1))); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(2))); +} + +TEST_F(Mesh_Element_Tri3_One_LS, Node0_Snapped_Node1_2_Neg) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + std::vector node_LS_values(3); + node_LS_values[0] = 0; + node_LS_values[1] = -1.; + node_LS_values[2] = -1.; + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + EXPECT_EQ(3u, mesh_nodes.size()); + EXPECT_EQ( 0, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[2]->get_node_sign()); + + // NOTE: Edge 0 is from nodes 0->1, Edge 1 is 1->2, Edge 2 is 2->0 + // NOTE: Because we snap-away from nodes, crossing will be epsilon away from nodes. + EXPECT_NEAR(0., mesh_elem.interface_crossing_position(iface, get_edge_segment(0)), std::numeric_limits::epsilon()); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(1))); + EXPECT_NEAR(1., mesh_elem.interface_crossing_position(iface, get_edge_segment(2)), std::numeric_limits::epsilon()); +} + +TEST_F(Mesh_Element_Tri3_One_LS, Node0_Snapped_Node1_Pos_Node_2_Neg) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + std::vector node_LS_values(3); + node_LS_values[0] = 0; + // Crossing position 0.608351703529745 for edge 1 + // gives a test case that fails to intersect exactly with node 0 + node_LS_values[1] = 1.; + node_LS_values[2] = -(1/0.60835170352974499152765019971412 - 1.); + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + EXPECT_EQ(3u, mesh_nodes.size()); + EXPECT_EQ( 0, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[2]->get_node_sign()); + + // NOTE: Edge 0 is from nodes 0->1, Edge 1 is 1->2, Edge 2 is 2->0 + // NOTE: Because we snap-away from nodes, crossing will be epsilon away from nodes. + EXPECT_DOUBLE_EQ(node_LS_values[1]/(node_LS_values[1] - node_LS_values[2]), mesh_elem.interface_crossing_position(iface, get_edge_segment(1))); + EXPECT_NEAR(1., mesh_elem.interface_crossing_position(iface, get_edge_segment(2)), std::numeric_limits::epsilon()); +} + +TEST_F(Mesh_Element_Tri3_One_LS, Node0_1_Snapped_Node2_Pos) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + std::vector node_LS_values(3); + node_LS_values[0] = 0; + node_LS_values[1] = 0; + node_LS_values[2] = 1.; + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_FALSE(mesh_elem.have_interface()); + + // NOTE: Edge 0 is from nodes 0->1, Edge 1 is 1->2, Edge 2 is 2->0 + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(0))); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(1))); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface, get_edge_segment(2))); +} + +TEST_F(Mesh_Element_Tri3_One_LS, Node0_1_Snapped_Node2_Neg) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + + std::vector node_LS_values(3); + node_LS_values[0] = 0; + node_LS_values[1] = 0; + node_LS_values[2] = -1.; + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + EXPECT_EQ(3u, mesh_nodes.size()); + EXPECT_EQ( 0, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ( 0, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[2]->get_node_sign()); + + // NOTE: Edge 0 is from nodes 0->1, Edge 1 is 1->2, Edge 2 is 2->0 + // TODO: What should intersections be for snapped edge? + // I don't think we should ever be looking for the interface crossing position of it + // NOTE: Because we snap-away from nodes, crossing will be epsilon away from nodes. + EXPECT_NEAR(0., mesh_elem.interface_crossing_position(iface, get_edge_segment(1)), std::numeric_limits::epsilon()); + EXPECT_NEAR(1., mesh_elem.interface_crossing_position(iface, get_edge_segment(2)), std::numeric_limits::epsilon()); +} + +typedef Mesh_Element_Tri3 Mesh_Element_Tri3_Three_LS; +TEST_F(Mesh_Element_Tri3_Three_LS, All_Phase2_Unsnapped) +{ + const LS_Field ls1("LS1", LevelSet_Identifier(1)); + const LS_Field ls2("LS2", LevelSet_Identifier(2)); + const LS_Field ls3("LS3", LevelSet_Identifier(3)); + CDFEM_Support & cdfem_supp = CDFEM_Support::get(elem_fixture.stk_fixture.meta_data()); + cdfem_supp.add_ls_field(ls1); + cdfem_supp.add_ls_field(ls2); + cdfem_supp.add_ls_field(ls3); + const InterfaceID iface01(0,1); + const InterfaceID iface02(0,2); + const InterfaceID iface12(1,2); + krino_mesh.add_interface_id(iface01); + krino_mesh.add_interface_id(iface02); + krino_mesh.add_interface_id(iface12); + Phase_Support::set_one_levelset_per_phase(true); + + std::vector > node_LS_values(3); + node_LS_values[0].resize(3); + node_LS_values[1].resize(3); + node_LS_values[2].resize(3); + // LS 0 + node_LS_values[0][0] = 1; + node_LS_values[1][0] = 1; + node_LS_values[2][0] = 1; + // LS 1 + node_LS_values[0][1] = 1; + node_LS_values[1][1] = 1; + node_LS_values[2][1] = 1; + // LS 2 + node_LS_values[0][2] = -1; + node_LS_values[1][2] = -1; + node_LS_values[2][2] = -1; + + generate_mesh_element_and_cutter(node_LS_values); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_FALSE(mesh_elem.have_interface()); +} + +TEST_F(Mesh_Element_Tri3_Three_LS, One_Interface) +{ + const LS_Field ls1("LS1", LevelSet_Identifier(1)); + const LS_Field ls2("LS2", LevelSet_Identifier(2)); + const LS_Field ls3("LS3", LevelSet_Identifier(3)); + CDFEM_Support & cdfem_supp = CDFEM_Support::get(elem_fixture.stk_fixture.meta_data()); + cdfem_supp.add_ls_field(ls1); + cdfem_supp.add_ls_field(ls2); + cdfem_supp.add_ls_field(ls3); + const InterfaceID iface01(0,1); + const InterfaceID iface02(0,2); + const InterfaceID iface12(1,2); + krino_mesh.add_interface_id(iface01); + krino_mesh.add_interface_id(iface02); + krino_mesh.add_interface_id(iface12); + Phase_Support::set_one_levelset_per_phase(true); + + std::vector > node_LS_values(3); + node_LS_values[0].resize(3); + node_LS_values[1].resize(3); + node_LS_values[2].resize(3); + // LS 0 + node_LS_values[0][0] = 0.038335; + node_LS_values[1][0] = 0.014437; + node_LS_values[2][0] = -0.01288; + // LS 1 + node_LS_values[0][1] = 0.037250; + node_LS_values[1][1] = 0.070753; + node_LS_values[2][1] = 0.021996; + // LS 2 + node_LS_values[0][2] = 0.01; + node_LS_values[1][2] = 0.01; + node_LS_values[2][2] = 0.01; + + generate_mesh_element_and_cutter(node_LS_values); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + EXPECT_EQ(3u, mesh_nodes.size()); + + const Vector3d node0_coords = mesh_nodes[0]->owner_coords(&mesh_elem); + const Vector3d node1_coords = mesh_nodes[1]->owner_coords(&mesh_elem); + const Vector3d node2_coords = mesh_nodes[2]->owner_coords(&mesh_elem); + const Segment3d edge0(node0_coords, node1_coords); + const Segment3d edge1(node1_coords, node2_coords); + const Segment3d edge2(node2_coords, node0_coords); + + krino_mesh.determine_node_signs(iface01); + + if (mesh_elem.have_interface(iface01)) + { + EXPECT_EQ(+1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[2]->get_node_sign()); + } + + krino_mesh.determine_node_signs(iface02); + + EXPECT_TRUE(mesh_elem.have_interface(iface02)); + EXPECT_EQ(+1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[2]->get_node_sign()); + + krino_mesh.determine_node_signs(iface12); + + if (mesh_elem.have_interface(iface12)) + { + EXPECT_EQ(+1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[2]->get_node_sign()); + } + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge1)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge2)); + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface12, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface12, edge1)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface12, edge2)); + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface02, edge0)); +} + +TEST_F(Mesh_Element_Tri3_Three_LS, Handle_Hanging_Child) +{ + const LS_Field ls1("LS1", LevelSet_Identifier(1)); + const LS_Field ls2("LS2", LevelSet_Identifier(2)); + const LS_Field ls3("LS3", LevelSet_Identifier(3)); + CDFEM_Support & cdfem_supp = CDFEM_Support::get(elem_fixture.stk_fixture.meta_data()); + cdfem_supp.add_ls_field(ls1); + cdfem_supp.add_ls_field(ls2); + cdfem_supp.add_ls_field(ls3); + const InterfaceID iface01(0,1); + const InterfaceID iface02(0,2); + const InterfaceID iface12(1,2); + krino_mesh.add_interface_id(iface01); + krino_mesh.add_interface_id(iface02); + krino_mesh.add_interface_id(iface12); + Phase_Support::set_one_levelset_per_phase(true); + + std::vector > node_LS_values(3); + node_LS_values[0].resize(3); + node_LS_values[1].resize(3); + node_LS_values[2].resize(3); + // LS 0 + node_LS_values[0][0] = 0.04; + node_LS_values[1][0] = 0.015; + node_LS_values[2][0] = 0.025; + // LS 1 + node_LS_values[0][1] = 0.; + node_LS_values[1][1] = 0.02; + node_LS_values[2][1] = 0.02; + // LS 2 + node_LS_values[0][2] = 0.01; + node_LS_values[1][2] = 0.01; + node_LS_values[2][2] = 0.01; + + generate_mesh_element_and_cutter(node_LS_values); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + EXPECT_EQ(3u, mesh_nodes.size()); + + const Vector3d node0_coords = mesh_nodes[0]->owner_coords(&mesh_elem); + const Vector3d node1_coords = mesh_nodes[1]->owner_coords(&mesh_elem); + const Vector3d node2_coords = mesh_nodes[2]->owner_coords(&mesh_elem); + const Segment3d edge0(node0_coords, node1_coords); + const Segment3d edge1(node1_coords, node2_coords); + const Segment3d edge2(node2_coords, node0_coords); + + krino_mesh.determine_node_signs(iface01); + + if (mesh_elem.have_interface(iface01)) + { + EXPECT_EQ(+1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[2]->get_node_sign()); + } + + krino_mesh.determine_node_signs(iface02); + + if (mesh_elem.have_interface(iface02)) + { + EXPECT_EQ(+1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[2]->get_node_sign()); + }; + + krino_mesh.determine_node_signs(iface12); + + EXPECT_TRUE(mesh_elem.have_interface(iface12)); + EXPECT_EQ(-1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[2]->get_node_sign()); + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge1)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge2)); + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface02, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface02, edge1)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface02, edge2)); + + EXPECT_DOUBLE_EQ(0.5, mesh_elem.interface_crossing_position(iface12, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface12, edge1)); + EXPECT_DOUBLE_EQ(0.5, mesh_elem.interface_crossing_position(iface12, edge2)); +} + +TEST_F(Mesh_Element_Tri3_Three_LS, Zero_Crossings_For_Phases_Present_Bug) +{ + const LS_Field ls1("LS1", LevelSet_Identifier(1)); + const LS_Field ls2("LS2", LevelSet_Identifier(2)); + const LS_Field ls3("LS3", LevelSet_Identifier(3)); + CDFEM_Support & cdfem_supp = CDFEM_Support::get(elem_fixture.stk_fixture.meta_data()); + cdfem_supp.add_ls_field(ls1); + cdfem_supp.add_ls_field(ls2); + cdfem_supp.add_ls_field(ls3); + const InterfaceID iface01(0,1); + const InterfaceID iface02(0,2); + const InterfaceID iface12(1,2); + krino_mesh.add_interface_id(iface01); + krino_mesh.add_interface_id(iface02); + krino_mesh.add_interface_id(iface12); + Phase_Support::set_one_levelset_per_phase(true); + + std::vector > node_LS_values(4); + node_LS_values[0].resize(3); + node_LS_values[1].resize(3); + node_LS_values[2].resize(3); + node_LS_values[3].resize(3); + // Node 0 + node_LS_values[0][0] = -0.00765969; + node_LS_values[0][1] = 0.532721; + node_LS_values[0][2] = 0.; + // Node 1 + node_LS_values[1][0] = -0.000100754; + node_LS_values[1][1] = 0.; + node_LS_values[1][2] = 0.; + // Node 2 + node_LS_values[2][0] = -0.00666939; + node_LS_values[2][1] = 0.337202; + node_LS_values[2][2] = 0.; + // Node 3 (Mid-node for edge 1) + node_LS_values[3][0] = -2.76e-6; + node_LS_values[3][1] = -0.00614775; + node_LS_values[3][2] = 0.; + + generate_mesh_element_and_cutter(node_LS_values); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_FALSE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + EXPECT_EQ(3u, mesh_nodes.size()); + + const Vector3d node0_coords = mesh_nodes[0]->owner_coords(&mesh_elem); + const Vector3d node1_coords = mesh_nodes[1]->owner_coords(&mesh_elem); + const Vector3d node2_coords = mesh_nodes[2]->owner_coords(&mesh_elem); + const Segment3d edge0(node0_coords, node1_coords); + const Segment3d edge1(node1_coords, node2_coords); + const Segment3d edge2(node2_coords, node0_coords); + + krino_mesh.determine_node_signs(iface01); + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge1)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface01, edge2)); + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface02, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface02, edge1)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface02, edge2)); + + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface12, edge0)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface12, edge1)); + EXPECT_ANY_THROW(mesh_elem.interface_crossing_position(iface12, edge2)); +} + +typedef Mesh_Element_Fixture Mesh_Element_Tet4; +TEST_F(Mesh_Element_Tet4, generate) +{ + generate_nonconformal_elements(); + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_EQ(elem(), mesh_elem.entity()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + ASSERT_EQ(4u, mesh_nodes.size()); + const stk::mesh::Entity * const elem_nodes = stk_bulk().begin_nodes(elem()); + for(unsigned i=0; i < mesh_nodes.size(); ++i) + { + EXPECT_EQ(mesh_nodes[i]->entity(), elem_nodes[i]); + } +} + +TEST_F(Mesh_Element_Tet4, OneInterfaceCheckNodeScore) +{ + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + Phase_Support::set_one_levelset_per_phase(false); + + std::vector node_LS_values(4); + node_LS_values[0] = 1.; + node_LS_values[1] = -0.2; + node_LS_values[2] = -0.5; + node_LS_values[3] = -0.8; + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + ASSERT_EQ(4u, mesh_nodes.size()); + EXPECT_EQ(+1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[2]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[3]->get_node_sign()); + + krino_mesh.decompose_edges(iface); + krino_mesh.determine_node_scores(iface); + + EXPECT_GT(mesh_nodes[0]->get_node_score(), mesh_nodes[3]->get_node_score()); + EXPECT_GT(mesh_nodes[3]->get_node_score(), mesh_nodes[2]->get_node_score()); + EXPECT_GT(mesh_nodes[2]->get_node_score(), mesh_nodes[1]->get_node_score()); +} + +TEST_F(Mesh_Element_Tet4, OneInterfaceCheckNodeScore_ScoreBasedOnAngleNotPosition) +{ + krino_mesh.get_cdfem_support().set_simplex_generation_method(CUT_QUADS_BY_LARGEST_ANGLE); + + const InterfaceID iface(0,0); + krino_mesh.add_interface_id(iface); + Phase_Support::set_one_levelset_per_phase(false); + + std::vector node_LS_values(4); + node_LS_values[0] = -1.0; + node_LS_values[1] = -1.01; + node_LS_values[2] = -1.02; + node_LS_values[3] = 1.0; + + generate_mesh_element_and_cutter(node_LS_values); + krino_mesh.determine_node_signs(iface); + + Mesh_Element & mesh_elem = get_mesh_element(); + EXPECT_TRUE(mesh_elem.have_interface()); + const NodeVec & mesh_nodes = mesh_elem.get_nodes(); + ASSERT_EQ(4u, mesh_nodes.size()); + EXPECT_EQ(-1, mesh_nodes[0]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[1]->get_node_sign()); + EXPECT_EQ(-1, mesh_nodes[2]->get_node_sign()); + EXPECT_EQ(+1, mesh_nodes[3]->get_node_sign()); + + krino_mesh.decompose_edges(iface); + krino_mesh.determine_node_scores(iface); + + EXPECT_GT(mesh_nodes[0]->get_node_score(), mesh_nodes[1]->get_node_score()); + EXPECT_GT(mesh_nodes[0]->get_node_score(), mesh_nodes[2]->get_node_score()); + EXPECT_GT(mesh_nodes[2]->get_node_score(), mesh_nodes[1]->get_node_score()); +} + +} diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Element_Cutter.cpp b/packages/krino/krino/unit_tests/Akri_Unit_Element_Cutter.cpp new file mode 100644 index 000000000000..b44c1914a3c8 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Element_Cutter.cpp @@ -0,0 +1,188 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace krino +{ + +static std::function &)> +build_always_false_diagonal_picker() +{ + auto diagonalPicker = + [](const std::array & faceNodes) + { + return false; + }; + return diagonalPicker; +} + +static void build_simple_parent_edges(const stk::topology topology, + const std::vector & nodeIds, + const std::vector> & nodalIsovars, + ParentEdgeMap & parentEdges, + std::vector & elementParentEdges, + std::vector & areParentEdgesAreOrientedSameAsElementEdges) +{ + const unsigned numEdges = topology.num_edges(); + + elementParentEdges.clear(); + for(unsigned i=0; i < numEdges; ++i) + { + const unsigned * edgeLNN = get_edge_node_ordinals(topology, i); + const unsigned i0 = edgeLNN[0]; + const unsigned i1 = edgeLNN[1]; + const ParentEdgeKey edge_key(nodeIds[i0], nodeIds[i1]); + CDFEM_Parent_Edge & parentEdge = parentEdges[edge_key]; + + if(!parentEdge.valid()) + parentEdge = CDFEM_Parent_Edge({nodalIsovars[i0], nodalIsovars[i1]}); + + elementParentEdges.push_back(&parentEdge); + } + + areParentEdgesAreOrientedSameAsElementEdges.clear(); + areParentEdgesAreOrientedSameAsElementEdges.resize(numEdges, true); +} + +struct ElementWithCutter : public ::testing::Test +{ + ElementWithCutter() {} + + void build_parent_edges_and_cutter(const stk::topology topology, + const std::vector & nodeIds, + const std::vector > & nodalIsovars) + { + Phase_Support::set_one_levelset_per_phase(true); + const auto diagonalPicker = build_always_false_diagonal_picker(); + + const MasterElement & masterElem = MasterElementDeterminer::getMasterElement(topology); + + std::vector elementParentEdges; + std::vector areParentEdgesAreOrientedSameAsElementEdges; + + build_simple_parent_edges(topology, nodeIds, nodalIsovars, parentEdges, elementParentEdges, areParentEdgesAreOrientedSameAsElementEdges); + + cutter.reset( new One_LS_Per_Phase_Cutter(masterElem, elementParentEdges, areParentEdgesAreOrientedSameAsElementEdges, diagonalPicker) ); + } + + ParentEdgeMap parentEdges; + CDFEM_Snapper snapper; + std::unique_ptr cutter; +}; + +struct TriangleWithTriplePoint : public ElementWithCutter +{ + const stk::topology topology{stk::topology::TRIANGLE_3_2D}; + const std::vector > nodalIsovars{ {-1., 0., 0.}, {0., -1., 0.}, {1.,1.,0.} }; + const std::vector nodeIds{1,2,3}; + + const InterfaceID iface01{0,1}; + const InterfaceID iface02{0,2}; + const InterfaceID iface12{1,2}; +}; + +struct TriangleWithFakeTriplePoint : public ElementWithCutter +{ + const stk::topology topology{stk::topology::TRIANGLE_3_2D}; + const std::vector > nodalIsovars{ {2., 2.,-1., 0.}, {-1.,0.5, 2., 0.}, {0.5, -1., 2., 0.} }; + const std::vector nodeIds{1,2,3}; +}; + +TEST_F(TriangleWithTriplePoint, givenCutter_haveExpectedInterfaces) +{ + build_parent_edges_and_cutter(topology, nodeIds, nodalIsovars); + + std::vector interfacesWithCuttingSurface; + cutter->fill_interfaces_with_cutting_surface(interfacesWithCuttingSurface); + EXPECT_EQ(3u, interfacesWithCuttingSurface.size()); + + EXPECT_TRUE(cutter->have_cutting_surface(iface01)); + EXPECT_TRUE(cutter->have_cutting_surface(iface02)); + EXPECT_TRUE(cutter->have_cutting_surface(iface12)); +} + +static bool is_nearly_eq(const Vector3d & v0, const Vector3d & v1, const double relativeTol=1.e-6) +{ + const double absoluteTol = relativeTol * (v0.length() + v1.length()); + for (int i=0; i<3; ++i) + if (std::abs(v0[i]-v1[i]) > absoluteTol) return false; + return true; +} + +void expect_to_find_all_first_in_second(const std::vector & vec0, const std::vector & vec1, const std::string & errorMsg) +{ + for (auto && val0 : vec0) + { + bool found = false; + for (auto && val1 : vec1) + { + if (is_nearly_eq(val0.parametricCoords, val1.parametricCoords) && val0.sortedDomains == val1.sortedDomains) + { + found = true; + break; + } + } + EXPECT_TRUE(found) << errorMsg << val0; + } +} + +void expect_num_interfaces_with_cutting_surface(size_t goldNumInterfacesWithCuttingSurfaces, const Element_Cutter & cutter) +{ + std::vector interfacesWithCuttingSurface; + cutter.fill_interfaces_with_cutting_surface(interfacesWithCuttingSurface); + EXPECT_EQ(goldNumInterfacesWithCuttingSurfaces, interfacesWithCuttingSurface.size()); +} + +void expect_intersections(const std::vector & goldIntersections, const std::vector & actualIntersections) +{ + EXPECT_EQ(goldIntersections.empty(), actualIntersections.empty()); + expect_to_find_all_first_in_second(actualIntersections, goldIntersections, "Actual intersection not found in gold intersections: "); + expect_to_find_all_first_in_second(goldIntersections, actualIntersections, "Gold intersection not found in actual intersections: "); +} + +TEST_F(TriangleWithTriplePoint, whenFindingIntersectionPoints_findPointAtCentroid) +{ + build_parent_edges_and_cutter(topology, nodeIds, nodalIsovars); + + std::vector triangleIntersections; + cutter->fill_interior_intersections(triangleIntersections); + + const std::vector goldIntersections{ {Vector3d{1./3.,1./3.,0.}, std::vector{0,1,2}} }; + expect_intersections(goldIntersections, triangleIntersections); +} + +TEST_F(TriangleWithFakeTriplePoint, whenFindingIntersectionPoints_findCorrectPoints) +{ + build_parent_edges_and_cutter(topology, nodeIds, nodalIsovars); + + std::vector triangleIntersections; + cutter->fill_interior_intersections(triangleIntersections); + + expect_num_interfaces_with_cutting_surface(4u, *cutter); + + const std::vector goldIntersections{ {Vector3d{4./9., 4./9., 0.}, std::vector{0,1,3}} }; + expect_intersections(goldIntersections, triangleIntersections); +} + + +} diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Explicit_Hamilton_Jacobi.cpp b/packages/krino/krino/unit_tests/Akri_Unit_Explicit_Hamilton_Jacobi.cpp new file mode 100644 index 000000000000..33e73c71a2e4 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Explicit_Hamilton_Jacobi.cpp @@ -0,0 +1,903 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace krino { + +struct ProblemFields +{ + stk::mesh::Field * levelSetField = nullptr; + stk::mesh::Field * coordsField = nullptr; + stk::mesh::Field * RHS = nullptr; + stk::mesh::Field * RHSNorm = nullptr; + stk::mesh::Field * speedField = nullptr; +}; + +void associate_input_mesh(stk::io::StkMeshIoBroker & stkIo, const std::string & meshName) +{ + stkIo.property_add(Ioss::Property("DECOMPOSITION_METHOD", "RIB")); + stkIo.add_mesh_database(meshName, stk::io::READ_MESH); + stkIo.create_input_mesh(); +} + +stk::mesh::BulkData & read_mesh(stk::io::StkMeshIoBroker & stkIo) +{ + stkIo.populate_bulk_data(); + return stkIo.bulk_data(); +} + +void declare_fields(stk::mesh::MetaData & meta, ProblemFields & fields) +{ + fields.levelSetField = &meta.declare_field>(stk::topology::NODE_RANK, "LevelSet", 2); + stk::mesh::put_field_on_mesh(*fields.levelSetField, meta.universal_part(), nullptr); + fields.RHS = &meta.declare_field>(stk::topology::NODE_RANK, "RHS", 1); + stk::mesh::put_field_on_mesh(*fields.RHS, meta.universal_part(), nullptr); + fields.RHSNorm = &meta.declare_field>(stk::topology::NODE_RANK, "RHSNorm", 1); + stk::mesh::put_field_on_mesh(*fields.RHSNorm, meta.universal_part(), nullptr); + auto constCoordsField = static_cast*>(meta.coordinate_field()); + fields.coordsField = const_cast*>(constCoordsField); + + if (true) + { + fields.speedField = &meta.declare_field>(stk::topology::ELEMENT_RANK, "Speed", 1); + stk::mesh::put_field_on_mesh(*fields.speedField, meta.universal_part(), nullptr); + } +} + +const int TetFaceTable[4][3] = { {0, 1, 2}, + {1, 2, 3}, + {0, 2, 3}, + {0, 1, 3} }; + +const int TetEdgeNodeOrder[6][2] = // [edge][edge_node] + { {0,1}, {1,2}, {2,0}, {0,3}, {1,3}, {2,3} }; + +void initialize_level_set(const stk::mesh::BulkData & mesh, const ProblemFields & fields, std::function initial_level_set, const double multiplier = 1.0) +{ + for (auto & bptr : mesh.buckets(stk::topology::NODE_RANK)) + { + for (auto & node : *bptr) + { + const double * x = stk::mesh::field_data(*fields.coordsField, node); + double* LS = stk::mesh::field_data(fields.levelSetField->field_of_state(stk::mesh::StateNP1), node); + *LS = initial_level_set(x) * multiplier; + } + } +} + +double initialize_constant_speed(const stk::mesh::BulkData & mesh, const ProblemFields & fields, const double speed) +{ + stk::mesh::field_fill(speed, *fields.speedField); + return speed; +} + +double compute_tet_vol(const stk::mesh::Entity * elemNodes, const stk::mesh::Field & coordsField) +{ + std::array xNode; + for (int n=0; n<4; ++n) xNode[n] = Vector3d(stk::mesh::field_data(coordsField, elemNodes[n])); + + return Dot(xNode[1]-xNode[0],Cross(xNode[2]-xNode[0],xNode[3]-xNode[0]))/6.; +} + +double compute_tri_vol(const stk::mesh::Entity * elemNodes, const stk::mesh::Field & coordsField) +{ + std::array xNode; + for (int n=0; n<3; ++n) xNode[n] = Vector3d(stk::mesh::field_data(coordsField, elemNodes[n]),2); + + return 0.5*Cross(xNode[1]-xNode[0],xNode[2]-xNode[0]).length(); +} + +double compute_vol(const stk::mesh::Entity * elemNodes, const unsigned numElemNodes, const stk::mesh::Field & coordsField) +{ + if (numElemNodes == 3) + return compute_tri_vol(elemNodes, coordsField); + else + return compute_tet_vol(elemNodes, coordsField); +} + +void compute_tet_gradOP_and_vol(const stk::mesh::Entity * elemNodes, const stk::mesh::Field & coordsField, std::vector & gradOP, double & vol) +{ + std::array xNode; + for (int n=0; n<4; ++n) xNode[n] = Vector3d(stk::mesh::field_data(coordsField, elemNodes[n])); + + std::array xFace; + for (int f=0; f<4; ++f) xFace[f] = (xNode[TetFaceTable[f][0]]+xNode[TetFaceTable[f][1]]+xNode[TetFaceTable[f][2]])/3.; + + std::array xEdge; + for (int e=0; e<6; ++e) xEdge[e] = 0.5*(xNode[TetEdgeNodeOrder[e][0]]+xNode[TetEdgeNodeOrder[e][1]]); + + vol = Dot(xNode[1]-xNode[0],Cross(xNode[2]-xNode[0],xNode[3]-xNode[0]))/6.; + const double norm = 0.5/vol; + + gradOP[0] = (Cross(xEdge[3]-xEdge[0],xNode[0]-xFace[3])+ + Cross(xEdge[2]-xEdge[3],xNode[0]-xFace[2])+ + Cross(xEdge[0]-xEdge[2],xNode[0]-xFace[0]))*norm; + gradOP[1] = (Cross(xEdge[0]-xEdge[4],xNode[1]-xFace[3])+ + Cross(xEdge[1]-xEdge[0],xNode[1]-xFace[0])+ + Cross(xEdge[4]-xEdge[1],xNode[1]-xFace[1]))*norm; + gradOP[2] = (Cross(xEdge[2]-xEdge[1],xNode[2]-xFace[0])+ + Cross(xEdge[5]-xEdge[2],xNode[2]-xFace[2])+ + Cross(xEdge[1]-xEdge[5],xNode[2]-xFace[1]))*norm; + gradOP[3] = (Cross(xEdge[5]-xEdge[4],xNode[3]-xFace[1])+ + Cross(xEdge[3]-xEdge[5],xNode[3]-xFace[2])+ + Cross(xEdge[4]-xEdge[3],xNode[3]-xFace[3]))*norm; +} + +void compute_tri_gradOP_and_vol(const stk::mesh::Entity * elemNodes, const stk::mesh::Field & coordsField, std::vector & gradOP, double & vol) +{ + std::array xNode; + for (int n=0; n<3; ++n) xNode[n] = Vector3d(stk::mesh::field_data(coordsField, elemNodes[n]),2); + + vol = 0.5*Cross(xNode[1]-xNode[0],xNode[2]-xNode[0]).length(); + const double norm = 0.5/vol; + + gradOP[0] = (crossZ(xNode[0]-xNode[2])+crossZ(xNode[1]-xNode[0]))*norm; + gradOP[1] = (crossZ(xNode[1]-xNode[0])+crossZ(xNode[2]-xNode[1]))*norm; + gradOP[2] = (crossZ(xNode[2]-xNode[1])+crossZ(xNode[0]-xNode[2]))*norm; +} + +void compute_gradOP_and_vol(const stk::mesh::Entity * elemNodes, const stk::mesh::Field & coordsField, std::vector & gradOP, double &vol) +{ + if (gradOP.size() == 3) + compute_tri_gradOP_and_vol(elemNodes, coordsField, gradOP, vol); + else + compute_tet_gradOP_and_vol(elemNodes, coordsField, gradOP, vol); +} + +Vector3d compute_scalar_gradient(const std::vector & nodalAreaVectors, const stk::mesh::Entity * elemNodes, const stk::mesh::Field & levelSetField) +{ + Vector3d grad(Vector3d::ZERO); + for (unsigned i=0; i & coordsField) +{ + const unsigned nodesPerElem = mesh.mesh_meta_data().spatial_dimension() + 1; + + double minVol = std::numeric_limits::max(); + for (auto & bptr : mesh.buckets(stk::topology::ELEMENT_RANK)) + { + for (auto & elem : *bptr) + { + const stk::mesh::Entity * elemNodes = mesh.begin_nodes(elem); + minVol = std::min(minVol, compute_vol(elemNodes, nodesPerElem, coordsField)); + } + } + + return std::pow(minVol, 1./mesh.mesh_meta_data().spatial_dimension()); +} + +double mesh_minimum_length_scale(const stk::mesh::BulkData & mesh, const stk::mesh::Field & coordsField) +{ + const unsigned dim = mesh.mesh_meta_data().spatial_dimension(); + std::vector gradOP(dim+1); + + double vol = 0.; + double maxGradLength = 0.; + for (auto & bptr : mesh.buckets(stk::topology::ELEMENT_RANK)) + { + for (auto & elem : *bptr) + { + const stk::mesh::Entity * elemNodes = mesh.begin_nodes(elem); + compute_gradOP_and_vol(elemNodes, coordsField, gradOP, vol); + for (unsigned n=0; n gradOP(dim+1); + + stk::mesh::field_fill(0.0, *fields.RHS); + stk::mesh::field_fill(0.0, *fields.RHSNorm); + + for (auto & bptr : mesh.buckets(stk::topology::ELEMENT_RANK)) + { + for (auto & elem : *bptr) + { + const unsigned numElemNodes = mesh.num_nodes(elem); + const stk::mesh::Entity * elemNodes = mesh.begin_nodes(elem); + double vol = 0.; + compute_gradOP_and_vol(elemNodes, *fields.coordsField, gradOP, vol); + const Vector3d normalDir = compute_scalar_gradient(gradOP, elemNodes, fields.levelSetField->field_of_state(stk::mesh::StateN)).unit_vector(); + + const double elementSpeed = *stk::mesh::field_data(*fields.speedField, elem); + + std::vector volHamiltonianCoeffs(dim+1); // K_i in Barth-Sethian + double sumNegCoeffs = 0.; + double sumPosCoeffs = 0.; + double sumNegContrib = 0.; + double volHamiltonian = 0.; + for (unsigned n=0; nfield_of_state(stk::mesh::StateN), node); + volHamiltonianCoeffs[n] = elementSpeed*vol*Dot(normalDir, gradOP[n]); + volHamiltonian += volHamiltonianCoeffs[n] * LSOld; + + if (volHamiltonianCoeffs[n] < 0.) + { + sumNegCoeffs += volHamiltonianCoeffs[n]; + sumNegContrib += volHamiltonianCoeffs[n] * LSOld; + } + else + { + sumPosCoeffs += volHamiltonianCoeffs[n]; + } + } + + std::vector alpha(dim+1, 0.); // delta phi_i in Barth-Sethian + double sumPosAlpha = 0.; + for (unsigned n=0; nfield_of_state(stk::mesh::StateN), node); + if (volHamiltonianCoeffs[n] > 0.) + { + alpha[n] = volHamiltonianCoeffs[n]/sumPosCoeffs*(sumNegContrib-sumNegCoeffs*LSOld)/volHamiltonian; + if (alpha[n] > 0.) sumPosAlpha += alpha[n]; + } + } + + for (unsigned n=0; n 0.) + { + const double wt = alpha[n]/sumPosAlpha; + residual += wt * volHamiltonian; + residualNorm += wt*vol; + } + } + } + } +} + +void assemble_residual_for_nodal_speed(const stk::mesh::BulkData & mesh, const ProblemFields & fields, const double eps) +{ + // Not extensively tested. This is an adaptation of Barth-Sethian positive coefficient scheme for nodal speed fields + + const unsigned dim = mesh.mesh_meta_data().spatial_dimension(); + std::vector gradOP(dim+1); + + stk::mesh::field_fill(0.0, *fields.RHS); + stk::mesh::field_fill(0.0, *fields.RHSNorm); + + for (auto & bptr : mesh.buckets(stk::topology::ELEMENT_RANK)) + { + for (auto & elem : *bptr) + { + const unsigned numElemNodes = mesh.num_nodes(elem); + const stk::mesh::Entity * elemNodes = mesh.begin_nodes(elem); + double vol = 0.; + compute_gradOP_and_vol(elemNodes, *fields.coordsField, gradOP, vol); + const Vector3d normalDir = compute_scalar_gradient(gradOP, elemNodes, fields.levelSetField->field_of_state(stk::mesh::StateN)).unit_vector(); + + for (unsigned n=0; nfield_of_state(stk::mesh::StateN), elemNodes[n]); + + //const double nodalSpeed = *stk::mesh::field_data(fields.speed, node); + const double nodalSpeed = 1.0; + + const double volHamiltonianCoeffs_n = vol*nodalSpeed*Dot(normalDir, gradOP[n]); // K_n in Barth-Sethian, probably should use nodal volume not elem volume + + if (volHamiltonianCoeffs_n > 0.) + { + double sumNegCoeffs = 0.; + double sumPosCoeffs = 0.; + double sumNegContrib = 0.; + double volHamiltonian = 0.; + for (unsigned j=0; jfield_of_state(stk::mesh::StateN), elemNodes[j]); + const double volHamiltonianCoeffs_j = vol*nodalSpeed*Dot(normalDir, gradOP[j]); + volHamiltonian += volHamiltonianCoeffs_j * LSOldj; + + if (volHamiltonianCoeffs_j < 0.) + { + sumNegCoeffs += volHamiltonianCoeffs_j; + sumNegContrib += volHamiltonianCoeffs_j * LSOldj; + } + else + { + sumPosCoeffs += volHamiltonianCoeffs_j; + } + } + + const double wt = volHamiltonianCoeffs_n/sumPosCoeffs*(sumNegContrib-sumNegCoeffs*LSOld)/volHamiltonian; + if (wt > 0.) + { + double & residual = *stk::mesh::field_data(*fields.RHS, node); + double & residualNorm = *stk::mesh::field_data(*fields.RHSNorm, node); + residual += wt * volHamiltonian; + residualNorm += wt*vol; + } + } + } + } + } +} + +double assemble_and_update_Eikonal(const stk::mesh::BulkData & mesh, const ProblemFields & fields, const double eps, const double dt, const bool computeArrivalTime) +{ + // This is a hybrid between the Barth-Sethian positive coefficient scheme and the Morgan-Waltz scheme for reinitialization. + // Uses element based speed and nodal sign function to assemble nodal contributions for Hamiltonian. + // The assembled nodal Hamiltonian is then used with nodal source term to explicitly update signed distance + // (or arrival time for non-unit speed). + // Unlike the elemental algorithm developed by Barth-Sethian, this algorithm converges to the exact solution + // for the "Distance Function Test" described in Morgan-Waltz. Unlike the Morgan-Waltz algorithm, this + // form converges much faster and is tolerant of meshes with obtuse angles because it uses the positive coefficient + // form in Barth-Sethian. + assert(!computeArrivalTime || nullptr != fields.speedField); + + const unsigned dim = mesh.mesh_meta_data().spatial_dimension(); + std::vector gradOP(dim+1); + + stk::mesh::field_fill(0.0, *fields.RHS); + stk::mesh::field_fill(0.0, *fields.RHSNorm); + + for (auto & bptr : mesh.buckets(stk::topology::ELEMENT_RANK)) + { + for (auto & elem : *bptr) + { + const unsigned numElemNodes = mesh.num_nodes(elem); + const stk::mesh::Entity * elemNodes = mesh.begin_nodes(elem); + double vol = 0.; + compute_gradOP_and_vol(elemNodes, *fields.coordsField, gradOP, vol); + const Vector3d normalDir = compute_scalar_gradient(gradOP, elemNodes, fields.levelSetField->field_of_state(stk::mesh::StateN)).unit_vector(); + + double elementSpeed = 1.0; + if (computeArrivalTime) + { + elementSpeed = *stk::mesh::field_data(*fields.speedField, elem); + } + + for (unsigned n=0; nfield_of_state(stk::mesh::StateN), elemNodes[n]); + + double nodalSpeed = 0.; + + const double sign = LSOld/sqrt(LSOld*LSOld + eps*eps); + nodalSpeed = sign*elementSpeed; + + const double volHamiltonianCoeffs_n = vol*nodalSpeed*Dot(normalDir, gradOP[n]); // K_n in Barth-Sethian, probably should use nodal volume not elem volume + + if (volHamiltonianCoeffs_n > 0.) + { + double sumNegCoeffs = 0.; + double sumPosCoeffs = 0.; + double sumNegContrib = 0.; + double volHamiltonian = 0.; + for (unsigned j=0; jfield_of_state(stk::mesh::StateN), elemNodes[j]); + const double volHamiltonianCoeffs_j = vol*nodalSpeed*Dot(normalDir, gradOP[j]); + volHamiltonian += volHamiltonianCoeffs_j * LSOldj; + + if (volHamiltonianCoeffs_j < 0.) + { + sumNegCoeffs += volHamiltonianCoeffs_j; + sumNegContrib += volHamiltonianCoeffs_j * LSOldj; + } + else + { + sumPosCoeffs += volHamiltonianCoeffs_j; + } + } + + const double wt = volHamiltonianCoeffs_n/sumPosCoeffs*(sumNegContrib-sumNegCoeffs*LSOld)/volHamiltonian; + if (wt > 0.) + { + double & residual = *stk::mesh::field_data(*fields.RHS, node); + double & residualNorm = *stk::mesh::field_data(*fields.RHSNorm, node); + residual += wt * volHamiltonian; + residualNorm += wt*vol; + } + } + } + } + } + + double sumSqrResid = 0.; + size_t sumCount = 0; + + for (auto & bptr : mesh.buckets(stk::topology::NODE_RANK)) + { + for (auto & node : *bptr) + { + const double residField = *stk::mesh::field_data(*fields.RHS, node); + const double residNormField = *stk::mesh::field_data(*fields.RHSNorm, node); + + const double Hamiltonian = (residNormField > 0.) ? (residField/residNormField) : 0.; + + const double LSOld = *stk::mesh::field_data(fields.levelSetField->field_of_state(stk::mesh::StateN), node); + double & LS = *stk::mesh::field_data(fields.levelSetField->field_of_state(stk::mesh::StateNP1), node); + const double sign = LSOld/sqrt(LSOld*LSOld + eps*eps); + + LS = LSOld - dt * (Hamiltonian - sign); + + if (computeArrivalTime || std::abs(LSOld) < eps) + { + sumSqrResid += (Hamiltonian - sign)*(Hamiltonian - sign); + sumCount++; + } + } + } + return std::sqrt(sumSqrResid/sumCount); +} + + +double apply_level_set_update(const stk::mesh::BulkData & mesh, const ProblemFields & fields, const double eps, const double dt) +{ + double sumResid = 0.; + size_t sumCount = 0; + + for (auto & bptr : mesh.buckets(stk::topology::NODE_RANK)) + { + for (auto & node : *bptr) + { + const double residField = *stk::mesh::field_data(*fields.RHS, node); + const double residNormField = *stk::mesh::field_data(*fields.RHSNorm, node); + + const double resid = (residNormField > 0.) ? (residField/residNormField) : 0.; + + const double LSOld = *stk::mesh::field_data(fields.levelSetField->field_of_state(stk::mesh::StateN), node); + double & LS = *stk::mesh::field_data(fields.levelSetField->field_of_state(stk::mesh::StateNP1), node); + + LS = LSOld - dt * resid; + + if (std::abs(LSOld) < eps) + { + sumResid += std::abs(resid); + sumCount++; + } + } + } + return sumResid/sumCount; +} + +bool domain_contains_interface(const stk::mesh::BulkData & mesh, const stk::mesh::Field & levelSetField) +{ + bool hasNeg = false; + bool hasPos = false; + + for (auto & bptr : mesh.buckets(stk::topology::NODE_RANK)) + { + for (auto & node : *bptr) + { + const double LS = *stk::mesh::field_data(levelSetField, node); + + if (LS < 0.) hasNeg = true; + if (LS > 0.) hasPos = true; + + if (hasNeg && hasPos) return true; + } + } + return false; +} + +void evolve_level_set(const stk::mesh::BulkData & mesh, const ProblemFields & fields, const double eps, const double dt) +{ + assemble_residual_for_element_speed(mesh, fields, eps); + apply_level_set_update(mesh, fields, eps, dt); +} + +void reinitialize_level_set( + stk::mesh::BulkData & mesh, + const ProblemFields & fields, + const double eps, + const double dtau, + stk::io::StkMeshIoBroker * stkIo = nullptr, + const double outputStartTime = 0., + const double outputStopTime = 0., + const size_t outputFileIndex = 0) +{ + if (!domain_contains_interface(mesh, fields.levelSetField->field_of_state(stk::mesh::StateNP1))) + { + return; + } + + const double convergedTol = 0.01; + bool converged = false; + const int maxIters = 1000; + const int printFreq = 50; + const double dOutputTime = (outputStopTime-outputStartTime)/(maxIters+1); + for (int iter = 0; iterfield_of_state(stk::mesh::StateNP1))) + { + std::cout << "Error, input level set field does not contain zero level set for initializing arrival time calculation." << std::endl; + return; + } + + bool converged = false; + const int maxIters = 5000; + const int printFreq = 50; + for (int iter = 0; iter eps) return 1.; + + static const double pi = std::atan(1)*4; + return 0.5*(1+ signedDist/eps + std::sin(pi*signedDist/eps)/pi); +} + +double delta(const double signedDist, const double eps) +{ + if (signedDist < -eps || signedDist > eps) return 0.; + + static const double pi = std::atan(1)*4; + return 0.5/eps * (1. + std::cos(pi*signedDist/eps)); +} + +double compute_level_set_volume(stk::mesh::BulkData & mesh, const ProblemFields & fields, const double eps) +{ + double vol = 0.; + for (auto & bptr : mesh.buckets(stk::topology::ELEMENT_RANK)) + { + for (auto & elem : *bptr) + { + const unsigned numElemNodes = mesh.num_nodes(elem); + const stk::mesh::Entity * elemNodes = mesh.begin_nodes(elem); + + double avgLS = 0.; + for (unsigned n=0; nfield_of_state(stk::mesh::StateNP1), elemNodes[n]); + avgLS += LS/numElemNodes; + } + + vol += Heaviside(avgLS, eps) * compute_vol(elemNodes, numElemNodes, *fields.coordsField); + } + } + return vol; +} + +double compute_unit_radius_error_norm(stk::mesh::BulkData & mesh, const ProblemFields & fields) +{ + double norm = 0.; + unsigned normCount = 0; + for (auto & bptr : mesh.buckets(stk::topology::NODE_RANK)) + { + for (auto & node : *bptr) + { + double & LS = *stk::mesh::field_data(fields.levelSetField->field_of_state(stk::mesh::StateNP1), node); + const Vector3d x(stk::mesh::field_data(*fields.coordsField, node), mesh.mesh_meta_data().spatial_dimension()); + + const double error = LS - (x.length() - 1.0); + norm += std::abs(error); + ++normCount; + } + } + return norm/normCount; +} + +double poor_initial_condition_for_unit_circle(const double * x) +{ + return ((x[0]-1.)*(x[0]-1.)+(x[1]-1.)*(x[1]-1.)+0.1)*(std::sqrt(x[0]*x[0]+x[1]*x[1])-1.); +} + +double poor_initial_condition_for_unit_sphere(const double * x) +{ + return ((x[0]-1.)*(x[0]-1.)+(x[1]-1.)*(x[1]-1.)+(x[2]-1.)*(x[2]-1.)+0.1)*(std::sqrt(x[0]*x[0]+x[1]*x[1]+x[2]*x[2])-1.); +} + +double flower_2D(const double * x) +{ + const int Nlobes = 3; + const double r = std::sqrt(x[0]*x[0]+x[1]*x[1]); + const double theta = std::atan2(x[1],x[0]); + const double rSurf = 0.2 + 0.1*std::sin(Nlobes*theta); + return r-rSurf; +} + +TEST(HamiltonJacobi, 2DPoorInitialCondition_ComputingArrivalTimeProducesLowErrorEverywhere) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + if (parallel_size > 1) return; + + stk::io::StkMeshIoBroker stkIo(pm); + ProblemFields fields; + + associate_input_mesh(stkIo, "square.g"); + declare_fields(stkIo.meta_data(),fields); + stk::mesh::BulkData & mesh = read_mesh(stkIo); + + initialize_level_set(mesh, fields, poor_initial_condition_for_unit_circle); + initialize_constant_speed(mesh, fields, 1.0); + + const double dx = mesh_minimum_length_scale(mesh, *fields.coordsField); + const double eps = 1.5*dx; // Should have same units as level set + const double dtau = 0.2*dx; // Reinitialization time step, based on unit advection speed used in reinitialization + + compute_arrival_time(mesh, fields, eps, dtau); + + const double errorNorm = compute_unit_radius_error_norm(mesh, fields); + std::cout << "Error norm " << errorNorm << std::endl; + EXPECT_LT(errorNorm, 0.001); +} + +TEST(HamiltonJacobi, 3DPoorInitialCondition_ComputingArrivalTimeProducesLowErrorEverywhere) +{ +#ifdef NDEBUG +#else + return; // Optimized only due to length +#endif + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + if (parallel_size > 1) return; + + stk::io::StkMeshIoBroker stkIo(pm); + ProblemFields fields; + + associate_input_mesh(stkIo, "cube_coarse.g"); + declare_fields(stkIo.meta_data(),fields); + stk::mesh::BulkData & mesh = read_mesh(stkIo); + + initialize_level_set(mesh, fields, poor_initial_condition_for_unit_sphere); + initialize_constant_speed(mesh, fields, 1.0); + + const double dx = mesh_minimum_length_scale(mesh, *fields.coordsField); + const double eps = 1.5*dx; // Should have same units as level set + const double dtau = 0.2*dx; // Reinitialization time step, based on unit advection speed used in reinitialization + + compute_arrival_time(mesh, fields, eps, dtau); + + const double errorNorm = compute_unit_radius_error_norm(mesh, fields); + std::cout << "Error norm " << errorNorm << std::endl; + EXPECT_LT(errorNorm, 0.01); +} + +void test_circle_with_flower(const double speed) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + if (parallel_size > 1) return; + + stk::io::StkMeshIoBroker stkIo(pm); + ProblemFields fields; + + associate_input_mesh(stkIo, "circle.g"); + declare_fields(stkIo.meta_data(),fields); + stk::mesh::BulkData & mesh = read_mesh(stkIo); + + const double maxSpeed = initialize_constant_speed(mesh, fields, speed); + initialize_level_set(mesh, fields, flower_2D, 1./maxSpeed); + + const double dx = mesh_minimum_length_scale(mesh, *fields.coordsField); + const double eps = 1.5*dx / maxSpeed; // Should have same units as level set + const double dtau = 0.2*dx / maxSpeed; // Units of time + + compute_arrival_time(mesh, fields, eps, dtau); +} + +TEST(HamiltonJacobi, CircleWithFlowerIC_ArrivalTimeConvergesForAnySpeed) +{ + // Probably would be better to test that these converge in exactly the same number of steps + test_circle_with_flower(1.0); + test_circle_with_flower(10.0); +} + +TEST(HamiltonJacobi, CircleWithFlowerIC_ReinitializationThenEvolveRuns) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + if (parallel_size > 1) return; + + stk::io::StkMeshIoBroker stkIo(pm); + ProblemFields fields; + + associate_input_mesh(stkIo, "circle.g"); + declare_fields(stkIo.meta_data(), fields); + stk::mesh::BulkData & mesh = read_mesh(stkIo); + + std::string outputFileName = "circle_flower.e"; + auto outputFileIndex = create_mesh(outputFileName, stkIo); + + const double maxSpeed = initialize_constant_speed(mesh, fields, 5.0); + initialize_level_set(mesh, fields, flower_2D); + + const double Courant = 0.25; + const double Ttotal = 0.1; + const double dx = mesh_minimum_length_scale(mesh, *fields.coordsField); + const unsigned Nt = (Ttotal+0.5*(Courant*dx/maxSpeed))/(Courant*dx/maxSpeed); + const double dt = Ttotal/Nt; + const double eps = 1.5*dx; + const double dtau = 0.2*dx; // Reinitialization time step, based on unit advection speed used in reinitialization + + write_fields(stkIo, outputFileIndex, 0.0); + reinitialize_level_set(mesh, fields, eps, dtau, &stkIo, 0.0, dt, outputFileIndex); + + std::cout << "Evolving to time " << Ttotal << " with " << Nt << " steps with Courant number " << Courant << std::endl; + + double time = 0.0; + for (unsigned n=0; n 1) return; + + stk::io::StkMeshIoBroker stkIo(pm); + ProblemFields fields; + + associate_input_mesh(stkIo, "cylinder_coarse.g"); + declare_fields(stkIo.meta_data(), fields); + stk::mesh::BulkData & mesh = read_mesh(stkIo); + + std::string outputFileName = "cylinder_flower.e"; + auto outputFileIndex = create_mesh(outputFileName, stkIo); + + const double maxSpeed = initialize_constant_speed(mesh, fields, 5.0); + initialize_level_set(mesh, fields, flower_2D); + + const double Courant = 0.25; + const double Ttotal = 0.01; + const double dx = mesh_minimum_length_scale(mesh, *fields.coordsField); + const unsigned Nt = (Ttotal+0.5*(Courant*dx/maxSpeed))/(Courant*dx/maxSpeed); + const double dt = Ttotal/Nt; + const double eps = 1.5*dx; + const double dtau = 0.2*dx; // Reinitialization time step, based on unit advection speed used in reinitialization + + write_fields(stkIo, outputFileIndex, 0.0); + reinitialize_level_set(mesh, fields, eps, dtau, &stkIo, 0.0, dt, outputFileIndex); + + std::cout << "Evolving to time " << Ttotal << " with " << Nt << " steps with Courant number " << Courant << std::endl; + + double time = 0.0; + for (unsigned n=0; n + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace krino +{ + +namespace { + int num_random_cases() { return 10000; } + double clip(const double in) { return std::floor(1000*in)/1000.; } +} // namespace + +TEST(Plane_Cutting_Surface, random_edge_cuts) +{ + const bool debug_output = false; + const Vector3d plane_pt0(0., 0., 0.); + const Vector3d plane_pt1(1., 0., 0.); + const Vector3d plane_pt2(0., 1., 0.); + Plane_Cutting_Surface surf(plane_pt0, plane_pt1, plane_pt2); + + + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_rank = stk::parallel_machine_rank(pm); + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution coord(-1., 1.); + + const int num_cases = num_random_cases(); + for (int icase=0; icase::epsilon()); + } + else + { + EXPECT_ANY_THROW(surf.interface_crossing_position(segment)); + } + } +} + +TEST(Intersecting_Planes_Cutting_Surface, planar_with_random_edge_cuts) +{ + const Vector3d plane_pt0(0., 0., 0.); + const Vector3d plane_pt1(1., 0., 0.); + const Vector3d plane_pt2(1., 1., 0.); + const Vector3d plane_pt3(0., 1., 0.); + Intersecting_Planes_Cutting_Surface surf(plane_pt0, plane_pt1, plane_pt2, plane_pt3); + + + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_rank = stk::parallel_machine_rank(pm); + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution coord(-1., 1.); + + const int num_cases = num_random_cases(); + for (int icase=0; icase::epsilon()); + } + else + { + EXPECT_ANY_THROW(surf.interface_crossing_position(segment)); + } + } +} + +TEST(Intersecting_Planes_Cutting_Surface, positive_dihedral_with_random_edge_cuts) +{ + const bool debug_output = false; + const Vector3d plane_pt0(0., 0., 0.); + const Vector3d plane_pt1(0., 1., 1.); + const Vector3d plane_pt2(1., 0., 0.); + const Vector3d plane_pt3(0., 1., 0.); + Intersecting_Planes_Cutting_Surface surf(plane_pt0, plane_pt1, plane_pt2, plane_pt3); + + + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_rank = stk::parallel_machine_rank(pm); + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution coord(-1., 1.); + + const int num_cases = num_random_cases(); + for (int icase=0; icase 0.5*pt0[1]) ? 1 : 0) + ((pt1[2] > 0.5*pt1[1]) ? 2 : 0); + switch (case_id) + { + case 0: EXPECT_NEAR(position, pos1, std::numeric_limits::epsilon()); break; + case 3: EXPECT_NEAR(position, pos0, std::numeric_limits::epsilon()); break; + } + } + else + { + EXPECT_ANY_THROW(surf.interface_crossing_position(segment)); + } + } +} + +TEST(Intersecting_Planes_Cutting_Surface, negative_dihedral_with_random_edge_cuts) +{ + const bool debug_output = false; + const Vector3d plane_pt0(0., 0., 0.); + const Vector3d plane_pt1(0., 1., 0.); + const Vector3d plane_pt2(1., 0., 0.); + const Vector3d plane_pt3(0., 1., 1.); + Intersecting_Planes_Cutting_Surface surf(plane_pt0, plane_pt1, plane_pt2, plane_pt3); + + + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_rank = stk::parallel_machine_rank(pm); + std::mt19937 mt(std::mt19937::default_seed + parallel_rank); + std::uniform_real_distribution coord(-1., 1.); + + const int num_cases = num_random_cases(); + for (int icase=0; icase 0.5*pt0[1]) ? 1 : 0) + ((pt1[2] > 0.5*pt1[1]) ? 2 : 0); + switch (case_id) + { + case 0: EXPECT_NEAR(position, pos1, std::numeric_limits::epsilon()); break; + case 3: EXPECT_NEAR(position, pos0, std::numeric_limits::epsilon()); break; + } + } + else + { + EXPECT_ANY_THROW(surf.interface_crossing_position(segment)); + } + } +} + +TEST(Intersecting_Planes_Cutting_Surface, infinitesimal_triangle_that_requires_robust_dihedral_angle) +{ + const double goldPosition = 0.5181038869168293; + const double otherCrossing = 0.4818961330726974; + const Vector3d plane_pt0(otherCrossing, 0., 1.-otherCrossing); + const Vector3d plane_pt1(0., 0., goldPosition); + const Vector3d plane_pt2(0., (1.-1.e-10), 0.); + const Vector3d plane_pt3(1.e-10, (1.-1.e-10), 0.); + Intersecting_Planes_Cutting_Surface surf(plane_pt0, plane_pt1, plane_pt2, plane_pt3); + + const Vector3d node0(0., 0., 0.); + const Vector3d node3(0., 0., 1.); + const double position = surf.interface_crossing_position(Segment3d(node0,node3)); + EXPECT_DOUBLE_EQ(position, goldPosition); +} + +void expect_intersection(const Plane3d & plane0, const Plane3d & plane1, const Plane3d & plane2, const Vector3d & goldIntersection) +{ + Vector3d intersectionPoint; + EXPECT_TRUE(find_intersection_of_three_planes(plane0, plane1, plane2, intersectionPoint)); + expect_eq(goldIntersection, intersectionPoint); +} + +void expect_intersection_with_side_of_tet(const int side, const Plane3d & plane0, const Plane3d & plane1, const Vector3d & goldIntersection) +{ + Vector3d intersectionPoint; + EXPECT_TRUE(find_intersection_of_two_planes_and_side_of_tet(side, plane0, plane1, intersectionPoint)); + expect_eq(goldIntersection, intersectionPoint); +} + +void expect_no_intersection(const Plane3d & plane0, const Plane3d & plane1, const Plane3d & plane2) +{ + Vector3d intersectionPoint; + EXPECT_FALSE(find_intersection_of_three_planes(plane0, plane1, plane2, intersectionPoint)); +} + +void expect_no_intersection_within_tet(const Plane3d & plane0, const Plane3d & plane1, const Plane3d & plane2) +{ + Vector3d intersectionPoint; + EXPECT_FALSE(find_intersection_of_three_planes_within_tet(plane0, plane1, plane2, intersectionPoint)); +} + +void expect_no_intersection_with_side_of_tet(const int side, const Plane3d & plane0, const Plane3d & plane1) +{ + Vector3d intersectionPoint; + EXPECT_FALSE(find_intersection_of_two_planes_and_side_of_tet(side, plane0, plane1, intersectionPoint)); +} + +struct IntersectPlanes : public ::testing::Test +{ + const Vector3d pt0{0., 0., 0.}; + const Vector3d pt1{1., 0., 0.}; + const Vector3d pt2{0., 1., 0.}; + const Vector3d pt3{0., 0., 1.}; + const Plane3d plane0{pt0, pt1, pt3}; + const Plane3d plane1{pt1, pt2, pt3}; + const Plane3d plane2{pt2, pt0, pt3}; + const Plane3d plane3{pt0, pt2, pt1}; + + const Vector3d offsetPt0{-0.1, -0.1, -0.1}; + const Vector3d offsetPt1{1.1, -0.1, -0.1}; + const Vector3d offsetPt2{-0.1, 1.1, -0.1}; + const Vector3d offsetPt3{-0.1, -0.1, 1.1}; + const Plane3d offsetPlane0{offsetPt0, offsetPt1, offsetPt3}; + const Plane3d offsetPlane1{offsetPt1, offsetPt2, offsetPt3}; + const Plane3d offsetPlane2{offsetPt2, offsetPt0, offsetPt3}; + const Plane3d offsetPlane3{offsetPt0, offsetPt2, offsetPt1}; + +}; + +TEST_F(IntersectPlanes, given3Planes_FindCorrectIntersection) +{ + expect_intersection(plane0, plane1, plane2, Vector3d{0., 0., 1.}); + expect_intersection(plane0, plane1, plane3, Vector3d{1., 0., 0.}); + expect_intersection(plane1, plane2, plane3, Vector3d{0., 1., 0.}); + expect_intersection(plane0, plane2, plane3, Vector3d{0., 0., 0.}); + + expect_no_intersection(plane0, plane0, plane1); +} + +TEST_F(IntersectPlanes, given3PlanesThatDoNotIntersectWithinTetBounds_FindNoIntersection) +{ + expect_no_intersection_within_tet(offsetPlane0, offsetPlane1, offsetPlane2); + expect_no_intersection_within_tet(offsetPlane0, offsetPlane1, offsetPlane3); + expect_no_intersection_within_tet(offsetPlane1, offsetPlane2, offsetPlane3); + expect_no_intersection_within_tet(offsetPlane0, offsetPlane2, offsetPlane3); +} + +TEST_F(IntersectPlanes, given2PlanesThatIntersectSideOfTet_FindCorrectIntersection) +{ + expect_intersection_with_side_of_tet(0, plane1, plane2, Vector3d{0., 0., 1.}); + expect_intersection_with_side_of_tet(0, plane1, plane3, Vector3d{1., 0., 0.}); + expect_intersection_with_side_of_tet(0, plane2, plane3, Vector3d{0., 0., 0.}); + + expect_intersection_with_side_of_tet(1, plane2, plane3, Vector3d{0., 1., 0.}); + expect_intersection_with_side_of_tet(1, plane0, plane2, Vector3d{0., 0., 1.}); + expect_intersection_with_side_of_tet(1, plane0, plane3, Vector3d{1., 0., 0.}); + + expect_intersection_with_side_of_tet(2, plane0, plane1, Vector3d{0., 0., 1.}); + expect_intersection_with_side_of_tet(2, plane0, plane3, Vector3d{0., 0., 0.}); + expect_intersection_with_side_of_tet(2, plane1, plane3, Vector3d{0., 1., 0.}); + + expect_intersection_with_side_of_tet(3, plane0, plane1, Vector3d{1., 0., 0.}); + expect_intersection_with_side_of_tet(3, plane0, plane2, Vector3d{0., 0., 0.}); + expect_intersection_with_side_of_tet(3, plane1, plane2, Vector3d{0., 1., 0.}); +} + +TEST_F(IntersectPlanes, given2PlanesThatDoNotIntersectSideOfTetAtAll_FindNoIntersection) +{ + expect_no_intersection_with_side_of_tet(0, plane0, plane2); + expect_no_intersection_with_side_of_tet(1, plane1, plane2); + expect_no_intersection_with_side_of_tet(2, plane2, plane1); + expect_no_intersection_with_side_of_tet(3, plane3, plane2); +} + +TEST_F(IntersectPlanes, given2PlanesThatDoNotIntersectSideOfTetWithinTetBounds_FindNoIntersection) +{ + expect_no_intersection_with_side_of_tet(0, offsetPlane1, offsetPlane2); + expect_no_intersection_with_side_of_tet(0, offsetPlane1, offsetPlane3); + expect_no_intersection_with_side_of_tet(0, offsetPlane2, offsetPlane3); + + expect_no_intersection_with_side_of_tet(1, offsetPlane2, offsetPlane3); + expect_no_intersection_with_side_of_tet(1, offsetPlane0, offsetPlane2); + expect_no_intersection_with_side_of_tet(1, offsetPlane0, offsetPlane3); + + expect_no_intersection_with_side_of_tet(2, offsetPlane0, offsetPlane1); + expect_no_intersection_with_side_of_tet(2, offsetPlane0, offsetPlane3); + expect_no_intersection_with_side_of_tet(2, offsetPlane1, offsetPlane3); + + expect_no_intersection_with_side_of_tet(3, offsetPlane0, offsetPlane1); + expect_no_intersection_with_side_of_tet(3, offsetPlane0, offsetPlane2); + expect_no_intersection_with_side_of_tet(3, offsetPlane1, offsetPlane2); +} + +static Vector3d compute_2d_plane_direction(const Vector3d & pt0, const Vector3d & pt1) +{ + return Vector3d(pt1[1]-pt0[1],pt0[0]-pt1[0],0.); +} + +static Plane3d build_2d_plane(const Vector3d & pt0, const Vector3d & pt1) +{ + return Plane3d(compute_2d_plane_direction(pt0,pt1),pt0); +} + +struct Intersect2DPlanes : public ::testing::Test +{ + Intersect2DPlanes() + : plane0{build_2d_plane(pt0, pt1)}, + plane1{build_2d_plane(pt1, pt2)}, + plane2{build_2d_plane(pt0, pt2)}, + offsetPlane0{build_2d_plane(offsetPt0, offsetPt1)}, + offsetPlane1{build_2d_plane(offsetPt1, offsetPt2)}, + offsetPlane2{build_2d_plane(offsetPt0, offsetPt2)} + { + } + + const Vector3d pt0{0., 0., 0.}; + const Vector3d pt1{1., 0., 0.}; + const Vector3d pt2{0., 1., 0.}; + + const Vector3d offsetPt0{-0.1, -0.1, 0.}; + const Vector3d offsetPt1{1.1, -0.1, 0.}; + const Vector3d offsetPt2{-0.1, 1.1, 0.}; + + Plane3d plane0; + Plane3d plane1; + Plane3d plane2; + + Plane3d offsetPlane0; + Plane3d offsetPlane1; + Plane3d offsetPlane2; +}; + +void expect_2d_intersection(const Plane3d & plane0, const Plane3d & plane1, const Vector3d & goldIntersection) +{ + Vector3d intersectionPoint; + EXPECT_TRUE(find_intersection_of_two_2D_planes(plane0, plane1, intersectionPoint)); + expect_eq(goldIntersection, intersectionPoint); +} + +void expect_no_2d_intersection_within_tri(const Plane3d & plane0, const Plane3d & plane1) +{ + Vector3d intersectionPoint; + EXPECT_FALSE(find_intersection_of_two_2D_planes_within_tri(plane0, plane1, intersectionPoint)); +} + +void expect_no_2d_intersection(const Plane3d & plane0, const Plane3d & plane1) +{ + Vector3d intersectionPoint; + EXPECT_FALSE(find_intersection_of_two_2D_planes(plane0, plane1, intersectionPoint)); +} + +TEST_F(Intersect2DPlanes, given2Plane2Ds_FindCorrectIntersection) +{ + expect_2d_intersection(plane0, plane1, Vector3d{1., 0., 0.}); + expect_2d_intersection(plane1, plane2, Vector3d{0., 1., 0.}); + expect_2d_intersection(plane0, plane2, Vector3d{0., 0., 0.}); + + expect_no_2d_intersection(plane0, plane0); +} + +TEST_F(Intersect2DPlanes, given2Plane2DsThatDoNotIntersectWithinTriBounds_FindNoIntersection) +{ + expect_no_2d_intersection_within_tri(offsetPlane0, offsetPlane1); + expect_no_2d_intersection_within_tri(offsetPlane1, offsetPlane2); + expect_no_2d_intersection_within_tri(offsetPlane0, offsetPlane2); +} + +TEST(ProjectionOf3DPointsIntoTriangle, givenTriangleCheckProjectionOfPointOnAndOffPlane) +{ + const Vector3d pt0{0., 0., 0.}; + const Vector3d pt1{1., 0., 0.}; + const Vector3d pt2{0., 1., 1.}; + const std::array triCoords{{ pt0, pt1, pt2 }}; + + expect_eq(Vector3d{0.,0.,0.}, triangle_parametric_coordinates_of_projected_point(triCoords, pt0)); + expect_eq(Vector3d{1.,0.,0.}, triangle_parametric_coordinates_of_projected_point(triCoords, pt1)); + expect_eq(Vector3d{0.,1.,0.}, triangle_parametric_coordinates_of_projected_point(triCoords, pt2)); + + const Vector3d normal = Cross(pt1-pt0, pt2-pt0).unit_vector(); + expect_eq(Vector3d{0.,0.,0.}, triangle_parametric_coordinates_of_projected_point(triCoords, pt0+normal)); + expect_eq(Vector3d{1.,0.,0.}, triangle_parametric_coordinates_of_projected_point(triCoords, pt1+normal)); + expect_eq(Vector3d{0.,1.,0.}, triangle_parametric_coordinates_of_projected_point(triCoords, pt2+normal)); + + const double oneThird = 1./3.; + const Vector3d midPt = oneThird*(pt0+pt1+pt2); + expect_eq(Vector3d{oneThird,oneThird,0.}, triangle_parametric_coordinates_of_projected_point(triCoords, midPt+normal)); +} + +} // namespace krino diff --git a/packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.cpp b/packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.cpp new file mode 100644 index 000000000000..bf1aa985a09b --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.cpp @@ -0,0 +1,27 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include + +namespace krino { + +LogRedirecter::LogRedirecter() +: myOriginalBuffer(krinolog.getStream().rdbuf()) +{ + krinolog.getStream().rdbuf(&myBuffer); +} + +LogRedirecter::~LogRedirecter() +{ + krinolog.getStream().rdbuf(myOriginalBuffer); +} + +} + diff --git a/packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.hpp b/packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.hpp new file mode 100644 index 000000000000..9b26c01006fc --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_LogRedirecter.hpp @@ -0,0 +1,32 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_UNIT_LOGREDIRECTER_H_ +#define AKRI_UNIT_LOGREDIRECTER_H_ + +#include + +namespace krino { + +class LogRedirecter +{ +public: + LogRedirecter(); + ~LogRedirecter(); + + void clear() { myBuffer.str(""); } + std::string get_log() const { return myBuffer.str(); } +private: + std::stringbuf myBuffer; + std::streambuf * myOriginalBuffer; +}; + +} + + +#endif diff --git a/packages/krino/krino/unit_tests/Akri_Unit_LowerEnvelope.cpp b/packages/krino/krino/unit_tests/Akri_Unit_LowerEnvelope.cpp new file mode 100644 index 000000000000..de5b17ccc7d5 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_LowerEnvelope.cpp @@ -0,0 +1,260 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include + + +namespace krino +{ + +TEST(Lower_Envelope, Find_Crossing_Segment) +{ + double s = 0.0; + + ASSERT_TRUE(find_lower_envelope_crossing_point({{{0., 1.}, {0., 0.}}}, s)); + EXPECT_DOUBLE_EQ(1.0, s); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{{0., 1.}, {1., 0.}}}, s)); + EXPECT_DOUBLE_EQ(0.5, s); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{{0., 1.}, {0.5, 0.}}}, s)); + EXPECT_DOUBLE_EQ(2./3., s); +} + +TEST(Lower_Envelope, Find_Crossing_Triangle) +{ + std::array pt = {{0., 0.}}; + + const std::vector phi000 = {0., 0., 0.}; + const std::vector phi011 = {0., 1., 1.}; + const std::vector phi101 = {1., 0., 1.}; + const std::vector phi110 = {1., 1., 0.}; + + EXPECT_FALSE(find_lower_envelope_crossing_point({{phi110, phi110, phi101}}, pt)); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi110, phi011, phi101}}, pt)); + std::array goldPt = {{1./3., 1./3.}}; + EXPECT_EQ(goldPt, pt); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{{1., 1., 0.75}, {0., 1., 0.75}, {1., 0., 0.75}}}, pt)); + goldPt = {{0.25, 0.25}}; + EXPECT_EQ(goldPt, pt); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi000, phi011, phi101}}, pt)); + goldPt = {{0., 0.}}; + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi011, phi000, phi101}}, pt)); + goldPt = {{1., 0.}}; + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi011, phi101, phi000}}, pt)); + goldPt = {{0., 1.}}; + EXPECT_EQ(goldPt, pt); +} + +TEST(Lower_Envelope, Find_Crossing_Tetrahedron) +{ + std::array pt = {{0., 0., 0.}}; + + const std::vector phi0000 = {0., 0., 0., 0.}; + const std::vector phi1110 = {1., 1., 1., 0.}; + const std::vector phi1101 = {1., 1., 0., 1.}; + const std::vector phi1011 = {1., 0., 1., 1.}; + const std::vector phi0111 = {0., 1., 1., 1.}; + + EXPECT_FALSE(find_lower_envelope_crossing_point({{phi1110, phi1110, phi1011, phi0111}}, pt)); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi1110, phi1101, phi1011, phi0111}}, pt)); + std::array goldPt = {{0.25, 0.25, 0.25}}; + EXPECT_EQ(goldPt, pt); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi0000, phi0111, phi1011, phi1101}}, pt)); + goldPt = {{0., 0., 0.}}; + EXPECT_EQ(goldPt, pt); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi0111, phi0000, phi1011, phi1101}}, pt)); + goldPt = {{1., 0., 0.}}; + EXPECT_EQ(goldPt, pt); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi0111, phi1011, phi0000, phi1101}}, pt)); + goldPt = {{0., 1., 0.}}; + EXPECT_EQ(goldPt, pt); + + ASSERT_TRUE(find_lower_envelope_crossing_point({{phi0111, phi1011, phi1101, phi0000}}, pt)); + goldPt = {{0., 0., 1.}}; + EXPECT_EQ(goldPt, pt); +} + + +TEST(Lower_Envelope, Two_LS_Bug) +{ + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope({0., 0.}, {1., 0.}); + ASSERT_EQ(1u, envelope.size()); + EXPECT_EQ(1, envelope[0].ls_index()); + EXPECT_DOUBLE_EQ(0.0, envelope[0].left_endpoint()); + EXPECT_DOUBLE_EQ(1., envelope[0].right_endpoint()); +} + +TEST(Lower_Envelope, Two_LS_Lower_Envelope) +{ + { + // 2 level sets, infinitesimal crossing on left by convention that highest level set index wins + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope({0., 0.}, {0.25, 1.}); + ASSERT_EQ(2u, envelope.size()); + const LS_Segment & env1 = envelope[0]; + const LS_Segment & env2 = envelope[1]; + EXPECT_EQ(1, env1.ls_index()); + EXPECT_EQ(0, env2.ls_index()); + EXPECT_DOUBLE_EQ(0.0, env1.left_endpoint()); + EXPECT_DOUBLE_EQ(0.0, env1.right_endpoint()); + EXPECT_DOUBLE_EQ(0.0, env2.left_endpoint()); + EXPECT_DOUBLE_EQ(1.0, env2.right_endpoint()); + } + + { + // 2 level sets, infinitesimal crossing on right by convention that highest level set index wins + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope({0.3, 0.6}, {0., 0.}); + ASSERT_EQ(2u, envelope.size()); + const LS_Segment & env1 = envelope[0]; + const LS_Segment & env2 = envelope[1]; + EXPECT_EQ(0, env1.ls_index()); + EXPECT_EQ(1, env2.ls_index()); + EXPECT_DOUBLE_EQ(0.0, env1.left_endpoint()); + EXPECT_DOUBLE_EQ(1.0, env1.right_endpoint()); + EXPECT_DOUBLE_EQ(1.0, env2.left_endpoint()); + EXPECT_DOUBLE_EQ(1.0, env2.right_endpoint()); + } +} + +void expect_good_edge(const Segment_Vector & envelope) +{ + for (size_t i=0; i= SegmentLowerEnvelope::MinSize()) << "Unexpected infinitesimal, non-zero segment at end of edge " << envelope; + } + else + { + EXPECT_GE(envelope[i].length(), SegmentLowerEnvelope::MinSize()) << "Unexpected internal infinitesimal internal segment on edge " << envelope; + } + + if (i & phi0, const std::vector & phi1) +{ + ASSERT_FALSE(envelope.empty()); + EXPECT_EQ(get_min(phi0), envelope.front().ls_index()) << "Front segment of edge does not match phase at node on edge " << envelope; + EXPECT_EQ(get_min(phi1), envelope.back().ls_index()) << "Back segment of edge does not match phase at node on edge " << envelope; +} + +TEST(Lower_Envelope, Three_LS_Lower_Envelope) +{ + { + // 3 level sets, edge is B from 0 to 0.5, C from 0.5 to 1.0 and + // 1st level set intersects both others at 0.5 but is never the lowest + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope({0.5, 0., 1.}, {0.5, 1., 0.}); + ASSERT_EQ(2u, envelope.size()); + const LS_Segment & env1 = envelope[0]; + const LS_Segment & env2 = envelope[1]; + EXPECT_EQ(1, env1.ls_index()); + EXPECT_EQ(2, env2.ls_index()); + EXPECT_DOUBLE_EQ(0.0, env1.left_endpoint()); + EXPECT_DOUBLE_EQ(0.5, env1.right_endpoint()); + EXPECT_DOUBLE_EQ(0.5, env2.left_endpoint()); + EXPECT_DOUBLE_EQ(1., env2.right_endpoint()); + + expect_good_edge(envelope); + } + + { + // 3 level sets, left is A, middle C, right B + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope({0., 1., 0.25}, {1., 0., 0.25}); + ASSERT_EQ(3u, envelope.size()); + const LS_Segment & env1 = envelope[0]; + const LS_Segment & env2 = envelope[1]; + const LS_Segment & env3 = envelope[2]; + EXPECT_EQ(0, env1.ls_index()); + EXPECT_EQ(2, env2.ls_index()); + EXPECT_EQ(1, env3.ls_index()); + EXPECT_DOUBLE_EQ(0.0, env1.left_endpoint()); + EXPECT_DOUBLE_EQ(0.25, env1.right_endpoint()); + EXPECT_DOUBLE_EQ(0.25, env2.left_endpoint()); + EXPECT_DOUBLE_EQ(0.75, env2.right_endpoint()); + EXPECT_DOUBLE_EQ(0.75, env3.left_endpoint()); + EXPECT_DOUBLE_EQ(1.0, env3.right_endpoint()); + + expect_good_edge(envelope); + } + + { + // 3 level sets with infinitesmal transitions near ends + const std::vector phi0 = {1.56125e-17, -4.77049e-18, 0.}; + const std::vector phi1 = {-0.0417425, 0.0477226, 0.}; + + { + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope(phi0, phi1); + + expect_good_edge(envelope); + expect_end_segments_to_match_end_phases(envelope, phi0, phi1); + } + { + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope(phi1, phi0); + + expect_good_edge(envelope); + expect_end_segments_to_match_end_phases(envelope, phi1, phi0); + } + } + + { + // 3 level sets with infinitesmal transitions near middle + const std::vector phi0 = {-1., (1.+1.e-12), 0.}; + const std::vector phi1 = {(1.+1.e-12), -1., 0.}; + + { + Segment_Vector envelope01 = SegmentLowerEnvelope::find_lower_envelope(phi0, phi1); + expect_good_edge(envelope01); + expect_end_segments_to_match_end_phases(envelope01, phi0, phi1); + EXPECT_EQ(2u, envelope01.size()); + + Segment_Vector envelope10 = SegmentLowerEnvelope::find_lower_envelope(phi1, phi0); + expect_good_edge(envelope10); + expect_end_segments_to_match_end_phases(envelope10, phi1, phi0); + EXPECT_EQ(2u, envelope10.size()); + + EXPECT_DOUBLE_EQ(envelope01[0].right_endpoint(), envelope10[0].right_endpoint()); + } + } +} + +void expect_segments_lengths(const Segment_Vector & segments, const std::vector goldSegmentLengthsByLS) +{ + for (auto && segment : segments) + { + ASSERT_TRUE(segment.ls_index() < (int)goldSegmentLengthsByLS.size()); + EXPECT_DOUBLE_EQ(goldSegmentLengthsByLS[segment.ls_index()], segment.length()); + } +} + +TEST(Lower_Envelope,Sensitive_LS) +{ + std::array,2> phi = {{ {-1.e-17,-2.e-17,1.0,0}, {2.e-17,1.0,-2.e-17,0} }}; + const double goldSegmentLengthForPhi0 = phi[0][0]/(phi[0][0]-phi[1][0]); + const std::vector goldSegmentLengthsByLS = { goldSegmentLengthForPhi0, 0., 0., 1.-goldSegmentLengthForPhi0 }; + + Segment_Vector envelope = SegmentLowerEnvelope::find_lower_envelope(phi[0], phi[1]); + expect_segments_lengths(envelope, goldSegmentLengthsByLS); +} + +} // namespace krino diff --git a/packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.cpp b/packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.cpp new file mode 100644 index 000000000000..a7a04758b117 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.cpp @@ -0,0 +1,278 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include // for declare_element +#include + +#include + +namespace krino { + +void build_mesh(stk::mesh::BulkData & mesh, const std::vector & elem_nodes, const std::vector & elem_procs, std::vector & elem_parts) +{ + if (elem_nodes.empty()) return; + + const size_t numElem = elem_nodes.size(); + EXPECT_TRUE(elem_procs.size() == numElem); + EXPECT_TRUE(elem_parts.size() == numElem); + const size_t nodesPerElem = elem_nodes[0].size(); + const int parallel_size = mesh.parallel_size(); + const int parallel_rank = mesh.parallel_rank(); + + mesh.modification_begin(); + + // Create nodes and elements + std::set nodes_I_declared; + for (size_t e=0; e 1) + { + for (size_t e=0; e sides; + stk::mesh::get_entities( mesh, mesh.mesh_meta_data().side_rank(), sides ); + + EXPECT_TRUE(1 == sides.size()); + + for (auto && side : sides) + { + EXPECT_TRUE(mesh.bucket(side).member(block_1)); + EXPECT_TRUE(mesh.bucket(side).member(block_2)); + } + + // cleanup + mesh.modification_begin(); + for (auto && side : sides) + { + EXPECT_TRUE(disconnect_and_destroy_entity(mesh, side)); + } + mesh.modification_end(); +} +} + +TEST(MeshHelpers, DeclareElementSide) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + if (parallel_size > 2) return; + + // This test will create a two element mesh (quad4 elements) on 1 or 2 processors. + + /* Mesh + * 4---5---6 P0 owns nodes 1,2,4,5; P, elem 1 + * | 1 | 2 | P1 : 3,6, elem 2 + * 1---2---3 + */ + + unsigned spatialDim = 2; + stk::mesh::MetaData meta(spatialDim); + stk::mesh::BulkData mesh(meta, pm); + + stk::mesh::Part& block_1 = meta.declare_part_with_topology("block_1", stk::topology::QUAD_4_2D); + stk::mesh::Part& block_2 = meta.declare_part_with_topology("block_2", stk::topology::QUAD_4_2D); + stk::mesh::Part& surface_1 = meta.declare_part_with_topology("surface_1", stk::topology::LINE_2); + + meta.commit(); + + const std::vector elem_nodes{ {1, 2, 5, 4}, {2, 3, 6, 5} }; + const std::vector elem_procs{0, 1}; + std::vector elem_parts{{&block_1}, {&block_2}}; + + build_mesh(mesh, elem_nodes, elem_procs, elem_parts); + + // All elements and nodes appear everywhere via aura + stk::mesh::Entity element1 = mesh.get_entity(stk::topology::ELEMENT_RANK, 1); + stk::mesh::Entity element2 = mesh.get_entity(stk::topology::ELEMENT_RANK, 2); + stk::mesh::Entity node2 = mesh.get_entity(stk::topology::NODE_RANK, 2); + stk::mesh::Entity node5 = mesh.get_entity(stk::topology::NODE_RANK, 5); + + std::vector side_nodes; + side_nodes.push_back(node2); + side_nodes.push_back(node5); + + mesh.initialize_face_adjacent_element_graph(); + + { + // Case 1: Declare side on owning processor for both elements + mesh.modification_begin(); + + if (mesh.bucket(element1).owned()) + { + const unsigned elem1_local_side_id = stk::mesh::get_entity_subcell_id(mesh, element1, meta.side_rank(), surface_1.topology(), side_nodes); + mesh.declare_element_side(element1, elem1_local_side_id, stk::mesh::ConstPartVector{&surface_1}); + } + if (mesh.bucket(element2).owned()) + { + const unsigned elem2_local_side_id = stk::mesh::get_entity_subcell_id(mesh, element2, meta.side_rank(), surface_1.topology(), side_nodes); + mesh.declare_element_side(element2, elem2_local_side_id, stk::mesh::ConstPartVector{&surface_1}); + } + + mesh.modification_end(); + + test_and_cleanup_internal_side(mesh, block_1, block_2); + } + + { + // Case 2: Declare side of element1 on processor that owns element1 + mesh.modification_begin(); + + if (mesh.bucket(element1).owned()) + { + const unsigned elem1_local_side_id = stk::mesh::get_entity_subcell_id(mesh, element1, meta.side_rank(), surface_1.topology(), side_nodes); + mesh.declare_element_side(element1, elem1_local_side_id, stk::mesh::ConstPartVector{&surface_1}); + } + + mesh.modification_end(); + + test_and_cleanup_internal_side(mesh, block_1, block_2); + } + + { + // Case 3: Declare side of element2 on processor that owns element2 + mesh.modification_begin(); + + if (mesh.bucket(element2).owned()) + { + const unsigned elem2_local_side_id = stk::mesh::get_entity_subcell_id(mesh, element2, meta.side_rank(), surface_1.topology(), side_nodes); + mesh.declare_element_side(element2, elem2_local_side_id, stk::mesh::ConstPartVector{&surface_1}); + } + + mesh.modification_end(); + + test_and_cleanup_internal_side(mesh, block_1, block_2); + } +} + +TEST(MeshHelpers, FullyCoincidentVolumeElements) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + + // This test will create a two element mesh (quad4 elements), on more than 2 processors only + // ranks 0 and 1 will have any elements. We test larger number of processors to ensure that + // we get a parallel-consistent result to avoid potential parallel hangs in the full app. + + unsigned spatialDim = 2; + stk::mesh::MetaData meta(spatialDim); + stk::mesh::BulkData mesh(meta, pm); + + stk::mesh::Part& block_1 = meta.declare_part_with_topology("block_1", stk::topology::QUAD_4_2D); + stk::mesh::Part& active_part = meta.declare_part("active"); + meta.commit(); + + const std::vector elem_nodes{ {1, 2, 3, 4}, {2, 3, 4, 1} }; + const std::vector elem_procs{0, 1}; + std::vector elem_parts{{&block_1}, {&block_1}}; + + build_mesh(mesh, elem_nodes, elem_procs, elem_parts); + + const bool ok = check_coincident_elements(mesh, active_part); + EXPECT_FALSE(ok); +} + +TEST(MeshHelpers, PartiallyCoincidentActiveVolumeElements) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + if (parallel_size > 2) return; + + // This test will create a two element mesh (quad4 elements) on 1 or 2 processors. + + unsigned spatialDim = 2; + stk::mesh::MetaData meta(spatialDim); + stk::mesh::BulkData mesh(meta, pm); + + stk::mesh::Part& block_1 = meta.declare_part_with_topology("block_1", stk::topology::QUAD_4_2D); + stk::mesh::Part& active_part = meta.declare_part("active"); + meta.commit(); + + const std::vector elem_nodes{ {1, 2, 3, 4}, {1, 2, 5, 6} }; + const std::vector elem_procs{0, 1}; + std::vector elem_parts{{&block_1, &active_part}, {&block_1, &active_part}}; + + build_mesh(mesh, elem_nodes, elem_procs, elem_parts); + + const bool ok = check_coincident_elements(mesh, active_part); + EXPECT_FALSE(ok); +} + +TEST(MeshHelpers, NotCoincidentActiveDegenerateVolumeElements) +{ + stk::ParallelMachine pm = MPI_COMM_WORLD; + const int parallel_size = stk::parallel_machine_size(pm); + if (parallel_size > 2) return; + + // This test will create a two element mesh (quad4 elements) on 1 or 2 processors. + + unsigned spatialDim = 2; + stk::mesh::MetaData meta(spatialDim); + stk::mesh::BulkData mesh(meta, pm); + + stk::mesh::Part& block_1 = meta.declare_part_with_topology("block_1", stk::topology::QUAD_4_2D); + stk::mesh::Part& active_part = meta.declare_part("active"); + meta.commit(); + + const std::vector elem_nodes{ {1, 2, 2, 3}, {3, 2, 2, 4} }; + const std::vector elem_procs{0, 1}; + std::vector elem_parts{{&block_1, &active_part}, {&block_1, &active_part}}; + + build_mesh(mesh, elem_nodes, elem_procs, elem_parts); + + const bool ok = check_coincident_elements(mesh, active_part); + EXPECT_TRUE(ok); +} + +} diff --git a/packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.hpp b/packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.hpp new file mode 100644 index 000000000000..976a24bacbcd --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_MeshHelpers.hpp @@ -0,0 +1,24 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef KRINO_UNIT_TESTS_INCLUDE_AKRI_UNIT_MESHHELPERS_H_ +#define KRINO_UNIT_TESTS_INCLUDE_AKRI_UNIT_MESHHELPERS_H_ + +#include +#include + +namespace krino +{ + +void build_mesh(stk::mesh::BulkData & mesh, + const std::vector & elem_nodes, + const std::vector & elem_procs, + std::vector & elem_parts); +} + +#endif /* KRINO_UNIT_TESTS_INCLUDE_AKRI_UNIT_MESHHELPERS_H_ */ diff --git a/packages/krino/krino/unit_tests/Akri_Unit_MortonIndex.cpp b/packages/krino/krino/unit_tests/Akri_Unit_MortonIndex.cpp new file mode 100644 index 000000000000..805731063269 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_MortonIndex.cpp @@ -0,0 +1,52 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include + +namespace krino +{ + +TEST(MortonIndex, specified_index) +{ + const std::vector valid_indices {0, 1, 2, 4, 3434, 23, 1414415, 76285, MAX_VALID_MORTON3D_INDEX}; + for (int valid_index : valid_indices) + { + EXPECT_EQ(valid_index, morton3d_decode_index(morton3d_encode_index(valid_index))); + } +#ifndef NDEBUG + const std::vector invalid_indices {MAX_VALID_MORTON3D_INDEX+1, 3000000, 2147483647}; + for (int invalid_index : invalid_indices) + { + EXPECT_ANY_THROW(morton3d_encode_index(invalid_index)); + } +#endif +} + +TEST(MortonIndex, specified_indices) +{ + const std::vector valid_indices {0, 1, 2, 4, 3434, 23, 1414415, 76285, MAX_VALID_MORTON3D_INDEX}; + for (int ix : valid_indices) + { + for (int iy : valid_indices) + { + for (int iz : valid_indices) + { + std::array out_indices = morton3d_decode_indices(morton3d_encode_indices({{ix,iy,iz}})); + EXPECT_EQ(ix, out_indices[0]); + EXPECT_EQ(iy, out_indices[1]); + EXPECT_EQ(iz, out_indices[2]); + } + } + } +} + +} // namespace krino diff --git a/packages/krino/krino/unit_tests/Akri_Unit_ParallelErrorMessage.cpp b/packages/krino/krino/unit_tests/Akri_Unit_ParallelErrorMessage.cpp new file mode 100644 index 000000000000..99d3c4f1a609 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_ParallelErrorMessage.cpp @@ -0,0 +1,42 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +namespace krino +{ + +template void write_rank_message(T & os, const int rank) +{ + os << "Rank " << rank << " message.\n"; +} + +TEST(ParallelErrorMessage, ConcatenateErrors) +{ + stk::Parallel world_comm(MPI_COMM_WORLD); + ParallelErrorMessage err(world_comm); + + write_rank_message(err, world_comm.parallel_rank()); + auto global_message = err.gather_message(); + EXPECT_TRUE(global_message.first); + + if (world_comm.parallel_rank() == 0) + { + std::ostringstream expected; + for (int i = 0; i < world_comm.parallel_size(); ++i) + { + write_rank_message(expected, i); + expected << std::endl; + } + EXPECT_EQ(expected.str(), global_message.second); + } +} +} diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.cpp b/packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.cpp new file mode 100644 index 000000000000..4d56eb46bd9f --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.cpp @@ -0,0 +1,197 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +namespace krino +{ + +Part_Decomposition_Fixture::Part_Decomposition_Fixture() +: fixture(3) +{ + AuxMetaData & aux_meta = AuxMetaData::get(get_meta_data()); + aux_meta.declare_io_part_with_topology("block_1", stk::topology::TETRAHEDRON_4); + aux_meta.declare_io_part_with_topology("block_2", stk::topology::TETRAHEDRON_4); + aux_meta.declare_io_part_with_topology("surface_1", stk::topology::TRIANGLE_3); +} + +Part_Decomposition_Fixture::~Part_Decomposition_Fixture() +{ +} + +Block_Surface_Connectivity Part_Decomposition_Fixture::addOneSidedSideset() +{ + Block_Surface_Connectivity block_surface_info; + stk::mesh::PartOrdinal surface_1_ordinal = get_meta_data().get_part("surface_1")->mesh_meta_data_ordinal(); + stk::mesh::PartOrdinal block_1_ordinal = get_meta_data().get_part("block_1")->mesh_meta_data_ordinal(); + block_surface_info.add_surface(surface_1_ordinal, {block_1_ordinal}); + return block_surface_info; +} + +Block_Surface_Connectivity Part_Decomposition_Fixture::addTwoSidedSideset() +{ + Block_Surface_Connectivity block_surface_info; + stk::mesh::PartOrdinal block_1_ordinal = get_meta_data().get_part("block_1")->mesh_meta_data_ordinal(); + stk::mesh::PartOrdinal block_2_ordinal = get_meta_data().get_part("block_2")->mesh_meta_data_ordinal(); + + stk::mesh::Part & surface_1 = *get_meta_data().get_part("surface_1"); + stk::mesh::Part & surface_1_block_1 = get_meta_data().declare_part_with_topology("surface_block_1_tri3_1", stk::topology::TRIANGLE_3); + stk::mesh::Part & surface_1_block_2 = get_meta_data().declare_part_with_topology("surface_block_2_tri3_1", stk::topology::TRIANGLE_3); + get_meta_data().declare_part_subset(surface_1, surface_1_block_1); + get_meta_data().declare_part_subset(surface_1, surface_1_block_2); + block_surface_info.add_surface(surface_1.mesh_meta_data_ordinal(), {block_1_ordinal, block_2_ordinal}); + block_surface_info.add_surface(surface_1_block_1.mesh_meta_data_ordinal(), {block_1_ordinal}); + block_surface_info.add_surface(surface_1_block_2.mesh_meta_data_ordinal(), {block_2_ordinal}); + return block_surface_info; +} + +stk::mesh::MetaData & Part_Decomposition_Fixture::get_meta_data() +{ + return fixture.meta_data(); +} + +stk::mesh::Part * Part_Decomposition_Fixture::findPart(const std::string & part_name) +{ + stk::mesh::Part * result = nullptr; + const stk::mesh::PartVector & mesh_parts = fixture.meta_data().get_parts(); + stk::mesh::PartVector::const_iterator found; + found = std::find_if(mesh_parts.begin(), mesh_parts.end(), PartNameIs(part_name)); + if( found != mesh_parts.end() ) + { + result = *found; + } + return result; +} + +stk::mesh::Part * Part_Decomposition_Fixture::findSuperset(const std::string & superset_name, const stk::mesh::Part * const part) +{ + stk::mesh::Part * result = nullptr; + stk::mesh::PartVector::const_iterator found; + found = std::find_if(part->supersets().begin(), part->supersets().end(), PartNameIs(superset_name)); + if( found != part->supersets().end() ) + { + result = *found; + } + return result; +} + +PhaseVec Part_Decomposition_Fixture::ls_phases(int num_ls, bool one_phase_per_ls) +{ + static bool init = false; + static std::vector phase_tags; + static std::vector named_phases; + if(!init) + { + const LevelSet_Identifier id0(0); + const LevelSet_Identifier id1(1); + const LevelSet_Identifier id2(2); + const LevelSet_Identifier id3(3); + PhaseTag pp, nn, pn, np; + pp.add(id0,1); pp.add(id1,1); + nn.add(id0,-1); nn.add(id1,-1); + pn.add(id0,1); pn.add(id1,-1); + np.add(id0,-1); np.add(id1,1); + + phase_tags.push_back(pp); + phase_tags.push_back(nn); + phase_tags.push_back(pn); + phase_tags.push_back(np); + + named_phases.push_back(NamedPhase("A", pp)); + named_phases.push_back(NamedPhase("B", nn)); + named_phases.push_back(NamedPhase("C", pn)); + named_phases.push_back(NamedPhase("D", np)); + + PhaseTag ls1, ls2, ls3, ls4; + ls1.add(id0,-1); + ls2.add(id1,-1); + ls3.add(id2,-1); + ls4.add(id3,-1); + named_phases.push_back(NamedPhase("LS1", ls1)); + named_phases.push_back(NamedPhase("LS2", ls2)); + named_phases.push_back(NamedPhase("LS3", ls3)); + named_phases.push_back(NamedPhase("LS4", ls4)); + + init = true; + } + + PhaseVec result; + if(!one_phase_per_ls) + { + result.push_back(named_phases[0]); + result.push_back(named_phases[1]); + if(num_ls == 2) + { + result.push_back(named_phases[2]); + result.push_back(named_phases[3]); + } + } + else + { + for(int i=0; i < num_ls; ++i) + { + result.push_back(named_phases[4+i]); + } + } + return result; +} + +PhaseVec Part_Decomposition_Fixture::death_phases() +{ + static bool init = false; + const LevelSet_Identifier id0(0); + static PhaseTag pos, neg; + pos.add(id0,1); neg.add(id0,-1); + static NamedPhase dead("dead", pos), alive("", neg); + static PhaseVec named_phases; + if(!init) + { + named_phases.push_back(alive); + named_phases.push_back(dead); + init = true; + } + + return named_phases; +} + +const Interface_Name_Generator & Part_Decomposition_Fixture::ls_name_generator() +{ + static LS_Name_Generator name_gen; + return name_gen; +} + +const Interface_Name_Generator & Part_Decomposition_Fixture::death_name_generator() +{ + static Death_Name_Generator name_gen("test"); + return name_gen; +} + +void Part_Decomposition_Fixture::performDecomposition(const stk::mesh::PartVector & used_blocks, + const Block_Surface_Connectivity & input_block_surface_info, + bool cdfem_death, int num_ls, bool one_ls_per_phase) +{ + Phase_Support & phase_support = Phase_Support::get(get_meta_data()); + phase_support.set_input_block_surface_connectivity(input_block_surface_info); + if(cdfem_death) + { + phase_support.decompose_blocks(used_blocks, + Part_Decomposition_Fixture::death_phases(), Part_Decomposition_Fixture::death_name_generator()); + phase_support.build_decomposed_block_surface_connectivity(); + } + else + { + phase_support.decompose_blocks(used_blocks, + Part_Decomposition_Fixture::ls_phases(num_ls, one_ls_per_phase), Part_Decomposition_Fixture::ls_name_generator()); + phase_support.build_decomposed_block_surface_connectivity(); + } +} + +} // namespace krino diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.hpp b/packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.hpp new file mode 100644 index 000000000000..5239d8f69a28 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Part_Decomposition_Fixture.hpp @@ -0,0 +1,75 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_UNIT_PHASE_SUPPORT_H_ +#define AKRI_UNIT_PHASE_SUPPORT_H_ + +#include + +#include +#include +#include +#include + +namespace stk { namespace mesh { class Part; } } +namespace stk { namespace mesh { class MetaData; } } +namespace krino { class Block_Surface_Connectivity; } +namespace krino { class Interface_Name_Generator; } +namespace krino { class NamedPhase; } + +namespace krino +{ + +struct PartNameIs +{ + PartNameIs(const std::string & name) : match_name(name) {} + bool operator()(const stk::mesh::Part * part) { + return part->name() == match_name; + } + std::string match_name; +}; + +class Part_Decomposition_Fixture : public ::testing::Test +{ +public: + Part_Decomposition_Fixture(); + virtual ~Part_Decomposition_Fixture(); + + void performDecomposition(const std::vector & used_blocks, const Block_Surface_Connectivity & input_block_surface_info, bool cdfem_death, int num_ls = 1, bool one_ls_per_phase=false); + + stk::mesh::MetaData & get_meta_data(); + + Block_Surface_Connectivity addOneSidedSideset(); + Block_Surface_Connectivity addTwoSidedSideset(); + +protected: + stk::mesh::Part * findPart(const std::string & part_name); + stk::mesh::Part * findSuperset(const std::string & superset_name, const stk::mesh::Part * const part); + void assert_conformal_part_exists(const std::string & conformal_part_name, const std::string & nonconformal_part_name) + { + const stk::mesh::Part * conformal_part = findPart(conformal_part_name); + Phase_Support & phase_support = Phase_Support::get(get_meta_data()); + ASSERT_TRUE( conformal_part != NULL ); + EXPECT_TRUE( phase_support.is_conformal(conformal_part) ); + EXPECT_EQ(nonconformal_part_name, phase_support.find_nonconformal_part(*conformal_part)->name() ); + } + +private: + static PhaseVec ls_phases(int num_ls, bool one_ls_per_phase=false); + static PhaseVec death_phases(); + + static const Interface_Name_Generator & ls_name_generator(); + static const Interface_Name_Generator & death_name_generator(); + + SimpleStkFixture fixture; + LogRedirecter log; +}; + +} // namespace krino + +#endif /* AKRI_UNIT_PHASE_SUPPORT_H_ */ diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Phase_Support.cpp b/packages/krino/krino/unit_tests/Akri_Unit_Phase_Support.cpp new file mode 100644 index 000000000000..f6b66c10a291 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Phase_Support.cpp @@ -0,0 +1,231 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include + +#include + +namespace krino +{ + +TEST_F(Part_Decomposition_Fixture, One_Block_LS) +{ + Block_Surface_Connectivity block_surface_info; + performDecomposition({findPart("block_1")}, block_surface_info, false); + + assert_conformal_part_exists("block_1_A", "block_1_nonconformal"); + assert_conformal_part_exists("block_1_B", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_A_B", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_B_A", "block_1_nonconformal"); +} + +TEST_F(Part_Decomposition_Fixture, One_Block_Death) +{ + Block_Surface_Connectivity block_surface_info; + performDecomposition({findPart("block_1")}, block_surface_info, true); + + assert_conformal_part_exists("block_1", "block_1_nonconformal"); + assert_conformal_part_exists("block_1_dead", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_test", "block_1_nonconformal"); +} + +TEST_F(Part_Decomposition_Fixture, OneSidedSideset_LS) +{ + Block_Surface_Connectivity block_surface_info = addOneSidedSideset(); + performDecomposition({findPart("block_1")}, block_surface_info, false); + + assert_conformal_part_exists("surface_1_A", "surface_1_nonconformal"); + assert_conformal_part_exists("surface_1_B", "surface_1_nonconformal"); +} + +TEST_F(Part_Decomposition_Fixture, OneSidedSideset_Death) +{ + Block_Surface_Connectivity block_surface_info = addOneSidedSideset(); + performDecomposition({findPart("block_1")}, block_surface_info, true); + + assert_conformal_part_exists("surface_1", "surface_1_nonconformal"); + assert_conformal_part_exists("surface_1_dead", "surface_1_nonconformal"); +} + +TEST_F(Part_Decomposition_Fixture, TwoSidedSideset_LS) +{ + Block_Surface_Connectivity block_surface_info = addTwoSidedSideset(); + performDecomposition({findPart("block_1")}, block_surface_info, false); + + //const stk::mesh::Part * surf_1_A_block_1 = findPart("surface_1_A_block_1"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_A_block_1 = findPart("surface_block_1_tri3_1_A"); + ASSERT_TRUE( surf_1_A_block_1 != NULL ); + EXPECT_TRUE( findSuperset("surface_1_A", surf_1_A_block_1) != NULL ); + + //const stk::mesh::Part * surf_1_B_block_1 = findPart("surface_1_B_block_1"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_B_block_1 = findPart("surface_block_1_tri3_1_B"); + ASSERT_TRUE( surf_1_B_block_1 != NULL ); + EXPECT_TRUE( findSuperset("surface_1_B", surf_1_B_block_1) != NULL ); + + //const stk::mesh::Part * surf_1_A_block_2 = findPart("surface_1_A_block_2"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_A_block_2 = findPart("surface_block_2_tri3_1_A"); + ASSERT_TRUE( surf_1_A_block_2 != NULL ); + EXPECT_TRUE( findSuperset("surface_1_A", surf_1_A_block_2) != NULL ); + + //const stk::mesh::Part * surf_1_B_block_2 = findPart("surface_1_B_block_2"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_B_block_2 = findPart("surface_block_2_tri3_1_B"); + ASSERT_TRUE( surf_1_B_block_2 != NULL ); + EXPECT_TRUE( findSuperset("surface_1_B", surf_1_B_block_2) != NULL ); +} + +TEST_F(Part_Decomposition_Fixture, TwoSidedSideset_Death) +{ + Block_Surface_Connectivity block_surface_info = addTwoSidedSideset(); + performDecomposition({findPart("block_1")}, block_surface_info, true); + + //const stk::mesh::Part * surf_1_block_1 = findPart("surface_1_block_1"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_block_1 = findPart("surface_block_1_tri3_1"); + ASSERT_TRUE( surf_1_block_1 != NULL ); + EXPECT_TRUE( findSuperset("surface_1", surf_1_block_1) != NULL ); + + //const stk::mesh::Part * surf_1_dead_block_1 = findPart("surface_1_dead_block_1"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_dead_block_1 = findPart("surface_block_1_tri3_1_dead"); + ASSERT_TRUE( surf_1_dead_block_1 != NULL ); + EXPECT_TRUE( findSuperset("surface_1_dead", surf_1_dead_block_1) != NULL ); + + //const stk::mesh::Part * surf_1_block_2 = findPart("surface_1_block_2"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_block_2 = findPart("surface_block_2_tri3_1"); + ASSERT_TRUE( surf_1_block_2 != NULL ); + EXPECT_TRUE( findSuperset("surface_1", surf_1_block_2) != NULL ); + + //const stk::mesh::Part * surf_1_dead_block_2 = findPart("surface_1_dead_block_2"); // No support for aliases in stk yet + const stk::mesh::Part * surf_1_dead_block_2 = findPart("surface_block_2_tri3_1_dead"); + ASSERT_TRUE( surf_1_dead_block_2 != NULL ); + EXPECT_TRUE( findSuperset("surface_1_dead", surf_1_dead_block_2) != NULL ); +} + +TEST_F(Part_Decomposition_Fixture, Multiple_LS_Decomposition) +{ + Block_Surface_Connectivity block_surface_info; + performDecomposition({findPart("block_1")}, block_surface_info, false, 2); + + assert_conformal_part_exists("block_1_A", "block_1_nonconformal"); + assert_conformal_part_exists("block_1_B", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_A_B", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_B_A", "block_1_nonconformal"); + + assert_conformal_part_exists("block_1_C", "block_1_nonconformal"); + assert_conformal_part_exists("block_1_D", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_C_D", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_D_C", "block_1_nonconformal"); + + assert_conformal_part_exists("surface_block_1_A_C", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_C_A", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_A_D", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_D_A", "block_1_nonconformal"); + + assert_conformal_part_exists("surface_block_1_B_C", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_C_B", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_B_D", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_D_B", "block_1_nonconformal"); +} + +TEST_F(Part_Decomposition_Fixture, find_conformal_io_part) +{ + std::vector decomposed_blocks; + decomposed_blocks.push_back("block_1"); + Block_Surface_Connectivity block_surface_info; + + stk::mesh::Part * block_1 = findPart("block_1"); + performDecomposition({block_1}, block_surface_info, false, 2); + const LevelSet_Identifier id0(0); + const LevelSet_Identifier id1(1); + LS_SideTag p0(id0,1); + LS_SideTag p1(id1,1); + LS_SideTag n0(id0,-1); + LS_SideTag n1(id1,-1); + PhaseTag pp, nn, pn, np; + pp.add(p0); pp.add(p1); // "A" + nn.add(n0); nn.add(n1); // "B" + pn.add(p0); pn.add(n1); // "C" + np.add(n0); np.add(p1); // "D" + + // Test volume conformal io part lookup + Phase_Support & phase_support = Phase_Support::get(get_meta_data()); + const stk::mesh::Part * block_1_A = phase_support.find_conformal_io_part(*block_1, pp); + ASSERT_TRUE( block_1_A != NULL ); + EXPECT_EQ( "block_1_A", block_1_A->name() ); + + const stk::mesh::Part * block_1_B = phase_support.find_conformal_io_part(*block_1, nn); + ASSERT_TRUE( block_1_B != NULL ); + EXPECT_EQ( "block_1_B", block_1_B->name() ); + + const stk::mesh::Part * block_1_C = phase_support.find_conformal_io_part(*block_1, pn); + ASSERT_TRUE( block_1_C != NULL ); + EXPECT_EQ( "block_1_C", block_1_C->name() ); + + const stk::mesh::Part * block_1_D = phase_support.find_conformal_io_part(*block_1, np); + ASSERT_TRUE( block_1_D != NULL ); + EXPECT_EQ( "block_1_D", block_1_D->name() ); + + const stk::mesh::Part * surface_block_1_A_B = phase_support.find_interface_part(*block_1_A, *block_1_B); + ASSERT_TRUE( surface_block_1_A_B != NULL ); + EXPECT_EQ( "surface_block_1_A_B", surface_block_1_A_B->name() ); + + const stk::mesh::Part * surface_block_1_A_C = phase_support.find_interface_part(*block_1_A, *block_1_C); + ASSERT_TRUE( surface_block_1_A_C != NULL ); + EXPECT_EQ( "surface_block_1_A_C", surface_block_1_A_C->name() ); + + const stk::mesh::Part * surface_block_1_A_D = phase_support.find_interface_part(*block_1_A, *block_1_D); + ASSERT_TRUE( surface_block_1_A_D != NULL ); + EXPECT_EQ( "surface_block_1_A_D", surface_block_1_A_D->name() ); + + const stk::mesh::Part * surface_block_1_C_D = phase_support.find_interface_part(*block_1_C, *block_1_D); + ASSERT_TRUE( surface_block_1_C_D != NULL ); + EXPECT_EQ( "surface_block_1_C_D", surface_block_1_C_D->name() ); +} + +TEST_F(Part_Decomposition_Fixture, get_blocks_touching_surface) +{ + Block_Surface_Connectivity block_surface_info = addTwoSidedSideset(); + performDecomposition({findPart("block_1")}, block_surface_info, false); + + std::vector blocks; + + blocks = get_meta_data().get_blocks_touching_surface(get_meta_data().get_part("surface_1_A")); + EXPECT_EQ( 2u, blocks.size() ); + EXPECT_TRUE( blocks.end() != std::find(blocks.begin(), blocks.end(), get_meta_data().get_part("block_1_A"))); + EXPECT_TRUE( blocks.end() != std::find(blocks.begin(), blocks.end(), get_meta_data().get_part("block_2"))); + + blocks = get_meta_data().get_blocks_touching_surface(get_meta_data().get_part("surface_block_1_tri3_1_A")); + EXPECT_EQ( 1u, blocks.size() ); + EXPECT_TRUE( blocks.end() != std::find(blocks.begin(), blocks.end(), get_meta_data().get_part("block_1_A"))); + + blocks = get_meta_data().get_blocks_touching_surface(get_meta_data().get_part("surface_block_2_tri3_1_A")); + EXPECT_EQ( 1u, blocks.size() ); + EXPECT_TRUE( blocks.end() != std::find(blocks.begin(), blocks.end(), get_meta_data().get_part("block_2"))); + + blocks = get_meta_data().get_blocks_touching_surface(get_meta_data().get_part("surface_block_1_A_B")); + EXPECT_EQ( 1u, blocks.size() ); + EXPECT_TRUE( blocks.end() != std::find(blocks.begin(), blocks.end(), get_meta_data().get_part("block_1_A"))); + + blocks = get_meta_data().get_blocks_touching_surface(get_meta_data().get_part("surface_block_1_B_A")); + EXPECT_EQ( 1u, blocks.size() ); + EXPECT_TRUE( blocks.end() != std::find(blocks.begin(), blocks.end(), get_meta_data().get_part("block_1_B"))); + +} + +TEST_F(Part_Decomposition_Fixture, One_Block_Two_LS_One_LS_Per_Phase) +{ + Block_Surface_Connectivity block_surface_info; + performDecomposition({findPart("block_1")}, block_surface_info, false, 2, true); + + assert_conformal_part_exists("block_1_LS1", "block_1_nonconformal"); + assert_conformal_part_exists("block_1_LS2", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_LS1_LS2", "block_1_nonconformal"); + assert_conformal_part_exists("surface_block_1_LS2_LS1", "block_1_nonconformal"); +} + +} // namespace krino diff --git a/packages/krino/krino/unit_tests/Akri_Unit_RebalanceUtils.cpp b/packages/krino/krino/unit_tests/Akri_Unit_RebalanceUtils.cpp new file mode 100644 index 000000000000..388ddd4a5c9b --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_RebalanceUtils.cpp @@ -0,0 +1,446 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace krino { +namespace rebalance_utils { + +namespace { + +/* + * Builds a single tri mesh with 1 level of adaptivity. Parent element is ID 1, + * Child elem and node IDs are in the ascii art below + * + * 3 + * / \ + * / 5 \ + * 6 /_____\ 5 + * /\ /\ + * / \ 3 / \ + * / 2 \ / 4 \ + * /______\/_____\ + * 1 4 2 + */ + +void create_block_and_register_fields(SimpleStkFixture & fixture) +{ + auto & meta = fixture.meta_data(); + meta.declare_part_with_topology("block_1", stk::topology::TRIANGLE_3_2D); + + meta.declare_field > + (stk::topology::NODE_RANK, "coordinates"); + auto & load_field = + meta.declare_field>(stk::topology::ELEMENT_RANK, "element_weights"); + stk::mesh::put_field_on_mesh(load_field, meta.universal_part(), nullptr); + + fixture.commit(); +} + +void build_unadapted_single_tri_mesh(SimpleStkFixture & fixture) +{ + auto & bulk_data = fixture.bulk_data(); + auto & block_1 = *fixture.meta_data().get_part("block_1"); + + bulk_data.modification_begin(); + if(bulk_data.parallel_rank() == 0) + { + stk::mesh::declare_element(bulk_data, block_1, 1, {1, 2, 3}); + } + bulk_data.modification_end(); +} + +void build_one_level_adapted_single_tri_mesh(SimpleStkFixture & fixture) +{ + auto & bulk_data = fixture.bulk_data(); + auto & block_1 = *fixture.meta_data().get_part("block_1"); + + bulk_data.modification_begin(); + if(bulk_data.parallel_rank() == 0) + { + auto parent_elem = stk::mesh::declare_element(bulk_data, block_1, 1, {1, 2, 3}); + auto child1 = stk::mesh::declare_element(bulk_data, block_1, 2, {1, 4, 6}); + auto child2 = stk::mesh::declare_element(bulk_data, block_1, 3, {4, 5, 6}); + auto child3 = stk::mesh::declare_element(bulk_data, block_1, 4, {4, 2, 5}); + auto child4 = stk::mesh::declare_element(bulk_data, block_1, 5, {6, 5, 3}); + auto family_tree = bulk_data.declare_constraint(1); + bulk_data.declare_relation(family_tree, parent_elem, 0); + bulk_data.declare_relation(family_tree, child1, 1); + bulk_data.declare_relation(family_tree, child2, 2); + bulk_data.declare_relation(family_tree, child3, 3); + bulk_data.declare_relation(family_tree, child4, 4); + } + bulk_data.modification_end(); +} + +void build_two_level_adapted_single_tri_mesh(SimpleStkFixture & fixture) +{ + build_one_level_adapted_single_tri_mesh(fixture); + + auto & bulk_data = fixture.bulk_data(); + auto & block_1 = *fixture.meta_data().get_part("block_1"); + + // Refine child elem 3 an additional time and make others into transition elements + bulk_data.modification_begin(); + if(bulk_data.parallel_rank() == 0) + { + { + auto parent = bulk_data.get_entity(stk::topology::ELEMENT_RANK, 3); + auto child1 = stk::mesh::declare_element(bulk_data, block_1, 6, {4, 8, 7}); + auto child2 = stk::mesh::declare_element(bulk_data, block_1, 7, {7, 8, 9}); + auto child3 = stk::mesh::declare_element(bulk_data, block_1, 8, {8, 5, 9}); + auto child4 = stk::mesh::declare_element(bulk_data, block_1, 9, {9, 6, 7}); + + auto family_tree = bulk_data.declare_constraint(2); + bulk_data.declare_relation(family_tree, parent, 0); + bulk_data.declare_relation(family_tree, child1, 1); + bulk_data.declare_relation(family_tree, child2, 2); + bulk_data.declare_relation(family_tree, child3, 3); + bulk_data.declare_relation(family_tree, child4, 4); + } + + { + auto parent = bulk_data.get_entity(stk::topology::ELEMENT_RANK, 2); + auto child1 = stk::mesh::declare_element(bulk_data, block_1, 10, {1, 4, 7}); + auto child2 = stk::mesh::declare_element(bulk_data, block_1, 11, {1, 7, 6}); + + auto family_tree = bulk_data.declare_constraint(3); + bulk_data.declare_relation(family_tree, parent, 0); + bulk_data.declare_relation(family_tree, child1, 1); + bulk_data.declare_relation(family_tree, child2, 2); + } + { + auto parent = bulk_data.get_entity(stk::topology::ELEMENT_RANK, 4); + auto child1 = stk::mesh::declare_element(bulk_data, block_1, 12, {4, 2, 8}); + auto child2 = stk::mesh::declare_element(bulk_data, block_1, 13, {2, 5, 8}); + + auto family_tree = bulk_data.declare_constraint(4); + bulk_data.declare_relation(family_tree, parent, 0); + bulk_data.declare_relation(family_tree, child1, 1); + bulk_data.declare_relation(family_tree, child2, 2); + } + { + auto parent = bulk_data.get_entity(stk::topology::ELEMENT_RANK, 4); + auto child1 = stk::mesh::declare_element(bulk_data, block_1, 14, {9, 5, 3}); + auto child2 = stk::mesh::declare_element(bulk_data, block_1, 15, {9, 3, 6}); + + auto family_tree = bulk_data.declare_constraint(5); + bulk_data.declare_relation(family_tree, parent, 0); + bulk_data.declare_relation(family_tree, child1, 1); + bulk_data.declare_relation(family_tree, child2, 2); + } + } + bulk_data.modification_end(); +} + +} + +TEST(UpdateRebalanceForAdaptivity, OneLevel) +{ + SimpleStkFixture fixture(2, MPI_COMM_SELF); + create_block_and_register_fields(fixture); + build_one_level_adapted_single_tri_mesh(fixture); + + const auto & mesh = fixture.bulk_data(); + + stk::balance::DecompositionChangeList change_list(fixture.bulk_data(), {}); + auto parent_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, 1); + + stk::mesh::EntityVector child_elems; + for(int child_id=2; child_id < 6; ++child_id) + { + auto child_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, child_id); + ASSERT_TRUE(mesh.is_valid(child_elem)); + child_elems.push_back(child_elem); + } + + const int dest_proc = 2; + change_list.set_entity_destination(parent_elem, dest_proc); + + for(auto && child_elem : child_elems) + { + ASSERT_FALSE(change_list.has_entity(child_elem)); + } + + impl::update_rebalance_for_adaptivity(change_list, mesh); + + for(auto && child_elem : child_elems) + { + EXPECT_TRUE(change_list.has_entity(child_elem)); + EXPECT_EQ(dest_proc, change_list.get_entity_destination(child_elem)); + } +} + +TEST(UpdateRebalanceForAdaptivity, OneLevelChildMovedWithoutParent) +{ + SimpleStkFixture fixture(2, MPI_COMM_SELF); + create_block_and_register_fields(fixture); + build_one_level_adapted_single_tri_mesh(fixture); + + const auto & mesh = fixture.bulk_data(); + + stk::balance::DecompositionChangeList change_list(fixture.bulk_data(), {}); + + stk::mesh::EntityVector child_elems; + for(int child_id=2; child_id < 6; ++child_id) + { + auto child_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, child_id); + ASSERT_TRUE(mesh.is_valid(child_elem)); + child_elems.push_back(child_elem); + change_list.set_entity_destination(child_elem, child_id); + } + + impl::update_rebalance_for_adaptivity(change_list, mesh); + + for(auto && child_elem : child_elems) + { + EXPECT_FALSE(change_list.has_entity(child_elem)); + } +} + +TEST(UpdateRebalanceForAdaptivity, TwoLevels) +{ + SimpleStkFixture fixture(2, MPI_COMM_SELF); + create_block_and_register_fields(fixture); + build_two_level_adapted_single_tri_mesh(fixture); + + const auto & mesh = fixture.bulk_data(); + + stk::balance::DecompositionChangeList change_list(fixture.bulk_data(), {}); + auto parent_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, 1); + + stk::mesh::EntityVector child_elems; + for(int child_id=2; child_id < 15; ++child_id) + { + auto child_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, child_id); + ASSERT_TRUE(mesh.is_valid(child_elem)); + child_elems.push_back(child_elem); + } + + const int dest_proc = 2; + change_list.set_entity_destination(parent_elem, dest_proc); + + impl::update_rebalance_for_adaptivity(change_list, mesh); + + for(auto && child_elem : child_elems) + { + EXPECT_TRUE(change_list.has_entity(child_elem)); + EXPECT_EQ(dest_proc, change_list.get_entity_destination(child_elem)); + } + + stk::mesh::EntityVector family_trees; + mesh.get_entities( + stk::topology::CONSTRAINT_RANK, mesh.mesh_meta_data().locally_owned_part(), family_trees); + for(auto && ft : family_trees) + { + EXPECT_TRUE(change_list.has_entity(ft)); + EXPECT_EQ(dest_proc, change_list.get_entity_destination(ft)); + } +} + +TEST(UpdateRebalanceForAdaptivity, TwoLevelsFirstLevelChildInitialDifferentProc) +{ + SimpleStkFixture fixture(2, MPI_COMM_SELF); + create_block_and_register_fields(fixture); + build_two_level_adapted_single_tri_mesh(fixture); + + const auto & mesh = fixture.bulk_data(); + + stk::balance::DecompositionChangeList change_list(fixture.bulk_data(), {}); + auto parent_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, 1); + + stk::mesh::EntityVector child_elems; + for(int child_id=2; child_id < 15; ++child_id) + { + auto child_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, child_id); + ASSERT_TRUE(mesh.is_valid(child_elem)); + child_elems.push_back(child_elem); + } + + const int dest_proc = 2; + change_list.set_entity_destination(parent_elem, dest_proc); + change_list.set_entity_destination(mesh.get_entity(stk::topology::ELEMENT_RANK, 3), 5); + + impl::update_rebalance_for_adaptivity(change_list, mesh); + + for(auto && child_elem : child_elems) + { + EXPECT_TRUE(change_list.has_entity(child_elem)); + EXPECT_EQ(dest_proc, change_list.get_entity_destination(child_elem)); + } +} + +TEST(AccumulateAdaptivityChildWeights, TwoLevelAdaptedTri) +{ + SimpleStkFixture fixture(2, MPI_COMM_SELF); + create_block_and_register_fields(fixture); + build_two_level_adapted_single_tri_mesh(fixture); + + const auto & mesh = fixture.bulk_data(); + + stk::mesh::Field & weights_field = static_cast &>( + *fixture.meta_data().get_field(stk::topology::ELEMENT_RANK, "element_weights")); + + auto parent_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, 1); + double & parent_weight = *stk::mesh::field_data(weights_field, parent_elem); + parent_weight = 10.; + stk::mesh::EntityVector child_elems; + for (int child_id = 2; child_id < 15; ++child_id) + { + auto child_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, child_id); + ASSERT_TRUE(mesh.is_valid(child_elem)); + child_elems.push_back(child_elem); + + double & weight = *stk::mesh::field_data(weights_field, child_elem); + weight = 1.; + } + + impl::accumulate_adaptivity_child_weights_to_parents(mesh, weights_field); + + for (auto && child_elem : child_elems) + { + const double & weight = *stk::mesh::field_data(weights_field, child_elem); + EXPECT_DOUBLE_EQ(0., weight); + } + EXPECT_DOUBLE_EQ(23., parent_weight); +} + +TEST(AccumulateAdaptivityChildWeights, UnadaptedElement) +{ + SimpleStkFixture fixture(2, MPI_COMM_SELF); + create_block_and_register_fields(fixture); + build_unadapted_single_tri_mesh(fixture); + + const auto & mesh = fixture.bulk_data(); + + stk::mesh::Field & weights_field = static_cast &>( + *fixture.meta_data().get_field(stk::topology::ELEMENT_RANK, "element_weights")); + + auto parent_elem = mesh.get_entity(stk::topology::ELEMENT_RANK, 1); + double & parent_weight = *stk::mesh::field_data(weights_field, parent_elem); + parent_weight = 10.; + + impl::accumulate_adaptivity_child_weights_to_parents(mesh, weights_field); + + EXPECT_DOUBLE_EQ(10., parent_weight); +} + +TEST(Rebalance, MultipleWeightFields) +{ + SimpleStkFixture fixture(2, MPI_COMM_WORLD); + + auto & mesh = fixture.bulk_data(); + auto & meta = fixture.meta_data(); + + if (mesh.parallel_size() != 2) return; + + stk::mesh::Part & block_1 = meta.declare_part_with_topology("block_1", stk::topology::QUAD_4_2D); + stk::mesh::Part & block_2 = meta.declare_part_with_topology("block_2", stk::topology::QUAD_4_2D); + + auto & coords_field = meta.declare_field>( + stk::topology::NODE_RANK, "coordinates"); + stk::mesh::put_field_on_mesh(coords_field, meta.universal_part(), nullptr); + + auto & weights_field_1 = meta.declare_field>( + stk::topology::ELEMENT_RANK, "element_weights_1"); + stk::mesh::put_field_on_mesh(weights_field_1, block_1, nullptr); + + auto & weights_field_2 = meta.declare_field>( + stk::topology::ELEMENT_RANK, "element_weights_2"); + stk::mesh::put_field_on_mesh(weights_field_2, block_2, nullptr); + + meta.commit(); + + // Create mesh with two disconnected blocks each containing two elements. Initially block_1 will + // be + // all owned by P0 and block_2 all by P1. Each element gets a weight of 1 in the weight field for + // its + // block, rebalance should put one element from each block on each proc. + const std::vector elem_nodes{ + {1, 2, 5, 4}, {2, 3, 6, 5}, {7, 8, 11, 12}, {8, 9, 10, 11}}; + const std::vector elem_procs{0, 0, 1, 1}; + std::vector elem_parts{{&block_1}, {&block_1}, {&block_2}, {&block_2}}; + std::vector> node_coords{{0, 0}, + {1, 0}, + {2, 0}, + {2, 1}, + {1, 1}, + {0, 1}, + {3, 0}, + {4, 0}, + {5, 0}, + {3, 1}, + {4, 1}, + {5, 1}}; + + build_mesh(mesh, elem_nodes, elem_procs, elem_parts); + + for (auto && b : mesh.buckets(stk::topology::NODE_RANK)) + { + for (auto && node : *b) + { + double * coords = stk::mesh::field_data(coords_field, node); + coords[0] = node_coords[mesh.identifier(node) - 1][0]; + coords[1] = node_coords[mesh.identifier(node) - 1][1]; + } + } + + stk::mesh::field_fill(1., weights_field_1); + stk::mesh::field_fill(1., weights_field_2); + + const auto parallel_rank = mesh.parallel_rank(); + const auto & owned_part = meta.locally_owned_part(); + if (parallel_rank == 0) + { + EXPECT_EQ(2u, + stk::mesh::count_selected_entities( + owned_part & block_1, mesh.buckets(stk::topology::ELEMENT_RANK))); + EXPECT_EQ(0u, + stk::mesh::count_selected_entities( + owned_part & block_2, mesh.buckets(stk::topology::ELEMENT_RANK))); + } + if (parallel_rank == 1) + { + EXPECT_EQ(0u, + stk::mesh::count_selected_entities( + owned_part & block_1, mesh.buckets(stk::topology::ELEMENT_RANK))); + EXPECT_EQ(2u, + stk::mesh::count_selected_entities( + owned_part & block_2, mesh.buckets(stk::topology::ELEMENT_RANK))); + } + + rebalance_utils::rebalance_mesh( + mesh, nullptr, {weights_field_1.name(), weights_field_2.name()}, "coordinates", "parmetis"); + + EXPECT_EQ(1u, + stk::mesh::count_selected_entities( + owned_part & block_1, mesh.buckets(stk::topology::ELEMENT_RANK))); + EXPECT_EQ(1u, + stk::mesh::count_selected_entities( + owned_part & block_2, mesh.buckets(stk::topology::ELEMENT_RANK))); +} +} +} + diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.cpp b/packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.cpp new file mode 100644 index 000000000000..6db2f2adce90 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.cpp @@ -0,0 +1,136 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace krino { + +void SimpleStkFixture::write_results(const std::string & filename, stk::mesh::BulkData & mesh, const bool use64bitIds) +{ + stk::io::StkMeshIoBroker io(mesh.parallel()); + io.set_bulk_data(mesh); + + Ioss::PropertyManager properties; + if (use64bitIds) + { + properties.add(Ioss::Property("INTEGER_SIZE_API", 8)); + properties.add(Ioss::Property("INTEGER_SIZE_DB", 8)); + } + + auto index = io.create_output_mesh(filename, stk::io::WRITE_RESULTS, properties); + io.write_output_mesh(index); + + for(auto && field : mesh.mesh_meta_data().get_fields()) + { + io.add_field(index, *field); + } + + io.begin_output_step(index, 0.); + io.write_defined_output_fields(index); + io.end_output_step(index); +} + +SingleElementFixture::SingleElementFixture(const stk::topology & topology) + : my_topology(topology), + stk_fixture(my_topology.dimension()) +{ + AuxMetaData & aux_meta = AuxMetaData::get(stk_fixture.meta_data()); + block_part = &stk_fixture.meta_data().declare_part_with_topology("block_1", my_topology); + const FieldType & vec_type = (my_topology.dimension() == 3) ? FieldType::VECTOR_3D : FieldType::VECTOR_2D; + coord_field = aux_meta.register_field("coordinates", vec_type, stk::topology::NODE_RANK, 1u, 1u, *block_part); + CDFEM_Support::get(stk_fixture.meta_data()).set_coords_field( coord_field ); + scalar_field = aux_meta.register_field("scalar_field", FieldType::REAL, stk::topology::NODE_RANK, 1u, 1u, *block_part); + stk_fixture.commit(); +} + +void +SingleElementFixture::generate_mesh() +{ + const AuxMetaData & aux_meta = AuxMetaData::get(stk_fixture.meta_data()); + stk_fixture.bulk_data().modification_begin(); + { + stk::mesh::PartVector elem_parts; + elem_parts.push_back(block_part); + elem_parts.push_back(&aux_meta.active_part()); + elem_parts.push_back(&stk_fixture.meta_data().locally_owned_part()); + stk::mesh::EntityIdVector node_ids(my_topology.num_nodes()); + for(unsigned i=0; i < node_ids.size(); ++i) + { + node_ids[i] = i+1; + } + const stk::mesh::EntityId elem_id = 1; + my_elem = stk::mesh::declare_element( stk_fixture.bulk_data(), elem_parts, elem_id, node_ids ); + const stk::mesh::Entity * const nodes = stk_fixture.bulk_data().begin_nodes(my_elem); + for(unsigned i=0; i < node_ids.size(); ++i) + { + EXPECT_EQ(node_ids[i], stk_fixture.bulk_data().identifier(nodes[i])); + } + } + stk_fixture.bulk_data().modification_end(); + + // set node coordinates + stk::mesh::field_fill(0., coord_field); + const stk::mesh::Entity * const nodes = stk_fixture.bulk_data().begin_nodes(my_elem); + for(unsigned i=0; i < my_topology.num_nodes(); ++i) + { + if (i > 0) + { + double * node_coords = field_data(coord_field, nodes[i]); + node_coords[i-1] = 1.0; + } + } +} + +TEST(SingleElementFixture, tri3) +{ + stk::topology tri3 = stk::topology::TRIANGLE_3_2D; + SingleElementFixture test_fixture(tri3); + + test_fixture.generate_mesh(); + + EXPECT_EQ(2u, test_fixture.stk_fixture.meta_data().spatial_dimension()); + + const stk::mesh::BucketVector & elem_buckets = test_fixture.stk_fixture.bulk_data().buckets(stk::topology::ELEMENT_RANK); + ASSERT_EQ(1u, elem_buckets.size()); + EXPECT_EQ(1u, (*elem_buckets.begin())->size()); + EXPECT_EQ(3u, test_fixture.stk_fixture.bulk_data().num_nodes((*elem_buckets[0])[0])); + const stk::mesh::BucketVector & node_buckets = test_fixture.stk_fixture.bulk_data().buckets(stk::topology::NODE_RANK); + ASSERT_EQ(1u, node_buckets.size()); + EXPECT_EQ(3u, (*node_buckets.begin())->size()); +} + +TEST(SingleElementFixture, tet4) +{ + stk::topology tet4 = stk::topology::TETRAHEDRON_4; + SingleElementFixture test_fixture(tet4); + + test_fixture.generate_mesh(); + + EXPECT_EQ(3u, test_fixture.stk_fixture.meta_data().spatial_dimension()); + + const stk::mesh::BucketVector & elem_buckets = test_fixture.stk_fixture.bulk_data().buckets(stk::topology::ELEMENT_RANK); + ASSERT_EQ(1u, elem_buckets.size()); + EXPECT_EQ(1u, (*elem_buckets.begin())->size()); + EXPECT_EQ(4u, test_fixture.stk_fixture.bulk_data().num_nodes((*elem_buckets[0])[0])); + const stk::mesh::BucketVector & node_buckets = test_fixture.stk_fixture.bulk_data().buckets(stk::topology::NODE_RANK); + ASSERT_EQ(1u, node_buckets.size()); + EXPECT_EQ(4u, (*node_buckets.begin())->size()); +} + +} diff --git a/packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.hpp b/packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.hpp new file mode 100644 index 000000000000..f3d969b484ba --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_Single_Element_Fixtures.hpp @@ -0,0 +1,79 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef AKRI_UNIT_SINGLE_ELEMENT_FIXTURES_H_ +#define AKRI_UNIT_SINGLE_ELEMENT_FIXTURES_H_ + +#include // for BulkData +#include // for MetaData + +#include "Akri_AuxMetaData.hpp" +#include "Akri_FieldRef.hpp" + +namespace krino { + +inline std::vector entity_rank_names_with_ft() +{ + auto entity_rank_names = stk::mesh::entity_rank_names(); + entity_rank_names.push_back("FAMILY_TREE"); + return entity_rank_names; +} + +class SimpleStkFixture +{ +public: + SimpleStkFixture(unsigned dimension, MPI_Comm comm = MPI_COMM_WORLD) + : meta(dimension, entity_rank_names_with_ft()), + bulk(meta, comm) + { + meta.set_mesh_bulk_data(&bulk); + AuxMetaData::create(meta); + } + void commit() { meta.commit(); } + void write_results(const std::string & filename) { write_results(filename, bulk); } + static void write_results(const std::string & filename, stk::mesh::BulkData & mesh, const bool use64bitIds = true); + stk::mesh::MetaData & meta_data() { return meta; } + stk::mesh::BulkData & bulk_data() { return bulk; } + +private: + stk::mesh::MetaData meta; + stk::mesh::BulkData bulk; +}; + +class SimpleStkFixture2d : public SimpleStkFixture +{ +public: + SimpleStkFixture2d(MPI_Comm comm = MPI_COMM_WORLD) : SimpleStkFixture(2) {} +}; + +class SimpleStkFixture3d : public SimpleStkFixture +{ +public: + SimpleStkFixture3d(MPI_Comm comm = MPI_COMM_WORLD) : SimpleStkFixture(3) {} +}; + +// Fixture to create a single element stk mesh of the given topology. +class SingleElementFixture +{ +public: + SingleElementFixture(const stk::topology & topology); + + void generate_mesh(); + + stk::topology my_topology; + SimpleStkFixture stk_fixture; + stk::mesh::Part * block_part; + FieldRef coord_field; + FieldRef scalar_field; + stk::mesh::Entity my_elem; +}; + + +} + +#endif /* AKRI_UNIT_SINGLE_ELEMENT_FIXTURES_H_ */ diff --git a/packages/krino/krino/unit_tests/Akri_Unit_main.cpp b/packages/krino/krino/unit_tests/Akri_Unit_main.cpp new file mode 100644 index 000000000000..d079d13ad299 --- /dev/null +++ b/packages/krino/krino/unit_tests/Akri_Unit_main.cpp @@ -0,0 +1,32 @@ +// Copyright 2002 - 2008, 2010, 2011 National Technology Engineering +// Solutions of Sandia, LLC (NTESS). Under the terms of Contract +// DE-NA0003525 with NTESS, the U.S. Government retains certain rights +// in this software. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include + +#include +#include +#include + +#include + +int main(int argc, char **argv) { + Kokkos::ScopeGuard guard(argc, argv); + + sierra::Env::set_input_file_required(false); + + testing::InitGoogleTest(&argc, argv); + + krino::Startup startup__(argc, argv); + + stk::unit_test_util::create_parallel_output(sierra::Env::parallel_rank()); + + return RUN_ALL_TESTS(); +} diff --git a/packages/krino/krino/unit_tests/CMakeLists.txt b/packages/krino/krino/unit_tests/CMakeLists.txt new file mode 100644 index 000000000000..3d92207e1423 --- /dev/null +++ b/packages/krino/krino/unit_tests/CMakeLists.txt @@ -0,0 +1,14 @@ + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_EXECUTABLE_AND_TEST( + krino_unit + SOURCES ${SOURCES} + COMM serial mpi + NUM_MPI_PROCS 1-4 + NOEXEPREFIX NOEXESUFFIX + ) diff --git a/packages/stk/stk_emend/stk_emend/independent_set/CMakeLists.txt b/packages/stk/stk_emend/stk_emend/independent_set/CMakeLists.txt index 65456ef38091..961a9218c3b9 100644 --- a/packages/stk/stk_emend/stk_emend/independent_set/CMakeLists.txt +++ b/packages/stk/stk_emend/stk_emend/independent_set/CMakeLists.txt @@ -46,8 +46,18 @@ # SET(HEADERS "") +SET(SOURCES "") + +INCLUDE_DIRECTORIES(${${PACKAGE_NAME}_SOURCE_DIR}) FILE(GLOB HEADERS *.hpp) +FILE(GLOB SOURCES *.cpp) + +TRIBITS_ADD_LIBRARY( + stk_emend + HEADERS ${HEADERS} + SOURCES ${SOURCES} + ) INSTALL(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/${${PROJECT_NAME}_INSTALL_INCLUDE_DIR}/stk_emend/independent_set) diff --git a/packages/stk/stk_emend/stk_emend/independent_set/IndependentSetDummy.cpp b/packages/stk/stk_emend/stk_emend/independent_set/IndependentSetDummy.cpp new file mode 100644 index 000000000000..b2ef6c6c65a0 --- /dev/null +++ b/packages/stk/stk_emend/stk_emend/independent_set/IndependentSetDummy.cpp @@ -0,0 +1,3 @@ +namespace independent_set { +void IndependentSetDummy() {} +}