// Copyright (c) 2010-2024, 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 "darcyreduction.hpp" namespace mfem { DarcyReduction::DarcyReduction(FiniteElementSpace *fes_u_, FiniteElementSpace *fes_p_, bool bsym_) : fes_u(fes_u_), fes_p(fes_p_), bsym(bsym_) { m_nlfi_u = NULL; m_nlfi_p = NULL; own_m_nlfi_u = false; own_m_nlfi_p = false; Af_data = NULL; Bf_data = NULL; Df_data = NULL; S = NULL; } DarcyReduction::~DarcyReduction() { if (own_m_nlfi_u) { delete m_nlfi_u; } if (own_m_nlfi_p) { delete m_nlfi_p; } delete[] Af_data; delete[] Bf_data; delete[] Df_data; delete S; } void DarcyReduction::SetFluxMassNonlinearIntegrator(NonlinearFormIntegrator *flux_integ, bool own) { if (own_m_nlfi_u) { delete m_nlfi_u; } own_m_nlfi_u = own; m_nlfi_u = flux_integ; } void DarcyReduction::SetPotMassNonlinearIntegrator(NonlinearFormIntegrator *pot_integ, bool own) { if (own_m_nlfi_p) { delete m_nlfi_p; } own_m_nlfi_p = own; m_nlfi_p = pot_integ; } void DarcyReduction::InitA() { const int NE = fes_u->GetNE(); // Define Af_offsets and Af_f_offsets Af_offsets.SetSize(NE+1); Af_offsets[0] = 0; Af_f_offsets.SetSize(NE+1); Af_f_offsets[0] = 0; for (int i = 0; i < NE; i++) { int f_size = fes_u->GetFE(i)->GetDof() * fes_u->GetVDim(); Af_offsets[i+1] = Af_offsets[i] + f_size*f_size; Af_f_offsets[i+1] = Af_f_offsets[i] + f_size; } if (!m_nlfi_u) { Af_data = new real_t[Af_offsets[NE]]; } } void DarcyReduction::InitBD() { const int NE = fes_u->GetNE(); // Define Bf_offsets, Df_offsets and Df_f_offsets Bf_offsets.SetSize(NE+1); Bf_offsets[0] = 0; Df_offsets.SetSize(NE+1); Df_offsets[0] = 0; Df_f_offsets.SetSize(NE+1); Df_f_offsets[0] = 0; for (int i = 0; i < NE; i++) { int f_size = Af_f_offsets[i+1] - Af_f_offsets[i]; int d_size = fes_p->GetFE(i)->GetDof(); Bf_offsets[i+1] = Bf_offsets[i] + f_size*d_size; Df_offsets[i+1] = Df_offsets[i] + d_size*d_size; Df_f_offsets[i+1] = Df_f_offsets[i] + d_size; } Bf_data = new real_t[Bf_offsets[NE]]();//init by zeros if (!m_nlfi_p) { Df_data = new real_t[Df_offsets[NE]]();//init by zeros } } void DarcyReduction::Init(const Array &) { InitA(); InitBD(); } void DarcyReduction::AssembleFluxMassMatrix(int el, const DenseMatrix &A) { const int s = Af_f_offsets[el+1] - Af_f_offsets[el]; DenseMatrix A_i(Af_data + Af_offsets[el], s, s); MFEM_ASSERT(A.Size() == s, "Incompatible sizes"); A_i = A; } void DarcyReduction::AssemblePotMassMatrix(int el, const DenseMatrix &D) { const int s = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix D_i(Df_data + Df_offsets[el], s, s); MFEM_ASSERT(D.Size() == s, "Incompatible sizes"); D_i += D; } void DarcyReduction::AssembleDivMatrix(int el, const DenseMatrix &B) { const int w = Af_f_offsets[el+1] - Af_f_offsets[el]; const int h = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix B_i(Bf_data + Bf_offsets[el], h, w); MFEM_ASSERT(B.Width() == w && B.Height() == h, "Incompatible sizes"); B_i += B; } void DarcyReduction::Mult(const Vector &x, Vector &y) const { S->Mult(x, y); } void DarcyReduction::Finalize() { if (!S) { ComputeS(); } } void DarcyReduction::Reset() { delete S; S = NULL; const int NE = fes_u->GetMesh()->GetNE(); memset(Bf_data, 0, Bf_offsets[NE] * sizeof(real_t)); if (Df_data) { memset(Df_data, 0, Df_offsets[NE] * sizeof(real_t)); } } DarcyFluxReduction::DarcyFluxReduction(FiniteElementSpace *fes_u, FiniteElementSpace *fes_p, bool bsym) : DarcyReduction(fes_u, fes_p, bsym) { width = height = fes_p->GetVSize(); Af_ipiv = NULL; } DarcyFluxReduction::~DarcyFluxReduction() { delete[] Af_ipiv; } void DarcyFluxReduction::Init(const Array &ess_flux_tdof_list) { MFEM_ASSERT(ess_flux_tdof_list.Size() == 0, "Essential VDOFs are not supported"); DarcyReduction::Init(ess_flux_tdof_list); const int NE = fes_u->GetNE(); Af_ipiv = new int[Af_f_offsets[NE]]; } void DarcyFluxReduction::ComputeS() { MFEM_ASSERT(!m_nlfi_u && !m_nlfi_p, "Cannot assemble S matrix in the non-linear regime"); const int skip_zeros = 1; const int NE = fes_u->GetNE(); if (!S) { S = new SparseMatrix(fes_p->GetVSize()); } DenseMatrix AiBt; Array p_dofs; for (int el = 0; el < NE; el++) { int a_dofs_size = Af_f_offsets[el+1] - Af_f_offsets[el]; int d_dofs_size = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix D(Df_data + Df_offsets[el], d_dofs_size, d_dofs_size); DenseMatrix B(Bf_data + Bf_offsets[el], d_dofs_size, a_dofs_size); // Decompose A LUFactors LU_A(Af_data + Af_offsets[el], Af_ipiv + Af_f_offsets[el]); LU_A.Factor(a_dofs_size); // Schur complement AiBt.Transpose(B); if (!bsym) { AiBt.Neg(); } LU_A.Solve(AiBt.Height(), AiBt.Width(), AiBt.GetData()); mfem::AddMult(B, AiBt, D); fes_p->GetElementDofs(el, p_dofs); S->AddSubMatrix(p_dofs, p_dofs, D, skip_zeros); } S->Finalize(); } void DarcyFluxReduction::ReduceRHS(const BlockVector &b, Vector &b_r) const { const int NE = fes_u->GetNE(); Vector bu_l, bp_l; Array u_vdofs, p_dofs; const Vector &bu = b.GetBlock(0); const Vector &bp = b.GetBlock(1); if (b_r.Size() != S->Height()) { b_r.SetSize(S->Height()); } for (int el = 0; el < NE; el++) { // Load RHS fes_u->GetElementVDofs(el, u_vdofs); bu.GetSubVector(u_vdofs, bu_l); fes_p->GetElementDofs(el, p_dofs); bp.GetSubVector(p_dofs, bp_l); // -B A^-1 bu int a_dofs_size = Af_f_offsets[el+1] - Af_f_offsets[el]; int d_dofs_size = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix B(Bf_data + Bf_offsets[el], d_dofs_size, a_dofs_size); LUFactors LU_A(Af_data + Af_offsets[el], Af_ipiv + Af_f_offsets[el]); LU_A.Solve(a_dofs_size, 1, bu_l.GetData()); B.AddMult(bu_l, bp_l, (bsym)?(+1.):(-1.)); b_r.SetSubVector(p_dofs, bp_l); } } void DarcyFluxReduction::ComputeSolution(const BlockVector &b, const Vector &sol_r, BlockVector &sol) const { const int NE = fes_u->GetNE(); Vector bu_l, p_l; Array u_vdofs, p_dofs; const Vector &bu = b.GetBlock(0); //const Vector &bp = b.GetBlock(1); Vector &u = sol.GetBlock(0); Vector &p = sol.GetBlock(1); p = sol_r; for (int el = 0; el < NE; el++) { //Load RHS fes_u->GetElementVDofs(el, u_vdofs); bu.GetSubVector(u_vdofs, bu_l); fes_p->GetElementDofs(el, p_dofs); p.GetSubVector(p_dofs, p_l); // A^-1 (R - B^T p) int a_dofs_size = Af_f_offsets[el+1] - Af_f_offsets[el]; int d_dofs_size = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix B(Bf_data + Bf_offsets[el], d_dofs_size, a_dofs_size); LUFactors LU_A(Af_data + Af_offsets[el], Af_ipiv + Af_f_offsets[el]); B.AddMultTranspose(p_l, bu_l, (bsym)?(-1.):(+1.)); LU_A.Solve(a_dofs_size, 1, bu_l.GetData()); u.SetSubVector(u_vdofs, bu_l); } } DarcyPotentialReduction::DarcyPotentialReduction(FiniteElementSpace *fes_u, FiniteElementSpace *fes_p, bool bsym) : DarcyReduction(fes_u, fes_p, bsym) { width = height = fes_u->GetVSize(); Ae_data = NULL; Be_data = NULL; Df_ipiv = NULL; } DarcyPotentialReduction::~DarcyPotentialReduction() { delete[] Ae_data; delete[] Be_data; delete[] Df_ipiv; } void DarcyPotentialReduction::Init(const Array &ess_flux_tdof_list) { const int NE = fes_p->GetNE(); // count the number of dofs in the discontinuous version of fes: Array vdofs; int num_hat_dofs = 0; hat_offsets.SetSize(NE+1); hat_offsets[0] = 0; for (int i = 0; i < NE; i++) { fes_u->GetElementVDofs(i, vdofs); num_hat_dofs += vdofs.Size(); hat_offsets[i+1] = num_hat_dofs; } // Define the "free" (0) and "essential" (1) hat_dofs. // The "essential" hat_dofs are those that depend only on essential cdofs; // all other hat_dofs are "free". hat_dofs_marker.SetSize(num_hat_dofs); Array free_tdof_marker; #ifdef MFEM_USE_MPI ParFiniteElementSpace *pfes = dynamic_cast(fes_u); free_tdof_marker.SetSize(pfes ? pfes->TrueVSize() : fes_u->GetConformingVSize()); #else free_tdof_marker.SetSize(fes_u->GetConformingVSize()); #endif free_tdof_marker = 1; for (int i = 0; i < ess_flux_tdof_list.Size(); i++) { free_tdof_marker[ess_flux_tdof_list[i]] = 0; } Array free_vdofs_marker; #ifdef MFEM_USE_MPI if (!pfes) { const SparseMatrix *cP = fes_u->GetConformingProlongation(); if (!cP) { free_vdofs_marker.MakeRef(free_tdof_marker); } else { free_vdofs_marker.SetSize(fes_u->GetVSize()); cP->BooleanMult(free_tdof_marker, free_vdofs_marker); } } else { HypreParMatrix *P = pfes->Dof_TrueDof_Matrix(); free_vdofs_marker.SetSize(fes_u->GetVSize()); P->BooleanMult(1, free_tdof_marker, 0, free_vdofs_marker); } #else const SparseMatrix *cP = fes_u->GetConformingProlongation(); if (!cP) { free_vdofs_marker.MakeRef(free_tdof_marker); } else { free_vdofs_marker.SetSize(fes_u->GetVSize()); cP->BooleanMult(free_tdof_marker, free_vdofs_marker); } #endif for (int i = 0; i < NE; i++) { fes_u->GetElementVDofs(i, vdofs); FiniteElementSpace::AdjustVDofs(vdofs); for (int j = 0; j < vdofs.Size(); j++) { hat_dofs_marker[hat_offsets[i]+j] = ! free_vdofs_marker[vdofs[j]]; } } #ifndef MFEM_DEBUG // In DEBUG mode this array is used below. free_tdof_marker.DeleteAll(); #endif free_vdofs_marker.DeleteAll(); // Split the "free" (0) hat_dofs into "internal" (0) or "boundary" (-1). // The "internal" hat_dofs are those "free" hat_dofs for which the // corresponding column in C is zero; otherwise the free hat_dof is // "boundary". /*for (int i = 0; i < num_hat_dofs; i++) { // skip "essential" hat_dofs and empty rows in Ct if (hat_dofs_marker[i] == 1) { continue; } //CT row???????? //hat_dofs_marker[i] = -1; // mark this hat_dof as "boundary" }*/ // Define Af_offsets and Af_f_offsets Af_offsets.SetSize(NE+1); Af_offsets[0] = 0; Af_f_offsets.SetSize(NE+1); Af_f_offsets[0] = 0; #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS Ae_offsets.SetSize(NE+1); Ae_offsets[0] = 0; Be_offsets.SetSize(NE+1); Be_offsets[0] = 0; #endif //MFEM_DARCY_REDUCTION_ELIM_BCS for (int i = 0; i < NE; i++) { int f_size = 0; // count the "free" hat_dofs in element i for (int j = hat_offsets[i]; j < hat_offsets[i+1]; j++) { if (hat_dofs_marker[j] != 1) { f_size++; } } Af_offsets[i+1] = Af_offsets[i] + f_size*f_size; Af_f_offsets[i+1] = Af_f_offsets[i] + f_size; #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS int a_size = hat_offsets[i+1] - hat_offsets[i]; int e_size = a_size - f_size; int d_size = fes_p->GetFE(i)->GetDof(); Ae_offsets[i+1] = Ae_offsets[i] + e_size*a_size; Be_offsets[i+1] = Be_offsets[i] + e_size*d_size; #endif //MFEM_DARCY_REDUCTION_ELIM_BCS } if (!m_nlfi_u) { Af_data = new real_t[Af_offsets[NE]]; #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS Ae_data = new real_t[Ae_offsets[NE]]; #endif //MFEM_DARCY_REDUCTION_ELIM_BCS } InitBD(); #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS Be_data = new real_t[Be_offsets[NE]]();//init by zeros #endif //MFEM_DARCY_REDUCTION_ELIM_BCS Df_ipiv = new int[Df_f_offsets[NE]]; } void DarcyPotentialReduction::GetFDofs(int el, Array &fdofs) const { const int o = hat_offsets[el]; const int s = hat_offsets[el+1] - o; Array vdofs; fes_u->GetElementVDofs(el, vdofs); MFEM_ASSERT(vdofs.Size() == s, "Incompatible DOF sizes"); fdofs.DeleteAll(); fdofs.Reserve(s); for (int i = 0; i < s; i++) { if (hat_dofs_marker[i + o] != 1) { fdofs.Append(vdofs[i]); } } } void DarcyPotentialReduction::GetEDofs(int el, Array &edofs) const { const int o = hat_offsets[el]; const int s = hat_offsets[el+1] - o; Array vdofs; fes_u->GetElementVDofs(el, vdofs); MFEM_ASSERT(vdofs.Size() == s, "Incompatible DOF sizes"); edofs.DeleteAll(); edofs.Reserve(s); for (int i = 0; i < s; i++) { if (hat_dofs_marker[i + o] == 1) { edofs.Append(vdofs[i]); } } } void DarcyPotentialReduction::ComputeS() { MFEM_ASSERT(!m_nlfi_u && !m_nlfi_p, "Cannot assemble S matrix in the non-linear regime"); const int skip_zeros = 1; const int NE = fes_u->GetNE(); if (!S) { S = new SparseMatrix(fes_u->GetVSize()); } DenseMatrix DiB; Array a_dofs; for (int el = 0; el < NE; el++) { int a_dofs_size = Af_f_offsets[el+1] - Af_f_offsets[el]; int d_dofs_size = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix A(Af_data + Af_offsets[el], a_dofs_size, a_dofs_size); DenseMatrix B(Bf_data + Bf_offsets[el], d_dofs_size, a_dofs_size); // Decompose D LUFactors LU_D(Df_data + Df_offsets[el], Df_ipiv + Df_f_offsets[el]); LU_D.Factor(d_dofs_size); // Schur complement DiB = B; if (!bsym) { DiB.Neg(); } LU_D.Solve(DiB.Height(), DiB.Width(), DiB.GetData()); mfem::AddMultAtB(B, DiB, A); GetFDofs(el, a_dofs); S->AddSubMatrix(a_dofs, a_dofs, A, skip_zeros); // Complete the diagonal GetEDofs(el, a_dofs); FiniteElementSpace::AdjustVDofs(a_dofs); for (int i = 0; i < a_dofs.Size(); i++) { S->Set(a_dofs[i], a_dofs[i], 1.); } } S->Finalize(); } void DarcyPotentialReduction::AssembleFluxMassMatrix(int el, const DenseMatrix &A) { const int o = hat_offsets[el]; const int s = hat_offsets[el+1] - o; real_t *Af_el_data = Af_data + Af_offsets[el]; #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS real_t *Ae_el_data = Ae_data + Ae_offsets[el]; #endif //MFEM_DARCY_REDUCTION_ELIM_BCS for (int j = 0; j < s; j++) { if (hat_dofs_marker[o + j] == 1) { #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS for (int i = 0; i < s; i++) { *(Ae_el_data++) = A(i, j); } #endif //MFEM_DARCY_REDUCTION_ELIM_BCS continue; } for (int i = 0; i < s; i++) { if (hat_dofs_marker[o + i] == 1) { continue; } *(Af_el_data++) = A(i, j); } } MFEM_ASSERT(Af_el_data == Af_data + Af_offsets[el+1], "Internal error"); #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS MFEM_ASSERT(Ae_el_data == Ae_data + Ae_offsets[el+1], "Internal error"); #endif //MFEM_DARCY_REDUCTION_ELIM_BCS } void DarcyPotentialReduction::AssembleDivMatrix(int el, const DenseMatrix &B) { const int o = hat_offsets[el]; const int w = hat_offsets[el+1] - o; const int h = Df_f_offsets[el+1] - Df_f_offsets[el]; real_t *Bf_el_data = Bf_data + Bf_offsets[el]; #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS real_t *Be_el_data = Be_data + Be_offsets[el]; #endif //MFEM_DARCY_REDUCTION_ELIM_BCS for (int j = 0; j < w; j++) { if (hat_dofs_marker[o + j] == 1) { #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS for (int i = 0; i < h; i++) { *(Be_el_data++) += B(i, j); } #endif //MFEM_DARCY_REDUCTION_ELIM_BCS continue; } for (int i = 0; i < h; i++) { *(Bf_el_data++) += B(i, j); } } MFEM_ASSERT(Bf_el_data == Bf_data + Bf_offsets[el+1], "Internal error"); #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS MFEM_ASSERT(Be_el_data == Be_data + Be_offsets[el+1], "Internal error"); #endif //MFEM_DARCY_REDUCTION_ELIM_BCS } void DarcyPotentialReduction::EliminateVDofsInRHS(const Array &vdofs_flux, const BlockVector &x, BlockVector &b) { const int NE = fes_u->GetNE(); Vector u_e, bu_e, bp_e; Array u_vdofs, p_dofs, edofs; const Vector &xu = x.GetBlock(0); Vector &bu = b.GetBlock(0); Vector &bp = b.GetBlock(1); for (int el = 0; el < NE; el++) { GetEDofs(el, edofs); xu.GetSubVector(edofs, u_e); u_e.Neg(); //bu -= A_e u_e const int a_size = hat_offsets[el+1] - hat_offsets[el]; DenseMatrix Ae(Ae_data + Ae_offsets[el], a_size, edofs.Size()); bu_e.SetSize(a_size); Ae.Mult(u_e, bu_e); fes_u->GetElementVDofs(el, u_vdofs); bu.AddElementVector(u_vdofs, bu_e); //bp -= B_e u_e const int d_size = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix Be(Be_data + Be_offsets[el], d_size, edofs.Size()); bp_e.SetSize(d_size); Be.Mult(u_e, bp_e); if (bsym) { //In the case of the symmetrized system, the sign is oppposite! bp_e.Neg(); } fes_p->GetElementDofs(el, p_dofs); bp.AddElementVector(p_dofs, bp_e); } for (int vdof : vdofs_flux) { bu(vdof) = xu(vdof);//<--can be arbitrary as it is ignored } } void DarcyPotentialReduction::ReduceRHS(const BlockVector &b, Vector &b_r) const { const int NE = fes_u->GetNE(); Vector bu_l, bp_l; Array u_vdofs, p_dofs; const Vector &bu = b.GetBlock(0); const Vector &bp = b.GetBlock(1); b_r = bu; for (int el = 0; el < NE; el++) { // Load RHS GetFDofs(el, u_vdofs); bu_l.SetSize(u_vdofs.Size()); fes_p->GetElementDofs(el, p_dofs); bp.GetSubVector(p_dofs, bp_l); // -B^T D^-1 bp int a_dofs_size = Af_f_offsets[el+1] - Af_f_offsets[el]; int d_dofs_size = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix B(Bf_data + Bf_offsets[el], d_dofs_size, a_dofs_size); LUFactors LU_D(Df_data + Df_offsets[el], Df_ipiv + Df_f_offsets[el]); LU_D.Solve(d_dofs_size, 1, bp_l.GetData()); bp_l.Neg(); B.MultTranspose(bp_l, bu_l); b_r.AddElementVector(u_vdofs, bu_l); } } void DarcyPotentialReduction::ComputeSolution(const BlockVector &b, const Vector &sol_r, BlockVector &sol) const { const int NE = fes_u->GetNE(); Vector bp_l, u_l; Array u_vdofs, p_dofs; //const Vector &bu = b.GetBlock(0); const Vector &bp = b.GetBlock(1); Vector &u = sol.GetBlock(0); Vector &p = sol.GetBlock(1); u = sol_r; for (int el = 0; el < NE; el++) { //Load RHS GetFDofs(el, u_vdofs); u.GetSubVector(u_vdofs, u_l); fes_p->GetElementDofs(el, p_dofs); bp.GetSubVector(p_dofs, bp_l); if (bsym) { //In the case of the symmetrized system, the sign is oppposite! bp_l.Neg(); } // D^-1 (F - B u) int a_dofs_size = Af_f_offsets[el+1] - Af_f_offsets[el]; int d_dofs_size = Df_f_offsets[el+1] - Df_f_offsets[el]; DenseMatrix B(Bf_data + Bf_offsets[el], d_dofs_size, a_dofs_size); LUFactors LU_D(Df_data + Df_offsets[el], Df_ipiv + Df_f_offsets[el]); B.AddMult(u_l, bp_l, -1.); LU_D.Solve(d_dofs_size, 1, bp_l.GetData()); p.SetSubVector(p_dofs, bp_l); } } void DarcyPotentialReduction::Reset() { DarcyReduction::Reset(); #ifdef MFEM_DARCY_REDUCTION_ELIM_BCS const int NE = fes_p->GetNE(); memset(Be_data, 0, Be_offsets[NE] * sizeof(real_t)); #endif //MFEM_DARCY_REDUCTION_ELIM_BCS } }