957 lines
28 KiB
C++
957 lines
28 KiB
C++
// Copyright (c) 2010-2025, 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 <set>
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
Eliminator::Eliminator(const SparseMatrix& B, const Array<int>& lagrange_tdofs_,
|
|
const Array<int>& primary_tdofs_,
|
|
const Array<int>& 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<Eliminator*>& 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<int>& primary_dofs,
|
|
Array<int>& 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<int> 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<int>& constraint_rowstarts)
|
|
:
|
|
ConstrainedSolver(A.GetComm(), A, B),
|
|
hA(A),
|
|
krylov(nullptr),
|
|
prec(nullptr)
|
|
{
|
|
if (!B.Empty())
|
|
{
|
|
int * I = B.GetI();
|
|
int * J = B.GetJ();
|
|
real_t * data = B.GetData();
|
|
|
|
for (int k = 0; k < constraint_rowstarts.Size() - 1; ++k)
|
|
{
|
|
int constraint_size = constraint_rowstarts[k + 1] -
|
|
constraint_rowstarts[k];
|
|
Array<int> lagrange_dofs(constraint_size);
|
|
Array<int> primary_dofs;
|
|
Array<int> 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];
|
|
real_t 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, real_t 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, real_t 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 override { y = x; }
|
|
void SetOperator(const Operator& op) override { }
|
|
};
|
|
|
|
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<BlockDiagonalPreconditioner&>(*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<Vector&>(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<ParFiniteElementSpace*>(&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<int>& constrained_att,
|
|
Array<int>& 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<int, int> dof_bconstraint;
|
|
// constraints[j] is a map from attribute to row number,
|
|
// the j itself is the index of a block-constraint
|
|
std::vector<std::map<int, int> > constraints;
|
|
int n_bconstraints = 0;
|
|
int n_rows = 0;
|
|
for (int att : constrained_att)
|
|
{
|
|
// identify tdofs on constrained boundary
|
|
std::set<int> constrained_tdofs;
|
|
for (int i = 0; i < fespace.GetNBE(); ++i)
|
|
{
|
|
if (fespace.GetBdrAttribute(i) == att)
|
|
{
|
|
Array<int> 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<int, int> 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<int, int> 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<int> 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 real_t pv = mout->SearchRow(inner_truek);
|
|
const real_t scaling = ((real_t) (visits - 1)) /
|
|
((real_t) 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<int>& constrained_att,
|
|
Array<int>& constraint_rowstarts)
|
|
{
|
|
return BuildNormalConstraints(fespace, constrained_att,
|
|
constraint_rowstarts, true);
|
|
}
|
|
#endif
|
|
|
|
}
|