848 lines
22 KiB
C++
848 lines
22 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 "mfem.hpp"
|
|
#include "unit_tests.hpp"
|
|
|
|
using namespace mfem;
|
|
|
|
class IdentitySolver : public Solver
|
|
{
|
|
public:
|
|
IdentitySolver(int n) : Solver(n) { }
|
|
void Mult(const Vector& x, Vector& y) const override { y = x; }
|
|
void SetOperator(const Operator& op) override { }
|
|
};
|
|
|
|
class SimpleSaddle
|
|
{
|
|
public:
|
|
SimpleSaddle(double alpha, double beta, bool parallel);
|
|
~SimpleSaddle();
|
|
|
|
void Schur(Vector& serr, Vector& lerr);
|
|
#ifdef MFEM_USE_MPI
|
|
void Penalty(double pen, Vector& serr, Vector& lerr);
|
|
void Elimination(Vector &serr, Vector& lerr, bool swap);
|
|
#endif
|
|
|
|
void SetConstraintRHS(Vector &dualrhs_);
|
|
|
|
private:
|
|
SparseMatrix A, B;
|
|
#ifdef MFEM_USE_MPI
|
|
HypreParMatrix * hA;
|
|
#endif
|
|
Vector rhs, sol, dualrhs, lambda;
|
|
double truex, truey, truelambda;
|
|
};
|
|
|
|
SimpleSaddle::SimpleSaddle(double alpha, double beta, bool parallel)
|
|
:
|
|
A(2, 2), B(1, 2), rhs(2), sol(2), dualrhs(1), lambda(1)
|
|
{
|
|
truex = 0.5 * alpha - 0.5 * beta;
|
|
truey = -0.5 * alpha + 0.5 * beta;
|
|
truelambda = 0.5 * (alpha + beta);
|
|
|
|
A.Add(0, 0, 1.0);
|
|
A.Add(1, 1, 1.0);
|
|
A.Finalize();
|
|
B.Add(0, 0, 1.0);
|
|
B.Add(0, 1, 1.0);
|
|
B.Finalize();
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
if (parallel)
|
|
{
|
|
HYPRE_BigInt row_starts[2] = {0, 2};
|
|
hA = new HypreParMatrix(MPI_COMM_WORLD, 2, row_starts, &A);
|
|
hA->CopyRowStarts();
|
|
}
|
|
else
|
|
{
|
|
hA = NULL;
|
|
}
|
|
#endif
|
|
|
|
rhs(0) = alpha;
|
|
rhs(1) = beta;
|
|
|
|
dualrhs = 0.0;
|
|
|
|
sol = 0.0;
|
|
}
|
|
|
|
SimpleSaddle::~SimpleSaddle()
|
|
{
|
|
#ifdef MFEM_USE_MPI
|
|
delete hA;
|
|
#endif
|
|
}
|
|
|
|
void SimpleSaddle::SetConstraintRHS(Vector& dualrhs_)
|
|
{
|
|
dualrhs = dualrhs_;
|
|
truelambda = truelambda - 0.5 * dualrhs(0);
|
|
truex = truex + 0.5 * dualrhs(0);
|
|
truey = truey + 0.5 * dualrhs(0);
|
|
}
|
|
|
|
void SimpleSaddle::Schur(Vector& serr, Vector& lerr)
|
|
{
|
|
IdentitySolver prec(2);
|
|
SchurConstrainedSolver * solver;
|
|
#ifdef MFEM_USE_MPI
|
|
if (hA)
|
|
{
|
|
solver = new SchurConstrainedSolver(MPI_COMM_WORLD, *hA, B, prec);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
solver = new SchurConstrainedSolver(A, B, prec);
|
|
}
|
|
solver->SetConstraintRHS(dualrhs);
|
|
solver->SetRelTol(1.e-14);
|
|
solver->Mult(rhs, sol);
|
|
solver->GetMultiplierSolution(lambda);
|
|
serr(0) = truex - sol(0);
|
|
serr(1) = truey - sol(1);
|
|
lerr(0) = truelambda - lambda(0);
|
|
delete solver;
|
|
}
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
void SimpleSaddle::Elimination(Vector& serr, Vector& lerr, bool swap)
|
|
{
|
|
Array<int> lagrange_rowstarts(2);
|
|
lagrange_rowstarts[0] = 0;
|
|
lagrange_rowstarts[1] = B.Height();
|
|
EliminationCGSolver solver(*hA, B, lagrange_rowstarts);
|
|
solver.SetConstraintRHS(dualrhs);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
serr(0) = truex - sol(0);
|
|
serr(1) = truey - sol(1);
|
|
lerr(0) = truelambda - lambda(0);
|
|
}
|
|
|
|
void SimpleSaddle::Penalty(double pen, Vector& serr, Vector& lerr)
|
|
{
|
|
PenaltyPCGSolver solver(*hA, B, pen);
|
|
solver.SetConstraintRHS(dualrhs);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
serr(0) = truex - sol(0);
|
|
serr(1) = truey - sol(1);
|
|
lerr(0) = truelambda - lambda(0);
|
|
}
|
|
#endif
|
|
|
|
// very basic sanity check - most of the useful/interesting solvers require MPI
|
|
TEST_CASE("SerialConstrainedSolver", "[ConstrainedSolver]")
|
|
{
|
|
Vector serr(2);
|
|
Vector lerr(1);
|
|
|
|
SimpleSaddle problem(4.0, -2.0, false);
|
|
|
|
problem.Schur(serr, lerr);
|
|
REQUIRE(serr(0) == MFEM_Approx(0.0));
|
|
REQUIRE(serr(1) == MFEM_Approx(0.0));
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
|
|
Vector dualrhs(1);
|
|
dualrhs(0) = 1.0;
|
|
problem.SetConstraintRHS(dualrhs);
|
|
|
|
problem.Schur(serr, lerr);
|
|
REQUIRE(serr(0) == MFEM_Approx(0.0));
|
|
REQUIRE(serr(1) == MFEM_Approx(0.0));
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
}
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
|
|
// this test case is intended to run on one processor, but it is
|
|
// marked [Parallel] because it uses hypre
|
|
TEST_CASE("ConstrainedSolver", "[Parallel], [ConstrainedSolver]")
|
|
{
|
|
if (HypreUsingGPU())
|
|
{
|
|
mfem::out << "\nAs of mfem-4.3 and hypre-2.22.0 (July 2021) this unit test\n"
|
|
<< "is NOT supported with the GPU version of hypre.\n\n";
|
|
return;
|
|
}
|
|
|
|
int comm_size;
|
|
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
|
|
|
|
if (comm_size == 1)
|
|
{
|
|
Vector serr(2);
|
|
Vector lerr(1);
|
|
|
|
SimpleSaddle problem(4.0, -2.0, true);
|
|
|
|
problem.Schur(serr, lerr);
|
|
REQUIRE(serr(0) == MFEM_Approx(0.0));
|
|
REQUIRE(serr(1) == MFEM_Approx(0.0));
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
|
|
problem.Elimination(serr, lerr, false);
|
|
REQUIRE(serr(0) == MFEM_Approx(0.0));
|
|
REQUIRE(serr(1) == MFEM_Approx(0.0));
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
|
|
problem.Elimination(serr, lerr, true);
|
|
REQUIRE(serr(0) == MFEM_Approx(0.0));
|
|
REQUIRE(serr(1) == MFEM_Approx(0.0));
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
|
|
for (auto pen : {1.e+3, 1.e+4, 1.e+6})
|
|
{
|
|
problem.Penalty(pen, serr, lerr);
|
|
REQUIRE(std::abs(serr(0)) < 1./pen);
|
|
REQUIRE(std::abs(serr(1)) < 1./pen);
|
|
REQUIRE(std::abs(lerr(0)) < 1./pen);
|
|
}
|
|
|
|
Vector dualrhs(1);
|
|
dualrhs(0) = 1.0;
|
|
problem.SetConstraintRHS(dualrhs);
|
|
|
|
problem.Schur(serr, lerr);
|
|
REQUIRE(serr(0) == MFEM_Approx(0.0));
|
|
REQUIRE(serr(1) == MFEM_Approx(0.0));
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
|
|
problem.Elimination(serr, lerr, false);
|
|
REQUIRE(serr(0) == MFEM_Approx(0.0));
|
|
REQUIRE(serr(1) == MFEM_Approx(0.0));
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
|
|
for (auto pen : {1.e+3, 1.e+4, 1.e+6})
|
|
{
|
|
problem.Penalty(pen, serr, lerr);
|
|
REQUIRE(std::abs(serr(0)) < 1./pen);
|
|
REQUIRE(std::abs(serr(1)) < 1./pen);
|
|
REQUIRE(std::abs(lerr(0)) < 1./pen);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// this problem is general, with constraints crossing
|
|
/// processor boundaries (elimination does not work in this case)
|
|
class ParallelTestProblem
|
|
{
|
|
public:
|
|
ParallelTestProblem();
|
|
~ParallelTestProblem();
|
|
|
|
void Schur(Vector& serr, Vector& lerr);
|
|
void Penalty(double pen, Vector& serr, Vector& lerr);
|
|
|
|
private:
|
|
SparseMatrix Alocal;
|
|
Vector rhs, sol, truesol, lambda, truelambda;
|
|
HypreParMatrix * amat;
|
|
HypreParMatrix * bmat;
|
|
};
|
|
|
|
|
|
ParallelTestProblem::ParallelTestProblem()
|
|
:
|
|
Alocal(2), rhs(2), sol(2), truesol(2), lambda(1), truelambda(1)
|
|
{
|
|
int rank;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
|
|
Alocal.Add(0, 0, 1.0);
|
|
Alocal.Add(1, 1, 1.0);
|
|
Alocal.Finalize();
|
|
|
|
HYPRE_BigInt row_starts_a[2] = {2 * rank, 2 * (rank + 1)};
|
|
amat = new HypreParMatrix(MPI_COMM_WORLD, 8, row_starts_a, &Alocal);
|
|
amat->CopyRowStarts();
|
|
|
|
SparseMatrix Blocal(1, 8);
|
|
if (rank == 3)
|
|
{
|
|
Blocal.Add(0, 0, 1.0);
|
|
Blocal.Add(0, 7, 1.0);
|
|
}
|
|
else
|
|
{
|
|
Blocal.Add(0, 2*rank + 1, 1.0);
|
|
Blocal.Add(0, 2*rank + 2, 1.0);
|
|
}
|
|
Blocal.Finalize();
|
|
HYPRE_BigInt row_starts_c[2] = { rank, rank + 1 };
|
|
HYPRE_BigInt col_starts[2] = { 2*rank, 2 * (rank + 1) };
|
|
|
|
Array<HYPRE_BigInt> Blocal_J(Blocal.NumNonZeroElems());
|
|
for (int i=0; i < Blocal_J.Size(); ++i)
|
|
{
|
|
Blocal_J[i] = static_cast<HYPRE_BigInt>(Blocal.GetJ()[i]);
|
|
}
|
|
|
|
bmat = new HypreParMatrix(MPI_COMM_WORLD, 1, 4, 8, Blocal.GetI(),
|
|
Blocal_J.GetData(), Blocal.GetData(), row_starts_c,
|
|
col_starts);
|
|
|
|
// rhs // [ 1.1 -2. 3. -1.4 2.1 -3.2 -1.1 2.2 0. 0. 0. 0. ]
|
|
// truesol // [-0.55 -2.5 2.5 -1.75 1.75 -1.05 1.05 0.55 0.5 0.35 -2.15 1.65]
|
|
|
|
sol = 0.0;
|
|
rhs = 0.0;
|
|
if (rank == 0)
|
|
{
|
|
rhs(0) = 1.1;
|
|
truesol(0) = -0.55;
|
|
rhs(1) = -2.0;
|
|
truesol(1) = -2.5;
|
|
truelambda(0) = 0.5;
|
|
}
|
|
else if (rank == 1)
|
|
{
|
|
rhs(0) = 3.0;
|
|
truesol(0) = 2.5;
|
|
rhs(1) = -1.4;
|
|
truesol(1) = -1.75;
|
|
truelambda(0) = 0.35;
|
|
}
|
|
else if (rank == 2)
|
|
{
|
|
rhs(0) = 2.1;
|
|
truesol(0) = 1.75;
|
|
rhs(1) = -3.2;
|
|
truesol(1) = -1.05;
|
|
truelambda(0) = -2.15;
|
|
}
|
|
else if (rank == 3)
|
|
{
|
|
rhs(0) = -1.1;
|
|
truesol(0) = 1.05;
|
|
rhs(1) = 2.2;
|
|
truesol(1) = 0.55;
|
|
truelambda(0) = 1.65;
|
|
}
|
|
else
|
|
{
|
|
mfem_error("Test only works on 4 ranks!");
|
|
}
|
|
}
|
|
|
|
ParallelTestProblem::~ParallelTestProblem()
|
|
{
|
|
delete amat;
|
|
delete bmat;
|
|
}
|
|
|
|
void ParallelTestProblem::Schur(Vector& serr, Vector& lerr)
|
|
{
|
|
IdentitySolver prec(2);
|
|
SchurConstrainedSolver solver(MPI_COMM_WORLD, *amat, *bmat, prec);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
for (int i = 0; i < truesol.Size(); ++i)
|
|
{
|
|
serr(i) = truesol(i) - sol(i);
|
|
}
|
|
for (int i = 0; i < truelambda.Size(); ++i)
|
|
{
|
|
lerr(i) = truelambda(i) - lambda(i);
|
|
}
|
|
}
|
|
|
|
void ParallelTestProblem::Penalty(double pen, Vector& serr, Vector& lerr)
|
|
{
|
|
PenaltyPCGSolver solver(*amat, *bmat, pen);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
for (int i = 0; i < truesol.Size(); ++i)
|
|
{
|
|
serr(i) = truesol(i) - sol(i);
|
|
}
|
|
for (int i = 0; i < truelambda.Size(); ++i)
|
|
{
|
|
lerr(i) = truelambda(i) - lambda(i);
|
|
}
|
|
}
|
|
|
|
/// *actual* parallel constrained solver
|
|
TEST_CASE("ParallelConstrainedSolver", "[Parallel], [ConstrainedSolver]")
|
|
{
|
|
if (HypreUsingGPU())
|
|
{
|
|
mfem::out << "\nAs of mfem-4.3 and hypre-2.22.0 (July 2021) this unit test\n"
|
|
<< "is NOT supported with the GPU version of hypre.\n\n";
|
|
return;
|
|
}
|
|
|
|
int comm_size;
|
|
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
|
|
|
|
if (comm_size == 4)
|
|
{
|
|
Vector serr(2), lerr(1);
|
|
ParallelTestProblem problem;
|
|
problem.Schur(serr, lerr);
|
|
double serrnorm = serr.Norml2();
|
|
INFO("Parallel Schur primal error: " << serrnorm << "\n");
|
|
REQUIRE(serrnorm == MFEM_Approx(0.0));
|
|
INFO("Parallel Schur dual error: " << lerr(0) << "\n");
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
|
|
for (auto pen : {1.e+3, 1.e+4, 1.e+6})
|
|
{
|
|
problem.Penalty(pen, serr, lerr);
|
|
serrnorm = serr.Norml2();
|
|
INFO("Parallel penalty primal error: " << serrnorm << "\n");
|
|
REQUIRE(serrnorm == MFEM_Approx(0.0, 2./pen));
|
|
INFO("Parallel penalty dual error: " << lerr(0) << "\n");
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0, 2./pen));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// test that block (nodal) eliminators do the same thing as the global
|
|
// eliminator, and also test that the assembled matrix has the same action
|
|
// as the object
|
|
TEST_CASE("EliminationProjection", "[Parallel], [ConstrainedSolver]")
|
|
{
|
|
int comm_size;
|
|
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
|
|
|
|
if (comm_size == 1)
|
|
{
|
|
SparseMatrix A(4, 4);
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
A.Add(i, i, 1.0);
|
|
}
|
|
A.Finalize();
|
|
SparseMatrix B(2, 4);
|
|
B.Add(0, 0, 1.0);
|
|
B.Add(0, 1, 1.0);
|
|
B.Add(1, 2, 1.0);
|
|
B.Add(1, 3, 1.0);
|
|
B.Finalize();
|
|
|
|
Array<int> primary_dofs;
|
|
primary_dofs.Append(1);
|
|
primary_dofs.Append(3);
|
|
Array<int> secondary_dofs;
|
|
secondary_dofs.Append(0);
|
|
secondary_dofs.Append(2);
|
|
Array<int> lagrange_dofs;
|
|
lagrange_dofs.Append(0);
|
|
lagrange_dofs.Append(1);
|
|
|
|
Eliminator eliminator(B, lagrange_dofs, primary_dofs, secondary_dofs);
|
|
Array<Eliminator*> eliminators;
|
|
eliminators.Append(&eliminator);
|
|
EliminationProjection newep(A, eliminators);
|
|
SparseMatrix * new_assembled_ep = newep.AssembleExact();
|
|
|
|
Array<int> n_lagrange_dofs(1);
|
|
Array<int> n_primary_dofs(1);
|
|
Array<int> n_secondary_dofs(1);
|
|
n_lagrange_dofs[0] = 0;
|
|
n_primary_dofs[0] = 1;
|
|
n_secondary_dofs[0] = 0;
|
|
Eliminator elimone(B, n_lagrange_dofs, n_primary_dofs, n_secondary_dofs);
|
|
n_lagrange_dofs[0] = 1;
|
|
n_primary_dofs[0] = 3;
|
|
n_secondary_dofs[0] = 2;
|
|
Eliminator elimtwo(B, n_lagrange_dofs, n_primary_dofs, n_secondary_dofs);
|
|
Array<Eliminator*> nodal_eliminators(2);
|
|
nodal_eliminators[0] = &elimone;
|
|
nodal_eliminators[1] = &elimtwo;
|
|
EliminationProjection new_nodalep(A, nodal_eliminators);
|
|
|
|
Vector x(2);
|
|
x.Randomize();
|
|
// x = 0.0;
|
|
// x(1) = 1.0;
|
|
Vector newx(4);
|
|
newx = 0.0;
|
|
for (int i = 0; i < primary_dofs.Size(); ++i)
|
|
{
|
|
newx(primary_dofs[i]) = x(i);
|
|
}
|
|
Vector nepy(4), newepy(4), aepy(4);
|
|
newep.Mult(newx, newepy);
|
|
new_nodalep.Mult(newx, nepy);
|
|
new_assembled_ep->Mult(newx, aepy);
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
REQUIRE(nepy(i) - aepy(i) == MFEM_Approx(0.0));
|
|
REQUIRE(nepy(i) - newepy(i) == MFEM_Approx(0.0));
|
|
}
|
|
|
|
Vector xt(4);
|
|
xt.Randomize();
|
|
Vector newepyt(4), nepyt(4), aepyt(4);
|
|
newep.MultTranspose(xt, newepyt);
|
|
new_nodalep.MultTranspose(xt, nepyt);
|
|
new_assembled_ep->MultTranspose(xt, aepyt);
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
REQUIRE(newepyt(i) - nepyt(i) == MFEM_Approx(0.0));
|
|
REQUIRE(nepyt(i) - aepyt(i) == MFEM_Approx(0.0));
|
|
}
|
|
|
|
delete new_assembled_ep;
|
|
}
|
|
}
|
|
|
|
/// actually parallel test problem, but constraints are not
|
|
/// allowed to cross processor boundaries
|
|
class ParallelTestProblemTwo
|
|
{
|
|
public:
|
|
ParallelTestProblemTwo();
|
|
~ParallelTestProblemTwo();
|
|
|
|
void Schur(Vector& serr, Vector& lerr);
|
|
void Penalty(double pen, Vector& serr, Vector& lerr);
|
|
void Elimination(Vector& serr, Vector& lerr);
|
|
|
|
// private:
|
|
SparseMatrix Alocal;
|
|
SparseMatrix * Blocal;
|
|
Vector rhs, sol, truesol, lambda, truelambda;
|
|
HypreParMatrix * amat;
|
|
HypreParMatrix * bmat;
|
|
};
|
|
|
|
|
|
ParallelTestProblemTwo::ParallelTestProblemTwo()
|
|
:
|
|
Alocal(2), rhs(2), sol(2), truesol(2), lambda(0), truelambda(0)
|
|
{
|
|
int rank;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
|
|
Alocal.Add(0, 0, 1.0);
|
|
Alocal.Add(1, 1, 1.0);
|
|
Alocal.Finalize();
|
|
|
|
HYPRE_BigInt row_starts_a[2] = {2 * rank, 2 * (rank + 1)};
|
|
amat = new HypreParMatrix(MPI_COMM_WORLD, 8, row_starts_a, &Alocal);
|
|
amat->CopyRowStarts();
|
|
|
|
int blocalrows = rank == 3 ? 1 : 0;
|
|
Blocal = new SparseMatrix(blocalrows, 2);
|
|
HYPRE_BigInt row_starts_b[2];
|
|
if (rank == 3)
|
|
{
|
|
truelambda.SetSize(1);
|
|
lambda.SetSize(1);
|
|
Blocal->Add(0, 0, 1.0);
|
|
Blocal->Add(0, 1, 1.0);
|
|
row_starts_b[0] = 0;
|
|
row_starts_b[1] = 1;
|
|
}
|
|
else
|
|
{
|
|
row_starts_b[0] = 0;
|
|
row_starts_b[1] = 0;
|
|
}
|
|
Blocal->Finalize();
|
|
HYPRE_BigInt col_starts[2] = { 2*rank, 2 * (rank + 1) };
|
|
|
|
bmat = new HypreParMatrix(MPI_COMM_WORLD, 1, 8, row_starts_b, col_starts,
|
|
Blocal);
|
|
bmat->CopyRowStarts();
|
|
bmat->CopyColStarts();
|
|
|
|
sol = 0.0;
|
|
rhs = 0.0;
|
|
if (rank == 0)
|
|
{
|
|
rhs(0) = 1.1;
|
|
truesol(0) = 1.1;
|
|
rhs(1) = -2.0;
|
|
truesol(1) = -2.0;
|
|
}
|
|
else if (rank == 1)
|
|
{
|
|
rhs(0) = 3.0;
|
|
truesol(0) = 3.0;
|
|
rhs(1) = -1.4;
|
|
truesol(1) = -1.4;
|
|
}
|
|
else if (rank == 2)
|
|
{
|
|
rhs(0) = 2.1;
|
|
truesol(0) = 2.1;
|
|
rhs(1) = -3.2;
|
|
truesol(1) = -3.2;
|
|
}
|
|
else if (rank == 3)
|
|
{
|
|
rhs(0) = -1.1;
|
|
truesol(0) = -1.65;
|
|
rhs(1) = 2.2;
|
|
truesol(1) = 1.65;
|
|
truelambda(0) = 0.55;
|
|
}
|
|
else
|
|
{
|
|
mfem_error("Test only works on 4 ranks!");
|
|
}
|
|
}
|
|
|
|
ParallelTestProblemTwo::~ParallelTestProblemTwo()
|
|
{
|
|
delete amat;
|
|
delete bmat;
|
|
delete Blocal;
|
|
}
|
|
|
|
void ParallelTestProblemTwo::Schur(Vector& serr, Vector& lerr)
|
|
{
|
|
IdentitySolver prec(2);
|
|
SchurConstrainedSolver solver(MPI_COMM_WORLD, *amat, *bmat, prec);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
for (int i = 0; i < truesol.Size(); ++i)
|
|
{
|
|
serr(i) = truesol(i) - sol(i);
|
|
}
|
|
for (int i = 0; i < truelambda.Size(); ++i)
|
|
{
|
|
lerr(i) = truelambda(i) - lambda(i);
|
|
}
|
|
}
|
|
|
|
void ParallelTestProblemTwo::Elimination(Vector& serr, Vector& lerr)
|
|
{
|
|
int rank;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
Array<int> lagrange_rowstarts(2);
|
|
lagrange_rowstarts[0] = 0;
|
|
lagrange_rowstarts[1] = 0;
|
|
if (rank == 3)
|
|
{
|
|
lagrange_rowstarts[1] = 1;
|
|
}
|
|
EliminationCGSolver solver(*amat, *Blocal, lagrange_rowstarts);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
for (int i = 0; i < truesol.Size(); ++i)
|
|
{
|
|
serr(i) = truesol(i) - sol(i);
|
|
}
|
|
for (int i = 0; i < truelambda.Size(); ++i)
|
|
{
|
|
lerr(i) = truelambda(i) - lambda(i);
|
|
}
|
|
}
|
|
|
|
void ParallelTestProblemTwo::Penalty(double pen, Vector& serr, Vector& lerr)
|
|
{
|
|
int rank;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
|
|
PenaltyPCGSolver solver(*amat, *bmat, pen);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
for (int i = 0; i < truesol.Size(); ++i)
|
|
{
|
|
serr(i) = truesol(i) - sol(i);
|
|
}
|
|
for (int i = 0; i < truelambda.Size(); ++i)
|
|
{
|
|
lerr(i) = truelambda(i) - lambda(i);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ParallelConstrainedSolverTwo", "[Parallel], [ConstrainedSolver]")
|
|
{
|
|
if (HypreUsingGPU())
|
|
{
|
|
mfem::out << "\nAs of mfem-4.3 and hypre-2.22.0 (July 2021) this unit test\n"
|
|
<< "is NOT supported with the GPU version of hypre.\n\n";
|
|
return;
|
|
}
|
|
|
|
int comm_rank, comm_size;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank);
|
|
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
|
|
|
|
if (comm_size == 4)
|
|
{
|
|
int lsize = comm_rank == 3 ? 1 : 0;
|
|
Vector serr(2), lerr(lsize);
|
|
ParallelTestProblemTwo problem;
|
|
|
|
problem.Schur(serr, lerr);
|
|
double serrnorm = serr.Norml2();
|
|
INFO("[" << comm_rank << "] Parallel Schur primal error: " << serrnorm << "\n");
|
|
REQUIRE(serrnorm == MFEM_Approx(0.0));
|
|
if (comm_rank == 3)
|
|
{
|
|
INFO("[" << comm_rank << "] Parallel Schur dual error: " << lerr(0) << "\n");
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
}
|
|
|
|
problem.Elimination(serr, lerr);
|
|
serrnorm = serr.Norml2();
|
|
INFO("[" << comm_rank << "] Parallel Elimination primal error: " << serrnorm <<
|
|
"\n");
|
|
REQUIRE(serrnorm == MFEM_Approx(0.0));
|
|
if (comm_rank == 3)
|
|
{
|
|
INFO("[" << comm_rank << "] Parallel Elimination dual error: " << lerr(
|
|
0) << "\n");
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0));
|
|
}
|
|
|
|
for (auto pen : {1.e+3, 1.e+4, 1.e+6})
|
|
{
|
|
problem.Penalty(pen, serr, lerr);
|
|
serrnorm = serr.Norml2();
|
|
INFO("Parallel penalty primal error: " << serrnorm << "\n");
|
|
REQUIRE(serrnorm == MFEM_Approx(0.0, 2./pen));
|
|
if (comm_rank == 3)
|
|
{
|
|
INFO("Parallel penalty dual error: " << lerr(0) << "\n");
|
|
REQUIRE(lerr(0) == MFEM_Approx(0.0, 2./pen));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure EliminationCGSolver correctly handles explicit
|
|
// zeros in the constraint matrix
|
|
class ZerosTestProblem
|
|
{
|
|
public:
|
|
ZerosTestProblem(bool e0, bool e1);
|
|
~ZerosTestProblem();
|
|
|
|
void Elimination(Vector& serr, Vector& lerr, bool twoblocks);
|
|
|
|
private:
|
|
SparseMatrix A, B;
|
|
HypreParMatrix * hA;
|
|
Vector rhs, sol, dualrhs, lambda;
|
|
Vector truesol, truelambda;
|
|
};
|
|
|
|
ZerosTestProblem::ZerosTestProblem(bool e0, bool e1)
|
|
:
|
|
A(3, 3), B(2, 3), rhs(3), sol(3), dualrhs(2), lambda(2),
|
|
truesol(3), truelambda(2)
|
|
{
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
A.Add(i, i, 1.0);
|
|
}
|
|
A.Finalize();
|
|
if (e0) { B.Add(0, 1, 0.0); }
|
|
B.Add(0, 2, 1.0);
|
|
B.Add(1, 1, 1.0);
|
|
if (e1) { B.Add(1, 2, 0.0); }
|
|
B.Finalize(0); // do not skip zeros!
|
|
|
|
HYPRE_BigInt row_starts[2] = {0, 3};
|
|
hA = new HypreParMatrix(MPI_COMM_WORLD, 3, row_starts, &A);
|
|
hA->CopyRowStarts();
|
|
|
|
// this solution is pretty boring
|
|
// (this problem is pretty boring)
|
|
sol = 0.0;
|
|
rhs = 0.0;
|
|
rhs(0) = 1.0;
|
|
|
|
dualrhs = 0.0;
|
|
|
|
truesol = 0.0;
|
|
truesol(0) = 1.0;
|
|
|
|
truelambda = 0.0;
|
|
}
|
|
|
|
ZerosTestProblem::~ZerosTestProblem()
|
|
{
|
|
delete hA;
|
|
}
|
|
|
|
void ZerosTestProblem::Elimination(Vector& serr, Vector& lerr, bool twoblocks)
|
|
{
|
|
Array<int> lagrange_rowstarts;
|
|
if (twoblocks)
|
|
{
|
|
lagrange_rowstarts.SetSize(3);
|
|
lagrange_rowstarts[0] = 0;
|
|
lagrange_rowstarts[1] = 1;
|
|
lagrange_rowstarts[2] = 2;
|
|
}
|
|
else
|
|
{
|
|
lagrange_rowstarts.SetSize(2);
|
|
lagrange_rowstarts[0] = 0;
|
|
lagrange_rowstarts[1] = 2;
|
|
}
|
|
EliminationCGSolver solver(*hA, B, lagrange_rowstarts);
|
|
solver.Mult(rhs, sol);
|
|
solver.GetMultiplierSolution(lambda);
|
|
for (int i = 0; i < truesol.Size(); ++i)
|
|
{
|
|
serr(i) = truesol(i) - sol(i);
|
|
}
|
|
for (int i = 0; i < truelambda.Size(); ++i)
|
|
{
|
|
lerr(i) = truelambda(i) - lambda(i);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ZerosTestCase", "[Parallel], [ConstrainedSolver]")
|
|
{
|
|
if (HypreUsingGPU())
|
|
{
|
|
mfem::out << "\nAs of mfem-4.3 and hypre-2.22.0 (July 2021) this unit test\n"
|
|
<< "is NOT supported with the GPU version of hypre.\n\n";
|
|
return;
|
|
}
|
|
|
|
int comm_rank, comm_size;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank);
|
|
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
|
|
|
|
if (comm_size == 1)
|
|
{
|
|
Vector serr(3), lerr(2);
|
|
auto e0 = GENERATE(true, false);
|
|
auto e1 = GENERATE(true, false);
|
|
ZerosTestProblem problem(e0, e1);
|
|
|
|
auto twoblocks = GENERATE(true, false);
|
|
problem.Elimination(serr, lerr, twoblocks);
|
|
double serrnorm = serr.Norml2();
|
|
INFO("[" << comm_rank << "] zeros test case primal error: " << serrnorm <<
|
|
"\n");
|
|
REQUIRE(serrnorm == MFEM_Approx(0.0));
|
|
double lerrnorm = lerr.Norml2();
|
|
INFO("[" << comm_rank << "] zeros test case dual error: " << lerrnorm << "\n");
|
|
REQUIRE(lerrnorm == MFEM_Approx(0.0));
|
|
}
|
|
}
|
|
|
|
#endif
|