// 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 "coupledop.hpp" namespace mfem { CoupledOperator::CoupledOperator(const Array &bdr_u_is_ess, const Array &bdr_E_is_ess, Coefficient *sigma_, const Array &lfs_, const Array &coeffs_, FiniteElementSpace *u_space_, FiniteElementSpace *n_space_, FiniteElementSpace *E_space_, FiniteElementSpace *B_space_, FiniteElementSpace *tr_space_, real_t td) : TimeDependentOperator(0, IMPLICIT), sigma(sigma_), lfs(lfs_), coeffs(coeffs_), u_space(u_space_), n_space(n_space_), E_space(E_space_), B_space(B_space_), tr_space(tr_space_) { offsets = ConstructOffsets(u_space, n_space, E_space, B_space, tr_space); width = height = offsets.Last(); //plasma const int dim = u_space->GetMesh()->Dimension(); const bool fec_disc = (u_space->FEColl()->GetContType() == FiniteElementCollection::DISCONTINUOUS); const bool fec_vec = (u_space->FEColl()->GetRangeType(dim) == FiniteElement::VECTOR); const bool dg = (fec_disc && !fec_vec); const bool brt = (fec_disc && fec_vec); if (!dg) { u_space->GetEssentialTrueDofs(bdr_u_is_ess, ess_u_tdofs_list); } darcy = new DarcyForm(u_space, n_space); Mu = darcy->GetFluxMassForm(); Mn = darcy->GetPotentialMassForm(); Du = darcy->GetFluxDivForm(); idtcoeff = new FunctionCoefficient([&](const Vector &) { return idt; }); dtcoeff = new FunctionCoefficient([&](const Vector &) { return 1./idt; }); if (dg) { Mu->AddDomainIntegrator(new VectorMassIntegrator(*idtcoeff)); } else { Mu->AddDomainIntegrator(new VectorFEMassIntegrator(idtcoeff)); } if (dg) { Du->AddDomainIntegrator(new VectorDivergenceIntegrator()); Du->AddInteriorFaceIntegrator(new TransposeIntegrator( new DGNormalTraceIntegrator(-1.))); Du->AddBdrFaceIntegrator(new TransposeIntegrator(new DGNormalTraceIntegrator( -1.)), const_cast&>(bdr_u_is_ess)); } else { Du->AddDomainIntegrator(new VectorFEDivergenceIntegrator()); if (brt) { Du->AddInteriorFaceIntegrator(new TransposeIntegrator( new DGNormalTraceIntegrator(-1.))); Du->AddBdrFaceIntegrator(new TransposeIntegrator(new DGNormalTraceIntegrator( -1.)), const_cast&>(bdr_u_is_ess)); } } if (dg && td > 0.) { Mn->AddInteriorFaceIntegrator(new HDGDiffusionIntegrator(*dtcoeff, td)); Mn->AddBdrFaceIntegrator(new HDGDiffusionIntegrator(*dtcoeff, td), const_cast&>(bdr_u_is_ess)); } Mn->AddDomainIntegrator(new MassIntegrator(*idtcoeff)); if (tr_space) { darcy->EnableHybridization(tr_space, new NormalTraceJumpIntegrator(), ess_u_tdofs_list); } //Maxwell E_space->GetEssentialTrueDofs(bdr_E_is_ess, ess_E_tdofs_list); ME = new BilinearForm(E_space); ME->AddDomainIntegrator(new VectorFEMassIntegrator()); CE = new MixedBilinearForm(E_space, B_space); CE->AddDomainIntegrator(new MixedCurlIntegrator()); CdE = new DiscreteLinearOperator(E_space, B_space); CdE->AddDomainInterpolator(new CurlInterpolator()); ME->Assemble(); ME->Finalize(); CE->Assemble(); CE->Finalize(); CdE->Assemble(); CdE->Finalize(); } CoupledOperator::~CoupledOperator() { delete ME; delete CE; delete CdE; delete darcy; delete idtcoeff; delete dtcoeff; } Array CoupledOperator::ConstructOffsets(const FiniteElementSpace *u_space, const FiniteElementSpace *n_space, const FiniteElementSpace *E_space, const FiniteElementSpace *B_space, const FiniteElementSpace *trace_space) { Array offsets((trace_space)?(6):(5)); int i = 0; offsets[i++] = 0; offsets[i++] = u_space->GetVSize(); offsets[i++] = n_space->GetVSize(); if (trace_space) { offsets[i++] = trace_space->GetVSize(); } offsets[i++] = E_space->GetVSize(); offsets[i++] = B_space->GetVSize(); offsets.PartialSum(); return offsets; } void CoupledOperator::ImplicitSolve(const double dt, const Vector &x, Vector &y) { const bool time_track = false; BlockVector bx(const_cast(x), offsets); BlockVector by(y, offsets); int i = 0; const Vector &un = bx.GetBlock(i++); const Vector &nn = bx.GetBlock(i++); if (tr_space) { i++; } const Vector &En = bx.GetBlock(i++); const Vector &Bn = bx.GetBlock(i++); i = 2; //Vector &u = by.GetBlock(i++); //Vector &n = by.GetBlock(i++); if (tr_space) { i++; } Vector &E = by.GetBlock(i++); Vector &B = by.GetBlock(i++); //offsets Array X_offsets(3); X_offsets[0] = 0; if (tr_space) { X_offsets[1] = tr_space->GetVSize(); } else { X_offsets[1] = darcy->Width(); } X_offsets[2] = E_space->GetVSize(); X_offsets.PartialSum(); //solution & rhs vectors BlockVector X(X_offsets), RHS(X_offsets); BlockDiagonalPreconditioner bprec(X_offsets); bprec.owns_blocks = true; //plasma LinearForm *g = lfs[0]; LinearForm *f = lfs[1]; LinearForm *h = lfs[2]; //set time for (Coefficient *coeff : coeffs) { coeff->SetTime(t); } //assemble rhs StopWatch chrono; if (time_track) { chrono.Clear(); chrono.Start(); } g->Assemble(); f->Assemble(); if (h) { h->Assemble(); } //check if the operator has to be reassembled bool reassemble = (idt != 1./dt); if (reassemble) { idt = 1./dt; //reset the operator darcy->Update(); //assemble the system darcy->Assemble(); if (Mu && tr_space) { Mu->Update(); Mu->Assemble(); //Mq0->Finalize(); } if (Mn && tr_space) { Mn->Update(); Mn->Assemble(); //Mt0->Finalize(); } } if (Mu) { GridFunction u_h; u_h.MakeRef(darcy->FluxFESpace(), const_cast(un), 0); Mu->AddMult(u_h, *g, +1.); } if (Mn) { GridFunction p_h; p_h.MakeRef(darcy->PotentialFESpace(), const_cast(nn), 0); Mn->AddMult(p_h, *f, -1.); } //form the reduced system OperatorHandle op_pl; BlockVector darcy_x(const_cast(x), darcy->GetOffsets()); BlockVector darcy_rhs(g->GetData(), darcy->GetOffsets()); BlockVector X_pl; BlockVector RHS_pl; Array tr_offsets(2); if (tr_space) { tr_offsets[0] = 0; tr_offsets[1] = tr_space->GetVSize(); X_pl.Update(X, tr_offsets); RHS_pl.Update(RHS, tr_offsets); if (h) { RHS_pl.Vector::operator=(*h); } else { RHS_pl = 0.; } darcy->FormLinearSystem(ess_u_tdofs_list, darcy_x, darcy_rhs, op_pl, X_pl, RHS_pl); } else { X_pl.Update(X, darcy->GetOffsets()); RHS_pl.Update(RHS, darcy->GetOffsets()); X_pl = darcy_x; RHS_pl = darcy_rhs; BlockVector darcy_x_tmp(X_pl, darcy->GetOffsets()); BlockVector darcy_rhs_tmp(RHS_pl, darcy->GetOffsets()); darcy->FormLinearSystem(ess_u_tdofs_list, darcy_x_tmp, darcy_rhs_tmp, op_pl, X_pl, RHS_pl); } if (time_track) { chrono.Stop(); std::cout << "Assembly took " << chrono.RealTime() << "s.\n"; } if (tr_space) { bprec.SetDiagonalBlock(0, new DSmoother(*op_pl.As())); } else { BlockDiagonalPreconditioner *darcy_prec = new BlockDiagonalPreconditioner( darcy->GetOffsets()); darcy_prec->owns_blocks = true;; darcy_prec->SetDiagonalBlock(0, new GSSmoother(Mu->SpMat())); darcy_prec->SetDiagonalBlock(1, new DSmoother(Mn->SpMat())); bprec.SetDiagonalBlock(0, darcy_prec); } //Maxwell TransposeOperator Ct(CE); ProductOperator CtCd(&Ct, CdE, false, false); SumOperator MECtCd(ME, 1., &CtCd, dt*dt, false, false); Vector &X_max = X.GetBlock(1); X_max = En; Vector &RHS_max = RHS.GetBlock(1); ME->Mult(En, RHS_max); //MECtCd.Mult(En, rhs); CE->AddMultTranspose(Bn, RHS_max, dt); //CtCd.AddMult(En, rhs, +dt*dt/2.); //ME - dt*c2/2*Ct(Bn-dt*CE) = MEn + dt*c2/2CtBn //(M + dt2*c2/2*Ct*C)E = MEn + dt*c2*CtBn //ME - dt*c2*Ct(Bn-dt/2*CE-dt/2*CEn) = MEn //(M + dt2*c2/2*Ct*C)E = MEn + dt*c2CtBn - dt2*c2/2*CtCEn //-1/dt B - CE = 0 //ME->EliminateVDofsInRHS(ess_tdof_list, En, rhs); ConstrainedOperator op_max(&MECtCd, ess_E_tdofs_list); //op_max.EliminateRHS(En, RHS_max); bprec.SetDiagonalBlock(1, new DSmoother(ME->SpMat())); //coupling /*const Mesh *mesh = n_space->GetMesh(); const int NE = mesh->GetNE(); for (int el = 0; el < NE; el++) { }*/ ReducedOperator bop(sigma, darcy, E_space, *op_pl.Ptr(), op_max); if (tr_space) { Array ess_tr_tdofs_list;//dummy bop.SetEssentialTDOFs(ess_tr_tdofs_list, ess_E_tdofs_list); bop.SetDarcyRHS(darcy_rhs); } else { bop.SetEssentialTDOFs(ess_u_tdofs_list, ess_E_tdofs_list); } bop.EliminateRHS(X, RHS); //solve GMRESSolver solver; solver.SetMaxIter(1000); solver.SetAbsTol(0.); solver.SetRelTol(1e-6); solver.SetOperator(bop); solver.SetPreconditioner(bprec); solver.SetPrintLevel(0); NewtonSolver newton; newton.SetMaxIter(1000); newton.SetAbsTol(0.); newton.SetRelTol(1e-5); newton.SetOperator(bop); newton.SetSolver(solver); newton.SetPrintLevel(1); if (time_track) { chrono.Clear(); chrono.Start(); } newton.Mult(RHS, X); if (time_track) { chrono.Stop(); } if (newton.GetConverged()) { std::cout << " converged in " << newton.GetNumIterations() << " iterations with a residual norm of " << newton.GetFinalNorm() << ".\n"; } else { std::cout << " did not converge in " << newton.GetNumIterations() << " iterations. Residual norm is " << newton.GetFinalNorm() << ".\n"; } if (time_track) { std::cout << "Solver took " << chrono.RealTime() << "s.\n"; } //recover solution BlockVector darcy_y(y, darcy->GetOffsets()); darcy_y = darcy_x; if (tr_space) { darcy->RecoverFEMSolution(X_pl, darcy_rhs, darcy_y); Vector &darcy_tr = by.GetBlock(2); darcy_tr = X_pl; darcy_tr -= bx.GetBlock(2); darcy_tr *= idt; } else { darcy->RecoverFEMSolution(X_pl, RHS_pl, X_pl); darcy_y = X_pl; } darcy_y -= darcy_x; darcy_y *= idt; E = X_max; E -= En; E *= idt; CdE->Mult(En, B); //CdE->AddMult(E, B); //B *= -1./2.; B.Neg(); } CoupledOperator::ReducedOperator::ReducedOperator(Coefficient *sigma_, DarcyForm *darcy_, FiniteElementSpace *fes_E_, Operator &pl, Operator &max) : sigma(sigma_), darcy(darcy_), fes_E(fes_E_) { offsets.SetSize(3); offsets[0] = 0; offsets[1] = pl.Width(); offsets[2] = max.Width(); offsets.PartialSum(); width = height = offsets.Last(); if (darcy->GetHybridization()) { offsets_x = offsets; } else { offsets_x.SetSize(4); offsets_x[0] = 0; offsets_x[1] = darcy->FluxFESpace()->GetVSize(); offsets_x[2] = darcy->PotentialFESpace()->GetVSize(); offsets_x[3] = fes_E->GetVSize(); offsets_x.PartialSum(); } BlockOperator *bop = new BlockOperator(offsets); bop->SetDiagonalBlock(0, &pl); bop->SetDiagonalBlock(1, &max); op.Reset(bop); } CoupledOperator::ReducedOperator::~ReducedOperator() { } void CoupledOperator::ReducedOperator::SetEssentialTDOFs( const Array &u_tdofs_list, const Array &E_tdofs_list) { ess_tdofs_list.DeleteAll(); ess_tdofs_list.Append(u_tdofs_list); ess_tdofs_list.Append(E_tdofs_list); const int size = ess_tdofs_list.Size(); for (int i = 0; i < E_tdofs_list.Size(); i++) { int &tdof = ess_tdofs_list[size - 1 - i]; if (tdof >= 0) { tdof += offsets[1]; } else { tdof -= offsets[1]; } } } void CoupledOperator::ReducedOperator::EliminateRHS(const Vector &x, Vector &b) const { if (ess_tdofs_list.Size() <= 0) { return; } Vector w(x.Size()), z(b.Size()); w = 0.; for (int tdof : ess_tdofs_list) { w(tdof) = x(tdof); } MultUnconstrained(w, z); b -= z; for (int tdof : ess_tdofs_list) { b(tdof) = x(tdof); } } void CoupledOperator::ReducedOperator::Mult(const Vector &x, Vector &y) const { Vector z(x.Size()); z = x; for (int tdof : ess_tdofs_list) { z(tdof) = 0.; } MultUnconstrained(z, y); for (int tdof : ess_tdofs_list) { y(tdof) = x(tdof); } } void CoupledOperator::ReducedOperator::MultUnconstrained(const Vector &x, Vector &y) const { op->Mult(x, y); const bool hybr = darcy->GetHybridization() != NULL; BlockVector bx(const_cast(x), offsets_x); BlockVector by(const_cast(y), offsets_x); BlockVector darcy_x; if (hybr) { darcy_x.Update(darcy->GetOffsets()); darcy->GetHybridization()->ComputeSolution(*darcy_rhs, x, darcy_x); darcy_rhs->Vector::operator=(darcy_rhs_lin); } const Vector &xn = ((hybr)?(darcy_x):(bx)).GetBlock(1); const Vector &xE = bx.GetBlock((hybr)?(1):(2)); Vector &yu = ((hybr)?(*darcy_rhs):(by)).GetBlock(0); Vector &yE = by.GetBlock((hybr)?(1):(2)); Mesh *mesh = fes_E->GetMesh(); FiniteElementSpace *fes_u = darcy->FluxFESpace(); FiniteElementSpace *fes_n = darcy->PotentialFESpace(); Array vdofs_u, dofs_n, vdofs_E; DenseMatrix vshape_u, vshape_E; Vector shape_u, shape_E, shape_n, n_z, E_z, E, bu_z, bE_z; for (int z = 0; z < mesh->GetNE(); z++) { ElementTransformation *Tr = mesh->GetElementTransformation(z); const FiniteElement *fe_u = fes_u->GetFE(z); const FiniteElement *fe_n = fes_n->GetFE(z); const FiniteElement *fe_E = fes_E->GetFE(z); const int sdim = Tr->GetSpaceDim(); const int ndof_u = fe_u->GetDof(); const int ndof_n = fe_n->GetDof(); fes_u->GetElementVDofs(z, vdofs_u); fes_n->GetElementDofs(z, dofs_n); fes_E->GetElementVDofs(z, vdofs_E); if (fe_u->GetRangeType() == FiniteElement::VECTOR) { vshape_u.SetSize(vdofs_u.Size(), sdim); } shape_u.SetSize(ndof_u); shape_n.SetSize(ndof_n); vshape_E.SetSize(vdofs_E.Size(), sdim); shape_E.SetSize(vdofs_E.Size()); E.SetSize(sdim); bu_z.SetSize(vdofs_u.Size()); bE_z.SetSize(vdofs_E.Size()); bu_z = 0.; bE_z = 0.; xn.GetSubVector(dofs_n, n_z); xE.GetSubVector(vdofs_E, E_z); const int order = std::max(fe_E->GetOrder(), fe_n->GetOrder()) * 2 + 1; const IntegrationRule &ir = IntRules.Get(fe_n->GetGeomType(), order); for (int q = 0; q < ir.GetNPoints(); q++) { const IntegrationPoint &ip = ir.IntPoint(q); Tr->SetIntPoint(&ip); fe_n->CalcShape(ip, shape_n); const real_t n = n_z * shape_n; fe_E->CalcVShape(*Tr, vshape_E); vshape_E.MultTranspose(E_z, E); real_t w = n * Tr->Weight(); if (sigma) { w *= sigma->Eval(*Tr, ip); } if (fe_u->GetRangeType() == FiniteElement::VECTOR) { fe_u->CalcVShape(*Tr, vshape_u); vshape_u.Mult(E, shape_u); bu_z.Add(w, shape_u); } else { fe_u->CalcShape(ip, shape_u); for (int d = 0; d < sdim; d++) for (int i = 0; i < ndof_u; i++) { bu_z(i+d*ndof_u) += w * E(d) * shape_u(i); } } vshape_E.Mult(E, shape_E); bE_z.Add(w, shape_E); } if (hybr) { bu_z.Neg(); } yu.AddElementVector(vdofs_u, bu_z); yE.AddElementVector(vdofs_E, bE_z); } if (hybr) { BlockVector darcy_Xrhs(darcy->GetOffsets()); add(darcy_rhs_lin, -1., *darcy_rhs, darcy_Xrhs); darcy->GetHybridization()->ReduceRHS(darcy_Xrhs, by.GetBlock(0)); } } Operator &CoupledOperator::ReducedOperator::GetGradient(const Vector &x) const { if (darcy->GetHybridization()) { //TODO: coupling terms return const_cast(*op); } grad.Clear(); FiniteElementSpace *fes_u = darcy->FluxFESpace(); FiniteElementSpace *fes_n = darcy->PotentialFESpace(); SparseMatrix *BEE, *BEu, *BnE, *Bnu; BEE = new SparseMatrix(fes_E->GetVSize()); BEu = new SparseMatrix(fes_u->GetVSize(), fes_E->GetVSize()); BnE = new SparseMatrix(fes_E->GetVSize(), fes_n->GetVSize()); Bnu = new SparseMatrix(fes_u->GetVSize(), fes_n->GetVSize()); BlockVector bx(const_cast(x), offsets_x); Mesh *mesh = fes_E->GetMesh(); Array vdofs_u, dofs_n, vdofs_E; DenseMatrix vshape_u, vshape_E; DenseMatrix BEE_z, BEu_z, BnE_z, Bnu_z; Vector shape_u, shape_E, shape_n, n_z, E_z, E; for (int z = 0; z < mesh->GetNE(); z++) { ElementTransformation *Tr = mesh->GetElementTransformation(z); const FiniteElement *fe_u = fes_u->GetFE(z); const FiniteElement *fe_n = fes_n->GetFE(z); const FiniteElement *fe_E = fes_E->GetFE(z); const int sdim = Tr->GetSpaceDim(); const int ndof_u = fe_u->GetDof(); const int ndof_n = fe_n->GetDof(); fes_u->GetElementVDofs(z, vdofs_u); fes_n->GetElementDofs(z, dofs_n); fes_E->GetElementVDofs(z, vdofs_E); if (fe_u->GetRangeType() == FiniteElement::VECTOR) { vshape_u.SetSize(vdofs_u.Size(), sdim); } shape_u.SetSize(ndof_u); shape_n.SetSize(ndof_n); vshape_E.SetSize(vdofs_E.Size(), sdim); shape_E.SetSize(vdofs_E.Size()); E.SetSize(sdim); BEE_z.SetSize(vdofs_E.Size()); BEu_z.SetSize(vdofs_u.Size(), vdofs_E.Size()); BnE_z.SetSize(vdofs_E.Size(), dofs_n.Size()); Bnu_z.SetSize(vdofs_u.Size(), dofs_n.Size()); BEE_z = 0.; BEu_z = 0.; BnE_z = 0.; Bnu_z = 0.; bx.GetBlock(1).GetSubVector(dofs_n, n_z); bx.GetBlock(2).GetSubVector(vdofs_E, E_z); const int order = std::max(fe_E->GetOrder(), fe_n->GetOrder()) * 2 + 1; const IntegrationRule &ir = IntRules.Get(fe_n->GetGeomType(), order); for (int q = 0; q < ir.GetNPoints(); q++) { const IntegrationPoint &ip = ir.IntPoint(q); Tr->SetIntPoint(&ip); fe_n->CalcShape(ip, shape_n); const real_t n = n_z * shape_n; fe_E->CalcVShape(*Tr, vshape_E); vshape_E.MultTranspose(E_z, E); real_t w = Tr->Weight(); if (sigma) { w *= sigma->Eval(*Tr, ip); } if (fe_u->GetRangeType() == FiniteElement::VECTOR) { fe_u->CalcVShape(*Tr, vshape_u); vshape_u.Mult(E, shape_u); AddMult_a_VWt(w, shape_u, shape_n, Bnu_z); AddMult_a_ABt(w * n, vshape_u, vshape_E, BEu_z); } else { fe_u->CalcShape(ip, shape_u); for (int d = 0; d < sdim; d++) for (int i = 0; i < ndof_u; i++) { for (int j = 0; j < ndof_n; j++) { Bnu_z(i+d*ndof_u, j) += w * E(d) * shape_u(i) * shape_n(j); } for (int j = 0; j < vdofs_E.Size(); j++) { BEu_z(i+d*ndof_u, j) += w * n * shape_u(i) * vshape_E(j, d); } } } vshape_E.Mult(E, shape_E); AddMult_a_AAt(w * n, vshape_E, BEE_z); AddMult_a_VWt(w, shape_E, shape_n, BnE_z); } BEE->AddSubMatrix(vdofs_E, vdofs_E, BEE_z); BEu->AddSubMatrix(vdofs_u, vdofs_E, BEu_z); BnE->AddSubMatrix(vdofs_E, dofs_n, BnE_z); Bnu->AddSubMatrix(vdofs_u, dofs_n, Bnu_z); } BEE->Finalize(); BEu->Finalize(); BnE->Finalize(); Bnu->Finalize(); BlockOperator *bcouple = new BlockOperator(offsets_x); bcouple->owns_blocks = true; bcouple->SetBlock(2, 2, BEE); bcouple->SetBlock(0, 2, BEu); bcouple->SetBlock(2, 1, BnE); bcouple->SetBlock(0, 1, Bnu); grad.Reset(new SumOperator(op.Ptr(), 1., bcouple, 1., false, true)); if (ess_tdofs_list.Size() > 0) { grad.SetOperatorOwner(false); grad.Reset(new ConstrainedOperator(grad.Ptr(), ess_tdofs_list)); } return *grad; } }