// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "../fem.hpp" #include "../../mesh/nurbs.hpp" #include "../../general/tic_toc.hpp" #include "../../linalg/dtensor.hpp" // For Reshape #include "../../general/forall.hpp" using namespace std; namespace mfem { // Adapted from PADiffusionSetup3D void SetupPatch3D(const int Q1Dx, const int Q1Dy, const int Q1Dz, const int coeffDim, const bool symmetric, const Array &w, const Vector &j, const Vector &c, Vector &d) { const bool const_c = (c.Size() == 1); MFEM_VERIFY(coeffDim < 6 || !const_c, "Constant matrix coefficient not supported"); const auto W = Reshape(w.Read(), Q1Dx,Q1Dy,Q1Dz); const auto J = Reshape(j.Read(), Q1Dx,Q1Dy,Q1Dz,3,3); const auto C = const_c ? Reshape(c.Read(), 1,1,1,1) : Reshape(c.Read(), coeffDim,Q1Dx,Q1Dy,Q1Dz); d.SetSize(Q1Dx * Q1Dy * Q1Dz * (symmetric ? 6 : 9)); auto D = Reshape(d.Write(), Q1Dx,Q1Dy,Q1Dz, symmetric ? 6 : 9); const int NE = 1; // TODO: MFEM_FORALL_3D without e? MFEM_FORALL_3D(e, NE, Q1Dx, Q1Dy, Q1Dz, { MFEM_FOREACH_THREAD(qx,x,Q1Dx) { MFEM_FOREACH_THREAD(qy,y,Q1Dy) { MFEM_FOREACH_THREAD(qz,z,Q1Dz) { const double J11 = J(qx,qy,qz,0,0); const double J21 = J(qx,qy,qz,1,0); const double J31 = J(qx,qy,qz,2,0); const double J12 = J(qx,qy,qz,0,1); const double J22 = J(qx,qy,qz,1,1); const double J32 = J(qx,qy,qz,2,1); const double J13 = J(qx,qy,qz,0,2); const double J23 = J(qx,qy,qz,1,2); const double J33 = J(qx,qy,qz,2,2); const double detJ = J11 * (J22 * J33 - J32 * J23) - /* */ J21 * (J12 * J33 - J32 * J13) + /* */ J31 * (J12 * J23 - J22 * J13); const double w_detJ = W(qx,qy,qz) / detJ; // adj(J) const double A11 = (J22 * J33) - (J23 * J32); const double A12 = (J32 * J13) - (J12 * J33); const double A13 = (J12 * J23) - (J22 * J13); const double A21 = (J31 * J23) - (J21 * J33); const double A22 = (J11 * J33) - (J13 * J31); const double A23 = (J21 * J13) - (J11 * J23); const double A31 = (J21 * J32) - (J31 * J22); const double A32 = (J31 * J12) - (J11 * J32); const double A33 = (J11 * J22) - (J12 * J21); if (coeffDim == 6 || coeffDim == 9) // Matrix coefficient version { // Compute entries of R = MJ^{-T} = M adj(J)^T, without det J. const double M11 = C(0, qx,qy,qz); const double M12 = C(1, qx,qy,qz); const double M13 = C(2, qx,qy,qz); const double M21 = (!symmetric) ? C(3, qx,qy,qz) : M12; const double M22 = (!symmetric) ? C(4, qx,qy,qz) : C(3, qx,qy,qz); const double M23 = (!symmetric) ? C(5, qx,qy,qz) : C(4, qx,qy,qz); const double M31 = (!symmetric) ? C(6, qx,qy,qz) : M13; const double M32 = (!symmetric) ? C(7, qx,qy,qz) : M23; const double M33 = (!symmetric) ? C(8, qx,qy,qz) : C(5, qx,qy,qz); const double R11 = M11*A11 + M12*A12 + M13*A13; const double R12 = M11*A21 + M12*A22 + M13*A23; const double R13 = M11*A31 + M12*A32 + M13*A33; const double R21 = M21*A11 + M22*A12 + M23*A13; const double R22 = M21*A21 + M22*A22 + M23*A23; const double R23 = M21*A31 + M22*A32 + M23*A33; const double R31 = M31*A11 + M32*A12 + M33*A13; const double R32 = M31*A21 + M32*A22 + M33*A23; const double R33 = M31*A31 + M32*A32 + M33*A33; // Now set D to J^{-1} R = adj(J) R D(qx,qy,qz,0) = w_detJ * (A11*R11 + A12*R21 + A13*R31); // 1,1 const double D12 = w_detJ * (A11*R12 + A12*R22 + A13*R32); D(qx,qy,qz,1) = D12; // 1,2 D(qx,qy,qz,2) = w_detJ * (A11*R13 + A12*R23 + A13*R33); // 1,3 const double D21 = w_detJ * (A21*R11 + A22*R21 + A23*R31); const double D22 = w_detJ * (A21*R12 + A22*R22 + A23*R32); const double D23 = w_detJ * (A21*R13 + A22*R23 + A23*R33); const double D33 = w_detJ * (A31*R13 + A32*R23 + A33*R33); D(qx,qy,qz,3) = symmetric ? D22 : D21; // 2,2 or 2,1 D(qx,qy,qz,4) = symmetric ? D23 : D22; // 2,3 or 2,2 D(qx,qy,qz,5) = symmetric ? D33 : D23; // 3,3 or 2,3 if (!symmetric) { D(qx,qy,qz,6) = w_detJ * (A31*R11 + A32*R21 + A33*R31); // 3,1 D(qx,qy,qz,7) = w_detJ * (A31*R12 + A32*R22 + A33*R32); // 3,2 D(qx,qy,qz,8) = D33; // 3,3 } } else // Vector or scalar coefficient version { const double C1 = const_c ? C(0,0,0,0) : C(0,qx,qy,qz); const double C2 = const_c ? C(0,0,0,0) : (coeffDim == 3 ? C(1,qx,qy,qz) : C(0,qx,qy,qz)); const double C3 = const_c ? C(0,0,0,0) : (coeffDim == 3 ? C(2,qx,qy,qz) : C(0,qx,qy,qz)); // detJ J^{-1} J^{-T} = (1/detJ) adj(J) adj(J)^T D(qx,qy,qz,0) = w_detJ * (C1*A11*A11 + C2*A12*A12 + C3*A13*A13); // 1,1 D(qx,qy,qz,1) = w_detJ * (C1*A11*A21 + C2*A12*A22 + C3*A13*A23); // 2,1 D(qx,qy,qz,2) = w_detJ * (C1*A11*A31 + C2*A12*A32 + C3*A13*A33); // 3,1 D(qx,qy,qz,3) = w_detJ * (C1*A21*A21 + C2*A22*A22 + C3*A23*A23); // 2,2 D(qx,qy,qz,4) = w_detJ * (C1*A21*A31 + C2*A22*A32 + C3*A23*A33); // 3,2 D(qx,qy,qz,5) = w_detJ * (C1*A31*A31 + C2*A32*A32 + C3*A33*A33); // 3,3 } } } } }); } // Compute a reduced integration rule, using NNLSSolver, for DiffusionIntegrator // on a NURBS patch with partial assembly. void GetReducedRule(const int nq, const int nd, Array2D const& B, Array2D const& G, std::vector minQ, std::vector maxQ, std::vector minD, std::vector maxD, std::vector minDD, std::vector maxDD, const IntegrationRule *ir, const bool zeroOrder, std::vector & reducedWeights, std::vector> & reducedIDs) { MFEM_VERIFY(B.NumRows() == nq, ""); MFEM_VERIFY(B.NumCols() == nd, ""); MFEM_VERIFY(G.NumRows() == nq, ""); MFEM_VERIFY(G.NumCols() == nd, ""); MFEM_VERIFY(ir->GetNPoints() == nq, ""); for (int dof=0; dofIntPoint(qx); const double w_qx = ip.weight; w[qx - minD[dof]] = w_qx; for (int dx = minQ[qx]; dx <= maxQ[qx]; ++dx) { const double Bd = zeroOrder ? B(qx,dx) : G(qx,dx); Gmat(dx - minDD[dof], qx - minD[dof]) = Bq * Bd; } } Vector sol(Gmat.NumCols()); #ifdef MFEM_USE_LAPACK NNLSSolver nnls; nnls.SetOperator(Gmat); nnls.Mult(w, sol); #else MFEM_ABORT("NNLSSolver requires building with LAPACK"); #endif int nnz = 0; for (int i=0; i 0, ""); Vector wred(nnz); std::vector idnnz(nnz); nnz = 0; for (int i=0; i& Q1D = pQ1D[patch]; const Array& D1D = pD1D[patch]; const std::vector>& B = pB[patch]; const std::vector>& G = pG[patch]; const IntArrayVar2D& minD = pminD[patch]; const IntArrayVar2D& maxD = pmaxD[patch]; const IntArrayVar2D& minQ = pminQ[patch]; const IntArrayVar2D& maxQ = pmaxQ[patch]; const IntArrayVar2D& minDD = pminDD[patch]; const IntArrayVar2D& maxDD = pmaxDD[patch]; const Array& ir1d = pir1d[patch]; MFEM_VERIFY(Q1D.Size() == 3, ""); const int dims = dim; // TODO: generalize const int symmDims = (dims * (dims + 1)) / 2; // 1x1: 1, 2x2: 3, 3x3: 6 int nq = Q1D[0]; for (int i=1; i weights(nq); const int MQfullDim = MQ ? MQ->GetHeight() * MQ->GetWidth() : 0; IntegrationPoint ip; Vector jac(dim * dim * nq); // Computed as in GeometricFactors::Compute for (int qz=0; qzGetIntegrationPointFrom1D(patch, qx, qy, qz, ip); const int e = patchRules->GetPointElement(patch, qx, qy, qz); ElementTransformation *tr = mesh->GetElementTransformation(e); weights[p] = ip.weight; tr->SetIntPoint(&ip); const DenseMatrix& Jp = tr->Jacobian(); for (int i=0; i(MQ)) { MFEM_VERIFY(SMQ->GetSize() == dim, ""); coeffDim = symmDims; coeff.SetSize(symmDims * nq); DenseSymmetricMatrix sym_mat; sym_mat.SetSize(dim); auto C = Reshape(coeff.HostWrite(), symmDims, nq); for (int qz=0; qzGetPointElement(patch, qx, qy, qz); ElementTransformation *tr = mesh->GetElementTransformation(e); patchRules->GetIntegrationPointFrom1D(patch, qx, qy, qz, ip); SMQ->Eval(sym_mat, *tr, ip); int cnt = 0; for (int i=0; iGetHeight() == dim && MQ->GetWidth() == dim, ""); coeffDim = MQfullDim; coeff.SetSize(MQfullDim * nq); DenseMatrix mat; mat.SetSize(dim); auto C = Reshape(coeff.HostWrite(), MQfullDim, nq); for (int qz=0; qzGetPointElement(patch, qx, qy, qz); ElementTransformation *tr = mesh->GetElementTransformation(e); patchRules->GetIntegrationPointFrom1D(patch, qx, qy, qz, ip); MQ->Eval(mat, *tr, ip); for (int i=0; iGetVDim() == dim, ""); coeffDim = VQ->GetVDim(); coeff.SetSize(coeffDim * nq); auto C = Reshape(coeff.HostWrite(), coeffDim, nq); Vector DM(coeffDim); for (int qz=0; qzGetPointElement(patch, qx, qy, qz); ElementTransformation *tr = mesh->GetElementTransformation(e); patchRules->GetIntegrationPointFrom1D(patch, qx, qy, qz, ip); VQ->Eval(DM, *tr, ip); for (int i=0; i(Q)) { coeff.SetSize(1); coeff(0) = cQ->constant; } else if (dynamic_cast(Q)) { MFEM_ABORT("QuadratureFunction not supported yet\n"); } else { coeff.SetSize(nq); auto C = Reshape(coeff.HostWrite(), nq); for (int qz=0; qzGetPointElement(patch, qx, qy, qz); ElementTransformation *tr = mesh->GetElementTransformation(e); patchRules->GetIntegrationPointFrom1D(patch, qx, qy, qz, ip); C(p) = Q->Eval(*tr, ip); } } } } if (unitWeights) { weights = 1.0; } SetupPatch3D(Q1D[0], Q1D[1], Q1D[2], coeffDim, symmetric, weights, jac, coeff, pa_data); numPatches = mesh->NURBSext->GetNP(); if (integrationMode != PATCHWISE_REDUCED) { return; } // Solve for reduced 1D quadrature rules const int totalDim = numPatches * dim * numTypes; reducedWeights.resize(totalDim); reducedIDs.resize(totalDim); auto rw = Reshape(reducedWeights.data(), numTypes, dim, numPatches); auto rid = Reshape(reducedIDs.data(), numTypes, dim, numPatches); for (int d=0; dGetDim(); const int spaceDim = dim; // TODO: generalize? Mesh *mesh = fes.GetMesh(); if (VQ) { MFEM_VERIFY(VQ->GetVDim() == spaceDim, "Unexpected dimension for VectorCoefficient"); } if (MQ) { MFEM_VERIFY(MQ->GetWidth() == spaceDim, "Unexpected width for MatrixCoefficient"); MFEM_VERIFY(MQ->GetHeight() == spaceDim, "Unexpected height for MatrixCoefficient"); } #ifdef MFEM_THREAD_SAFE DenseMatrix M(MQ ? spaceDim : 0); Vector D(VQ ? VQ->GetVDim() : 0); #else M.SetSize(MQ ? spaceDim : 0); D.SetSize(VQ ? VQ->GetVDim() : 0); #endif SetupPatchBasisData(mesh, patch); SetupPatchPA(patch, mesh); const Array& Q1D = pQ1D[patch]; const Array& D1D = pD1D[patch]; const std::vector>& B = pB[patch]; const std::vector>& G = pG[patch]; const IntArrayVar2D& minD = pminD[patch]; const IntArrayVar2D& maxD = pmaxD[patch]; const IntArrayVar2D& minQ = pminQ[patch]; const IntArrayVar2D& maxQ = pmaxQ[patch]; const IntArrayVar2D& minDD = pminDD[patch]; const IntArrayVar2D& maxDD = pmaxDD[patch]; int ndof = D1D[0]; for (int d=1; d> grad(dim); for (int d=0; d gradDXY(D1D[0], D1D[1], dim); Array2D gradDX(D1D[0], dim); int nd[3]; Array3D cdofs; int *smati = nullptr; int *smatj = nullptr; double *smata = nullptr; int nnz = 0; Array maxw(dim); maxw = 0; for (int d=0; d pkv; mesh->NURBSext->GetPatchKnotVectors(patch, pkv); MFEM_VERIFY(pkv.Size() == dim, ""); Array Q1D(dim); Array orders(dim); Array D1D(dim); std::vector> B(dim); std::vector> G(dim); Array ir1d(dim); IntArrayVar2D minD(dim); IntArrayVar2D maxD(dim); IntArrayVar2D minQ(dim); IntArrayVar2D maxQ(dim); IntArrayVar2D minDD(dim); IntArrayVar2D maxDD(dim); for (int d=0; dGetPatchRule1D(patch, d); Q1D[d] = ir1d[d]->GetNPoints(); orders[d] = pkv[d]->GetOrder(); D1D[d] = pkv[d]->GetNCP(); Vector shapeKV(orders[d]+1); Vector dshapeKV(orders[d]+1); B[d].SetSize(Q1D[d], D1D[d]); G[d].SetSize(Q1D[d], D1D[d]); minD[d].assign(D1D[d], Q1D[d]); maxD[d].assign(D1D[d], 0); minQ[d].assign(Q1D[d], D1D[d]); maxQ[d].assign(Q1D[d], 0); B[d] = 0.0; G[d] = 0.0; const Array& knotSpan1D = patchRules->GetPatchRule1D_KnotSpan(patch, d); MFEM_VERIFY(knotSpan1D.Size() == Q1D[d], ""); for (int i = 0; i < Q1D[d]; i++) { const IntegrationPoint &ip = ir1d[d]->IntPoint(i); const int ijk = knotSpan1D[i]; const double kv0 = (*pkv[d])[orders[d] + ijk]; double kv1 = (*pkv[d])[0]; for (int j = orders[d] + ijk + 1; j < pkv[d]->Size(); ++j) { if ((*pkv[d])[j] > kv0) { kv1 = (*pkv[d])[j]; break; } } MFEM_VERIFY(kv1 > kv0, ""); pkv[d]->CalcShape(shapeKV, ijk, (ip.x - kv0) / (kv1 - kv0)); pkv[d]->CalcDShape(dshapeKV, ijk, (ip.x - kv0) / (kv1 - kv0)); // Put shapeKV into array B storing shapes for all points. // TODO: This should be based on NURBS3DFiniteElement::CalcShape and CalcDShape. // For now, it works under the assumption that all NURBS weights are 1. for (int j=0; jGetDim(); const int spaceDim = dim; // TODO: generalize? Mesh *mesh = fes.GetMesh(); if (VQ) { MFEM_VERIFY(VQ->GetVDim() == spaceDim, "Unexpected dimension for VectorCoefficient"); } if (MQ) { MFEM_VERIFY(MQ->GetWidth() == spaceDim, "Unexpected width for MatrixCoefficient"); MFEM_VERIFY(MQ->GetHeight() == spaceDim, "Unexpected height for MatrixCoefficient"); } #ifdef MFEM_THREAD_SAFE DenseMatrix M(MQ ? spaceDim : 0); Vector D(VQ ? VQ->GetVDim() : 0); #else M.SetSize(MQ ? spaceDim : 0); D.SetSize(VQ ? VQ->GetVDim() : 0); #endif SetupPatchBasisData(mesh, patch); MFEM_VERIFY(3 == dim, "Only 3D so far"); // Setup quadrature point data. // For each point in patchRules, get the corresponding element and element // reference point, in order to use element transformations. This requires // data set up in NURBSPatchRule::SetPointToElement. SetupPatchPA(patch, mesh, true); const Array& Q1D = pQ1D[patch]; const Array& D1D = pD1D[patch]; const std::vector>& B = pB[patch]; const std::vector>& G = pG[patch]; const IntArrayVar2D& minD = pminD[patch]; const IntArrayVar2D& maxD = pmaxD[patch]; const IntArrayVar2D& minQ = pminQ[patch]; const IntArrayVar2D& maxQ = pmaxQ[patch]; const IntArrayVar2D& minDD = pminDD[patch]; const IntArrayVar2D& maxDD = pmaxDD[patch]; int ndof = D1D[0]; for (int d=1; d> grad(dim); for (int d=0; d to store 1D indices of the used points. Array3D gradUsed; gradUsed.SetSize(Q1D[0], Q1D[1], Q1D[2]); Array3D gradDXY(D1D[0], D1D[1], dim); Array2D gradDX(D1D[0], dim); int nd[3]; Array3D cdofs; int *smati = nullptr; int *smatj = nullptr; double *smata = nullptr; bool bugfound = false; int nnz = 0; Array maxw(dim); maxw = 0; for (int d=0; d