// 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 "constraints.hpp" #include "../fem/fespace.hpp" #include "../fem/pfespace.hpp" #include namespace mfem { Eliminator::Eliminator(const SparseMatrix& B, const Array& lagrange_tdofs_, const Array& primary_tdofs_, const Array& secondary_tdofs_) : lagrange_tdofs(lagrange_tdofs_), primary_tdofs(primary_tdofs_), secondary_tdofs(secondary_tdofs_) { MFEM_VERIFY(lagrange_tdofs.Size() == secondary_tdofs.Size(), "Dof sizes don't match!"); Bp.SetSize(lagrange_tdofs.Size(), primary_tdofs.Size()); B.GetSubMatrix(lagrange_tdofs, primary_tdofs, Bp); Bs.SetSize(lagrange_tdofs.Size(), secondary_tdofs.Size()); B.GetSubMatrix(lagrange_tdofs, secondary_tdofs, Bs); BsT.Transpose(Bs); ipiv.SetSize(Bs.Height()); Bsinverse.data = Bs.HostReadWrite(); Bsinverse.ipiv = ipiv.HostReadWrite(); Bsinverse.Factor(Bs.Height()); ipivT.SetSize(Bs.Height()); BsTinverse.data = BsT.HostReadWrite(); BsTinverse.ipiv = ipivT.HostReadWrite(); BsTinverse.Factor(Bs.Height()); } void Eliminator::Eliminate(const Vector& vin, Vector& vout) const { Bp.Mult(vin, vout); Bsinverse.Solve(Bs.Height(), 1, vout.GetData()); vout *= -1.0; } void Eliminator::EliminateTranspose(const Vector& vin, Vector& vout) const { Vector work(vin); BsTinverse.Solve(Bs.Height(), 1, work.GetData()); Bp.MultTranspose(work, vout); vout *= -1.0; } void Eliminator::LagrangeSecondary(const Vector& vin, Vector& vout) const { vout = vin; Bsinverse.Solve(Bs.Height(), 1, vout.GetData()); } void Eliminator::LagrangeSecondaryTranspose(const Vector& vin, Vector& vout) const { vout = vin; BsTinverse.Solve(Bs.Height(), 1, vout.GetData()); } void Eliminator::ExplicitAssembly(DenseMatrix& mat) const { mat.SetSize(Bp.Height(), Bp.Width()); mat = Bp; Bsinverse.Solve(Bs.Height(), Bp.Width(), mat.GetData()); mat *= -1.0; } EliminationProjection::EliminationProjection(const Operator& A, Array& eliminators_) : Operator(A.Height()), Aop(A), eliminators(eliminators_) { } void EliminationProjection::Mult(const Vector& vin, Vector& vout) const { MFEM_ASSERT(vin.Size() == width, "Wrong vector size!"); MFEM_ASSERT(vout.Size() == height, "Wrong vector size!"); vout = vin; for (int k = 0; k < eliminators.Size(); ++k) { Eliminator* elim = eliminators[k]; Vector subvec_in; Vector subvec_out(elim->SecondaryDofs().Size()); vin.GetSubVector(elim->PrimaryDofs(), subvec_in); elim->Eliminate(subvec_in, subvec_out); vout.SetSubVector(elim->SecondaryDofs(), subvec_out); } } void EliminationProjection::MultTranspose(const Vector& vin, Vector& vout) const { MFEM_ASSERT(vin.Size() == height, "Wrong vector size!"); MFEM_ASSERT(vout.Size() == width, "Wrong vector size!"); vout = vin; for (int k = 0; k < eliminators.Size(); ++k) { Eliminator* elim = eliminators[k]; Vector subvec_in; Vector subvec_out(elim->PrimaryDofs().Size()); vin.GetSubVector(elim->SecondaryDofs(), subvec_in); elim->EliminateTranspose(subvec_in, subvec_out); vout.AddElementVector(elim->PrimaryDofs(), subvec_out); vout.SetSubVector(elim->SecondaryDofs(), 0.0); } } SparseMatrix * EliminationProjection::AssembleExact() const { SparseMatrix * mat = new SparseMatrix(height, width); for (int i = 0; i < height; ++i) { mat->Add(i, i, 1.0); } for (int k = 0; k < eliminators.Size(); ++k) { Eliminator* elim = eliminators[k]; DenseMatrix mat_k; elim->ExplicitAssembly(mat_k); for (int iz = 0; iz < elim->SecondaryDofs().Size(); ++iz) { int i = elim->SecondaryDofs()[iz]; for (int jz = 0; jz < elim->PrimaryDofs().Size(); ++jz) { int j = elim->PrimaryDofs()[jz]; mat->Add(i, j, mat_k(iz, jz)); } mat->Set(i, i, 0.0); } } mat->Finalize(); return mat; } void EliminationProjection::BuildGTilde(const Vector& r, Vector& rtilde) const { MFEM_ASSERT(rtilde.Size() == Aop.Height(), "Sizes don't match!"); rtilde = 0.0; for (int k = 0; k < eliminators.Size(); ++k) { Eliminator* elim = eliminators[k]; Vector subr; r.GetSubVector(elim->LagrangeDofs(), subr); Vector bsinvr(subr.Size()); elim->LagrangeSecondary(subr, bsinvr); rtilde.AddElementVector(elim->SecondaryDofs(), bsinvr); } } void EliminationProjection::RecoverMultiplier( const Vector& disprhs, const Vector& disp, Vector& lagrangem) const { lagrangem = 0.0; MFEM_ASSERT(disp.Size() == Aop.Height(), "Sizes don't match!"); Vector fullrhs(Aop.Height()); Aop.Mult(disp, fullrhs); fullrhs -= disprhs; fullrhs *= -1.0; for (int k = 0; k < eliminators.Size(); ++k) { Eliminator* elim = eliminators[k]; Vector localsec; fullrhs.GetSubVector(elim->SecondaryDofs(), localsec); Vector locallagrange(localsec.Size()); elim->LagrangeSecondaryTranspose(localsec, locallagrange); lagrangem.AddElementVector(elim->LagrangeDofs(), locallagrange); } } #ifdef MFEM_USE_MPI EliminationSolver::~EliminationSolver() { delete h_explicit_operator; for (auto elim : eliminators) { delete elim; } delete projector; delete prec; delete krylov; } void EliminationSolver::BuildExplicitOperator() { SparseMatrix * explicit_projector = projector->AssembleExact(); HypreParMatrix * h_explicit_projector = new HypreParMatrix(hA.GetComm(), hA.GetGlobalNumRows(), hA.GetRowStarts(), explicit_projector); h_explicit_projector->CopyRowStarts(); h_explicit_projector->CopyColStarts(); h_explicit_operator = RAP(&hA, h_explicit_projector); // next line because of square projector h_explicit_operator->EliminateZeroRows(); h_explicit_operator->CopyRowStarts(); h_explicit_operator->CopyColStarts(); delete explicit_projector; delete h_explicit_projector; } EliminationSolver::EliminationSolver(HypreParMatrix& A, SparseMatrix& B, Array& primary_dofs, Array& secondary_dofs) : ConstrainedSolver(A.GetComm(), A, B), hA(A), krylov(nullptr), prec(nullptr) { MFEM_VERIFY(secondary_dofs.Size() == B.Height(), "Wrong number of dofs for elimination!"); Array lagrange_dofs(secondary_dofs.Size()); for (int i = 0; i < lagrange_dofs.Size(); ++i) { lagrange_dofs[i] = i; } eliminators.Append(new Eliminator(B, lagrange_dofs, primary_dofs, secondary_dofs)); projector = new EliminationProjection(hA, eliminators); BuildExplicitOperator(); } EliminationSolver::EliminationSolver(HypreParMatrix& A, SparseMatrix& B, Array& constraint_rowstarts) : ConstrainedSolver(A.GetComm(), A, B), hA(A), krylov(nullptr), prec(nullptr) { if (!B.Empty()) { int * I = B.GetI(); int * J = B.GetJ(); double * data = B.GetData(); for (int k = 0; k < constraint_rowstarts.Size() - 1; ++k) { int constraint_size = constraint_rowstarts[k + 1] - constraint_rowstarts[k]; Array lagrange_dofs(constraint_size); Array primary_dofs; Array secondary_dofs(constraint_size); secondary_dofs = -1; // loop through rows, identify one secondary dof for each row for (int i = constraint_rowstarts[k]; i < constraint_rowstarts[k + 1]; ++i) { lagrange_dofs[i - constraint_rowstarts[k]] = i; for (int jptr = I[i]; jptr < I[i + 1]; ++jptr) { int j = J[jptr]; double val = data[jptr]; if (std::abs(val) > 1.e-12 && secondary_dofs.Find(j) == -1) { secondary_dofs[i - constraint_rowstarts[k]] = j; break; } } } // loop through rows again, assigning non-secondary dofs as primary for (int i = constraint_rowstarts[k]; i < constraint_rowstarts[k + 1]; ++i) { MFEM_ASSERT(secondary_dofs[i - constraint_rowstarts[k]] >= 0, "Secondary dofs don't match rows!"); for (int jptr = I[i]; jptr < I[i + 1]; ++jptr) { int j = J[jptr]; if (secondary_dofs.Find(j) == -1) { primary_dofs.Append(j); } } } primary_dofs.Sort(); primary_dofs.Unique(); eliminators.Append(new Eliminator(B, lagrange_dofs, primary_dofs, secondary_dofs)); } } projector = new EliminationProjection(hA, eliminators); BuildExplicitOperator(); } void EliminationSolver::Mult(const Vector& rhs, Vector& sol) const { if (!prec) { prec = BuildPreconditioner(); } else { prec->SetOperator(*h_explicit_operator); } if (!krylov) { krylov = BuildKrylov(); krylov->SetOperator(*h_explicit_operator); krylov->SetPreconditioner(*prec); } krylov->SetMaxIter(max_iter); krylov->SetRelTol(rel_tol); krylov->SetAbsTol(abs_tol); krylov->SetPrintLevel(print_options); Vector rtilde(rhs.Size()); if (constraint_rhs.Size() > 0) { projector->BuildGTilde(constraint_rhs, rtilde); } else { rtilde = 0.0; } Vector temprhs(rhs); hA.Mult(-1.0, rtilde, 1.0, temprhs); Vector reducedrhs(rhs.Size()); projector->MultTranspose(temprhs, reducedrhs); Vector reducedsol(rhs.Size()); reducedsol = 0.0; krylov->Mult(reducedrhs, reducedsol); final_iter = krylov->GetNumIterations(); initial_norm = krylov->GetInitialNorm(); final_norm = krylov->GetFinalNorm(); converged = krylov->GetConverged(); projector->Mult(reducedsol, sol); projector->RecoverMultiplier(temprhs, sol, multiplier_sol); sol += rtilde; } void PenaltyConstrainedSolver::Initialize(HypreParMatrix& A, HypreParMatrix& B, HypreParMatrix& D) { HypreParMatrix * hBTB = RAP(&D, &B); // this matrix doesn't get cleanly deleted? // (hypre comm pkg) penalized_mat = ParAdd(&A, hBTB); delete hBTB; } PenaltyConstrainedSolver::PenaltyConstrainedSolver( HypreParMatrix& A, SparseMatrix& B, double penalty_) : ConstrainedSolver(A.GetComm(), A, B), penalty(B.Height()), constraintB(B), krylov(nullptr), prec(nullptr) { int rank, size; MPI_Comm_rank(A.GetComm(), &rank); MPI_Comm_size(A.GetComm(), &size); int constraint_running_total = 0; int local_constraints = B.Height(); MPI_Scan(&local_constraints, &constraint_running_total, 1, MPI_INT, MPI_SUM, A.GetComm()); int global_constraints = 0; if (rank == size - 1) { global_constraints = constraint_running_total; } MPI_Bcast(&global_constraints, 1, MPI_INT, size - 1, A.GetComm()); HYPRE_BigInt glob_num_rows = global_constraints; HYPRE_BigInt glob_num_cols = A.N(); HYPRE_BigInt row_starts[2] = { constraint_running_total - local_constraints, constraint_running_total }; HYPRE_BigInt col_starts[2] = { A.ColPart()[0], A.ColPart()[1] }; HypreParMatrix hB(A.GetComm(), glob_num_rows, glob_num_cols, row_starts, col_starts, &B); hB.CopyRowStarts(); hB.CopyColStarts(); penalty=penalty_; SparseMatrix D(penalty); HypreParMatrix hD(hB.GetComm(), hB.M(), hB.RowPart(), &D); hD.CopyRowStarts(); hD.CopyColStarts(); Initialize(A, hB, hD); } PenaltyConstrainedSolver::PenaltyConstrainedSolver( HypreParMatrix& A, HypreParMatrix& B, double penalty_) : ConstrainedSolver(A.GetComm(), A, B), penalty(B.Height()), constraintB(B), krylov(nullptr), prec(nullptr) { penalty=penalty_; SparseMatrix D(penalty); HypreParMatrix hD(B.GetComm(), B.M(), B.RowPart(), &D); hD.CopyRowStarts(); hD.CopyColStarts(); Initialize(A, B, hD); } PenaltyConstrainedSolver::PenaltyConstrainedSolver( HypreParMatrix& A, HypreParMatrix& B, Vector& penalty_) : ConstrainedSolver(A.GetComm(), A, B), penalty(penalty_), constraintB(B), krylov(nullptr), prec(nullptr) { SparseMatrix D(penalty_); HypreParMatrix hD(B.GetComm(), B.M(), B.RowPart(), &D); hD.CopyRowStarts(); hD.CopyColStarts(); Initialize(A, B, hD); } PenaltyConstrainedSolver::~PenaltyConstrainedSolver() { delete penalized_mat; delete prec; delete krylov; } void PenaltyConstrainedSolver::Mult(const Vector& b, Vector& x) const { if (!prec) { prec = BuildPreconditioner(); } else { prec->SetOperator(*penalized_mat); } if (!krylov) { krylov = BuildKrylov(); krylov->SetOperator(*penalized_mat); krylov->SetPreconditioner(*prec); } // form penalized right-hand side Vector penalized_rhs(b); if (constraint_rhs.Size() > 0) { Vector temp_rhs(constraint_rhs.Size()); SparseMatrix D(penalty); D.Mult(constraint_rhs, temp_rhs); Vector temp(x.Size()); constraintB.MultTranspose(temp_rhs, temp); penalized_rhs += temp; } // actually solve krylov->SetRelTol(rel_tol); krylov->SetAbsTol(abs_tol); krylov->SetMaxIter(max_iter); krylov->SetPrintLevel(print_options); krylov->Mult(penalized_rhs, x); final_iter = krylov->GetNumIterations(); initial_norm = krylov->GetInitialNorm(); final_norm = krylov->GetFinalNorm(); converged = krylov->GetConverged(); constraintB.Mult(x, multiplier_sol); if (constraint_rhs.Size() > 0) { multiplier_sol -= constraint_rhs; } multiplier_sol *= penalty; } #endif /// because IdentityOperator isn't a Solver class IdentitySolver : public Solver { public: IdentitySolver(int size) : Solver(size) { } void Mult(const Vector& x, Vector& y) const { y = x; } void SetOperator(const Operator& op) { } }; void SchurConstrainedSolver::Initialize() { offsets[0] = 0; offsets[1] = A.Height(); offsets[2] = A.Height() + B.Height(); block_op = new BlockOperator(offsets); block_op->SetBlock(0, 0, &A); block_op->SetBlock(1, 0, &B); tr_B = new TransposeOperator(&B); block_op->SetBlock(0, 1, tr_B); block_pc = new BlockDiagonalPreconditioner(block_op->RowOffsets()), rel_tol = 1.e-6; } #ifdef MFEM_USE_MPI SchurConstrainedSolver::SchurConstrainedSolver(MPI_Comm comm, Operator& A_, Operator& B_, Solver& primal_pc_) : ConstrainedSolver(comm, A_, B_), offsets(3), primal_pc(&primal_pc_), dual_pc(nullptr) { Initialize(); primal_pc->SetOperator(block_op->GetBlock(0, 0)); dual_pc = new IdentitySolver(block_op->RowOffsets()[2] - block_op->RowOffsets()[1]); block_pc->SetDiagonalBlock(0, primal_pc); block_pc->SetDiagonalBlock(1, dual_pc); } #endif SchurConstrainedSolver::SchurConstrainedSolver(Operator& A_, Operator& B_, Solver& primal_pc_) : ConstrainedSolver(A_, B_), offsets(3), primal_pc(&primal_pc_), dual_pc(nullptr) { Initialize(); primal_pc->SetOperator(block_op->GetBlock(0, 0)); dual_pc = new IdentitySolver(block_op->RowOffsets()[2] - block_op->RowOffsets()[1]); block_pc->SetDiagonalBlock(0, primal_pc); block_pc->SetDiagonalBlock(1, dual_pc); } #ifdef MFEM_USE_MPI // protected constructor SchurConstrainedSolver::SchurConstrainedSolver(MPI_Comm comm, Operator& A_, Operator& B_) : ConstrainedSolver(comm, A_, B_), offsets(3), primal_pc(nullptr), dual_pc(nullptr) { Initialize(); } #endif // protected constructor SchurConstrainedSolver::SchurConstrainedSolver(Operator& A_, Operator& B_) : ConstrainedSolver(A_, B_), offsets(3), primal_pc(nullptr), dual_pc(nullptr) { Initialize(); } SchurConstrainedSolver::~SchurConstrainedSolver() { delete block_op; delete tr_B; delete block_pc; delete dual_pc; } void SchurConstrainedSolver::LagrangeSystemMult(const Vector& x, Vector& y) const { GMRESSolver * gmres; #ifdef MFEM_USE_MPI if (GetComm() != MPI_COMM_NULL) { gmres = new GMRESSolver(GetComm()); } else #endif { gmres = new GMRESSolver; } gmres->SetOperator(*block_op); gmres->SetRelTol(rel_tol); gmres->SetAbsTol(abs_tol); gmres->SetMaxIter(max_iter); gmres->SetPrintLevel(print_options); gmres->SetPreconditioner( const_cast(*block_pc)); gmres->Mult(x, y); final_iter = gmres->GetNumIterations(); converged = gmres->GetConverged(); initial_norm = gmres->GetInitialNorm(); final_norm = gmres->GetFinalNorm(); delete gmres; } #ifdef MFEM_USE_MPI SchurConstrainedHypreSolver::SchurConstrainedHypreSolver(MPI_Comm comm, HypreParMatrix& hA_, HypreParMatrix& hB_, Solver * prec, int dimension, bool reorder) : SchurConstrainedSolver(comm, hA_, hB_), hA(hA_), hB(hB_) { if (prec == nullptr) { auto h_primal_pc = new HypreBoomerAMG(hA); h_primal_pc->SetPrintLevel(0); if (dimension > 0) { h_primal_pc->SetSystemsOptions(dimension, reorder); } primal_pc = h_primal_pc; } else { primal_pc = prec; } HypreParMatrix * scaledB = new HypreParMatrix(hB); Vector diagA; hA.GetDiag(diagA); HypreParMatrix * scaledBT = scaledB->Transpose(); scaledBT->InvScaleRows(diagA); schur_mat = ParMult(scaledB, scaledBT); schur_mat->CopyRowStarts(); schur_mat->CopyColStarts(); auto h_dual_pc = new HypreBoomerAMG(*schur_mat); h_dual_pc->SetPrintLevel(0); dual_pc = h_dual_pc; delete scaledB; delete scaledBT; block_pc->SetDiagonalBlock(0, primal_pc); block_pc->SetDiagonalBlock(1, dual_pc); } SchurConstrainedHypreSolver::~SchurConstrainedHypreSolver() { delete schur_mat; delete primal_pc; } #endif void ConstrainedSolver::Initialize() { height = A.Height() + B.Height(); width = A.Width() + B.Height(); workb.SetSize(A.Height()); workx.SetSize(A.Height()); constraint_rhs.SetSize(B.Height()); constraint_rhs = 0.0; multiplier_sol.SetSize(B.Height()); } #ifdef MFEM_USE_MPI ConstrainedSolver::ConstrainedSolver(MPI_Comm comm, Operator& A_, Operator& B_) : IterativeSolver(comm), A(A_), B(B_) { Initialize(); } #endif ConstrainedSolver::ConstrainedSolver(Operator& A_, Operator& B_) : A(A_), B(B_) { Initialize(); } void ConstrainedSolver::SetConstraintRHS(const Vector& r) { MFEM_VERIFY(r.Size() == multiplier_sol.Size(), "Vector is wrong size!"); constraint_rhs = r; } void ConstrainedSolver::Mult(const Vector& f, Vector &x) const { Vector pworkb(A.Height() + B.Height()); Vector pworkx(A.Height() + B.Height()); pworkb = 0.0; pworkx = 0.0; for (int i = 0; i < f.Size(); ++i) { pworkb(i) = f(i); pworkx(i) = x(i); } for (int i = 0; i < B.Height(); ++i) { pworkb(f.Size() + i) = constraint_rhs(i); } LagrangeSystemMult(pworkb, pworkx); for (int i = 0; i < f.Size(); ++i) { x(i) = pworkx(i); } for (int i = 0; i < B.Height(); ++i) { multiplier_sol(i) = pworkx(f.Size() + i); } } void ConstrainedSolver::LagrangeSystemMult(const Vector& f_and_r, Vector& x_and_lambda) const { workb.MakeRef(const_cast(f_and_r), 0); workx.MakeRef(x_and_lambda, 0); Vector ref_constraint_rhs(f_and_r.GetData() + A.Height(), B.Height()); constraint_rhs = ref_constraint_rhs; Mult(workb, workx); Vector ref_constraint_sol(x_and_lambda.GetData() + A.Height(), B.Height()); GetMultiplierSolution(ref_constraint_sol); } /* Helper routine to reduce code duplication - given a node (which MFEM sometimes calls a "dof"), this returns what normal people call a dof but which MFEM sometimes calls a "vdof" - note that MFEM's naming conventions regarding this are not entirely consistent. In parallel, this always returns the "truedof" in parallel numbering. */ int CanonicalNodeNumber(FiniteElementSpace& fespace, int node, bool parallel, int d=0) { #ifdef MFEM_USE_MPI if (parallel) { ParFiniteElementSpace* pfespace = dynamic_cast(&fespace); if (pfespace) { const int vdof = pfespace->DofToVDof(node, d); return pfespace->GetLocalTDofNumber(vdof); } else { MFEM_ABORT("Asked for parallel form of serial object!"); return -1; } } else #endif { return fespace.DofToVDof(node, d); } } SparseMatrix * BuildNormalConstraints(FiniteElementSpace& fespace, Array& constrained_att, Array& constraint_rowstarts, bool parallel) { int dim = fespace.GetVDim(); // dof_constraint maps a dof (column of the constraint matrix) to // a block-constraint // the indexing is by tdof, but a single tdof uniquely identifies a node // so we only store one tdof independent of dimension std::map dof_bconstraint; // constraints[j] is a map from attribute to row number, // the j itself is the index of a block-constraint std::vector > constraints; int n_bconstraints = 0; int n_rows = 0; for (int att : constrained_att) { // identify tdofs on constrained boundary std::set constrained_tdofs; for (int i = 0; i < fespace.GetNBE(); ++i) { if (fespace.GetBdrAttribute(i) == att) { Array nodes; // get nodes on boundary (MFEM sometimes calls these dofs, what // we call dofs it calls vdofs) fespace.GetBdrElementDofs(i, nodes); for (auto k : nodes) { // get the (local) dof number corresponding to // the x-coordinate dof for node k int tdof = CanonicalNodeNumber(fespace, k, parallel); if (tdof >= 0) { constrained_tdofs.insert(tdof); } } } } // fill in the maps identifying which constraints (rows) correspond to // which tdofs for (auto k : constrained_tdofs) { auto it = dof_bconstraint.find(k); if (it == dof_bconstraint.end()) { // build new block constraint dof_bconstraint[k] = n_bconstraints++; constraints.emplace_back(); constraints.back()[att] = n_rows++; } else { // add tdof to existing block constraint constraints[it->second][att] = n_rows++; } } } // reorder so block-constraints eliminated together are grouped together in // adjacent rows { std::map reorder_rows; int new_row = 0; constraint_rowstarts.DeleteAll(); constraint_rowstarts.Append(0); for (auto& it : dof_bconstraint) { int bconstraint_index = it.second; bool nconstraint = false; for (auto& att_it : constraints[bconstraint_index]) { auto rrit = reorder_rows.find(att_it.second); if (rrit == reorder_rows.end()) { nconstraint = true; reorder_rows[att_it.second] = new_row++; } } if (nconstraint) { constraint_rowstarts.Append(new_row); } } MFEM_VERIFY(new_row == n_rows, "Remapping failed!"); for (auto& constraint_map : constraints) { for (auto& it : constraint_map) { it.second = reorder_rows[it.second]; } } } SparseMatrix * mout = new SparseMatrix(n_rows, fespace.GetTrueVSize()); // fill in constraint matrix with normal vector information Vector nor(dim); // how many times we have seen a node (key is truek) std::map node_visits; for (int i = 0; i < fespace.GetNBE(); ++i) { int att = fespace.GetBdrAttribute(i); if (constrained_att.FindSorted(att) != -1) { ElementTransformation * Tr = fespace.GetBdrElementTransformation(i); const FiniteElement * fe = fespace.GetBE(i); const IntegrationRule& nodes = fe->GetNodes(); Array dofs; fespace.GetBdrElementDofs(i, dofs); MFEM_VERIFY(dofs.Size() == nodes.Size(), "Something wrong in finite element space!"); for (int j = 0; j < dofs.Size(); ++j) { Tr->SetIntPoint(&nodes[j]); // the normal returned in the next line is scaled by h, which is // probably what we want in most applications CalcOrtho(Tr->Jacobian(), nor); int k = dofs[j]; int truek = CanonicalNodeNumber(fespace, k, parallel); if (truek >= 0) { auto nv_it = node_visits.find(truek); if (nv_it == node_visits.end()) { node_visits[truek] = 1; } else { node_visits[truek]++; } int visits = node_visits[truek]; int bconstraint = dof_bconstraint[truek]; int row = constraints[bconstraint][att]; for (int d = 0; d < dim; ++d) { int inner_truek = CanonicalNodeNumber(fespace, k, parallel, d); if (visits == 1) { mout->Add(row, inner_truek, nor[d]); } else { mout->SetColPtr(row); const double pv = mout->SearchRow(inner_truek); const double scaling = ((double) (visits - 1)) / ((double) visits); // incremental average, based on how many times // this node has been visited mout->Set(row, inner_truek, scaling * pv + (1.0 / visits) * nor[d]); } } } } } } mout->Finalize(); return mout; } #ifdef MFEM_USE_MPI SparseMatrix * ParBuildNormalConstraints(ParFiniteElementSpace& fespace, Array& constrained_att, Array& constraint_rowstarts) { return BuildNormalConstraints(fespace, constrained_att, constraint_rowstarts, true); } #endif }