ComponentTransportProcess.cpp
/**
* \file
* \copyright
* Copyright (c) 2012-2023, OpenGeoSys Community (http://www.opengeosys.org)
* Distributed under a Modified BSD License.
* See accompanying file LICENSE.txt or
* http://www.opengeosys.org/project/license
*
*/
#include "ComponentTransportProcess.h"
#include <cassert>
#include <range/v3/view/drop.hpp>
#include "BaseLib/RunTime.h"
#include "ChemistryLib/ChemicalSolverInterface.h"
#include "MathLib/LinAlg/Eigen/EigenTools.h"
#include "MathLib/LinAlg/FinalizeMatrixAssembly.h"
#include "MathLib/LinAlg/FinalizeVectorAssembly.h"
#include "MathLib/LinAlg/LinAlg.h"
#include "MeshLib/Utils/getOrCreateMeshProperty.h"
#include "NumLib/DOF/ComputeSparsityPattern.h"
#include "ProcessLib/SurfaceFlux/SurfaceFlux.h"
#include "ProcessLib/SurfaceFlux/SurfaceFluxData.h"
#include "ProcessLib/Utils/ComputeResiduum.h"
#include "ProcessLib/Utils/CreateLocalAssemblers.h"
namespace ProcessLib
{
namespace ComponentTransport
{
ComponentTransportProcess::ComponentTransportProcess(
std::string name,
MeshLib::Mesh& mesh,
std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&& jacobian_assembler,
std::vector<std::unique_ptr<ParameterLib::ParameterBase>> const& parameters,
unsigned const integration_order,
std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>&&
process_variables,
ComponentTransportProcessData&& process_data,
SecondaryVariableCollection&& secondary_variables,
bool const use_monolithic_scheme,
std::unique_ptr<ProcessLib::SurfaceFluxData>&& surfaceflux,
std::unique_ptr<ChemistryLib::ChemicalSolverInterface>&&
chemical_solver_interface)
: Process(std::move(name), mesh, std::move(jacobian_assembler), parameters,
integration_order, std::move(process_variables),
std::move(secondary_variables), use_monolithic_scheme),
_process_data(std::move(process_data)),
_surfaceflux(std::move(surfaceflux)),
_chemical_solver_interface(std::move(chemical_solver_interface))
{
_residua.push_back(MeshLib::getOrCreateMeshProperty<double>(
mesh, "LiquidMassFlowRate", MeshLib::MeshItemType::Node, 1));
if (_use_monolithic_scheme)
{
const int process_id = 0;
for (auto const& pv :
_process_variables[process_id] | ranges::views::drop(1))
{
_residua.push_back(MeshLib::getOrCreateMeshProperty<double>(
mesh, pv.get().getName() + "FlowRate",
MeshLib::MeshItemType::Node, 1));
}
}
else
{
for (auto const& pv : _process_variables | ranges::views::drop(1))
{
_residua.push_back(MeshLib::getOrCreateMeshProperty<double>(
mesh, pv[0].get().getName() + "FlowRate",
MeshLib::MeshItemType::Node, 1));
}
}
}
void ComponentTransportProcess::initializeConcreteProcess(
NumLib::LocalToGlobalIndexMap const& dof_table,
MeshLib::Mesh const& mesh,
unsigned const integration_order)
{
int const mesh_space_dimension = _process_data.mesh_space_dimension;
_process_data.mesh_prop_velocity = MeshLib::getOrCreateMeshProperty<double>(
const_cast<MeshLib::Mesh&>(mesh), "velocity",
MeshLib::MeshItemType::Cell, mesh_space_dimension);
_process_data.mesh_prop_porosity = MeshLib::getOrCreateMeshProperty<double>(
const_cast<MeshLib::Mesh&>(mesh), "porosity_avg",
MeshLib::MeshItemType::Cell, 1);
std::vector<std::reference_wrapper<ProcessLib::ProcessVariable>>
transport_process_variables;
if (_use_monolithic_scheme)
{
const int process_id = 0;
for (auto pv_iter = std::next(_process_variables[process_id].begin());
pv_iter != _process_variables[process_id].end();
++pv_iter)
{
transport_process_variables.push_back(*pv_iter);
}
}
else
{
for (auto pv_iter = std::next(_process_variables.begin());
pv_iter != _process_variables.end();
++pv_iter)
{
transport_process_variables.push_back((*pv_iter)[0]);
}
}
ProcessLib::createLocalAssemblers<LocalAssemblerData>(
mesh_space_dimension, mesh.getElements(), dof_table, _local_assemblers,
NumLib::IntegrationOrder{integration_order}, mesh.isAxiallySymmetric(),
_process_data, transport_process_variables);
if (_chemical_solver_interface && !_use_monolithic_scheme)
{
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::setChemicalSystemID,
_local_assemblers, _chemical_solver_interface->getElementIDs());
_chemical_solver_interface->initialize();
}
_secondary_variables.addSecondaryVariable(
"darcy_velocity",
makeExtrapolator(
mesh_space_dimension, getExtrapolator(), _local_assemblers,
&ComponentTransportLocalAssemblerInterface::getIntPtDarcyVelocity));
_secondary_variables.addSecondaryVariable(
"molar_flux",
makeExtrapolator(
mesh_space_dimension, getExtrapolator(), _local_assemblers,
&ComponentTransportLocalAssemblerInterface::getIntPtMolarFlux));
}
void ComponentTransportProcess::setInitialConditionsConcreteProcess(
std::vector<GlobalVector*>& x, double const t, int const process_id)
{
if (!_chemical_solver_interface)
{
return;
}
if (process_id != static_cast<int>(x.size() - 1))
{
return;
}
std::for_each(
x.begin(), x.end(),
[](auto const process_solution)
{ MathLib::LinAlg::setLocalAccessibleVector(*process_solution); });
std::vector<NumLib::LocalToGlobalIndexMap const*> dof_tables;
dof_tables.reserve(x.size());
std::generate_n(std::back_inserter(dof_tables), x.size(),
[&]() { return _local_to_global_index_map.get(); });
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::initializeChemicalSystem,
_local_assemblers, _chemical_solver_interface->getElementIDs(),
dof_tables, x, t);
}
void ComponentTransportProcess::assembleConcreteProcess(
const double t, double const dt, std::vector<GlobalVector*> const& x,
std::vector<GlobalVector*> const& xdot, int const process_id,
GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b)
{
DBUG("Assemble ComponentTransportProcess.");
ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
std::vector<std::reference_wrapper<NumLib::LocalToGlobalIndexMap>>
dof_tables;
if (_use_monolithic_scheme)
{
dof_tables.push_back(std::ref(*_local_to_global_index_map));
}
else
{
std::generate_n(
std::back_inserter(dof_tables), _process_variables.size(),
[&]() { return std::ref(*_local_to_global_index_map); });
}
// Call global assembler for each local assembly item.
GlobalExecutor::executeSelectedMemberDereferenced(
_global_assembler, &VectorMatrixAssembler::assemble, _local_assemblers,
pv.getActiveElementIDs(), dof_tables, t, dt, x, xdot, process_id, M, K,
b);
MathLib::finalizeMatrixAssembly(M);
MathLib::finalizeMatrixAssembly(K);
MathLib::finalizeVectorAssembly(b);
if (_use_monolithic_scheme)
{
auto const residuum = computeResiduum(*x[0], *xdot[0], M, K, b);
for (std::size_t variable_id = 0; variable_id < _residua.size();
++variable_id)
{
transformVariableFromGlobalVector(
residuum, variable_id, dof_tables[0], *_residua[variable_id],
std::negate<double>());
}
}
else
{
auto const residuum =
computeResiduum(*x[process_id], *xdot[process_id], M, K, b);
transformVariableFromGlobalVector(residuum, 0, dof_tables[process_id],
*_residua[process_id],
std::negate<double>());
}
}
void ComponentTransportProcess::assembleWithJacobianConcreteProcess(
const double t, double const dt, std::vector<GlobalVector*> const& x,
std::vector<GlobalVector*> const& xdot, int const process_id,
GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b, GlobalMatrix& Jac)
{
DBUG("AssembleWithJacobian ComponentTransportProcess.");
if (_use_monolithic_scheme)
{
OGS_FATAL(
"The AssembleWithJacobian() for ComponentTransportProcess is "
"implemented only for staggered scheme.");
}
ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
std::vector<std::reference_wrapper<NumLib::LocalToGlobalIndexMap>>
dof_tables;
std::generate_n(std::back_inserter(dof_tables), _process_variables.size(),
[&]() { return std::ref(*_local_to_global_index_map); });
// Call global assembler for each local assembly item.
GlobalExecutor::executeSelectedMemberDereferenced(
_global_assembler, &VectorMatrixAssembler::assembleWithJacobian,
_local_assemblers, pv.getActiveElementIDs(), dof_tables, t, dt, x, xdot,
process_id, M, K, b, Jac);
// b is the negated residumm used in the Newton's method.
// Here negating b is to recover the primitive residuum.
transformVariableFromGlobalVector(b, 0, *_local_to_global_index_map,
*_residua[process_id],
std::negate<double>());
}
Eigen::Vector3d ComponentTransportProcess::getFlux(
std::size_t const element_id,
MathLib::Point3d const& p,
double const t,
std::vector<GlobalVector*> const& x) const
{
std::vector<GlobalIndexType> indices_cache;
auto const r_c_indices = NumLib::getRowColumnIndices(
element_id, *_local_to_global_index_map, indices_cache);
std::vector<std::vector<GlobalIndexType>> indices_of_all_coupled_processes{
x.size(), r_c_indices.rows};
auto const local_xs =
getCoupledLocalSolutions(x, indices_of_all_coupled_processes);
return _local_assemblers[element_id]->getFlux(p, t, local_xs);
}
void ComponentTransportProcess::solveReactionEquation(
std::vector<GlobalVector*>& x, std::vector<GlobalVector*> const& x_prev,
double const t, double const dt, NumLib::EquationSystem& ode_sys,
int const process_id)
{
// todo (renchao): move chemical calculation to elsewhere.
if (_process_data.lookup_table && process_id == 0)
{
INFO("Update process solutions via the look-up table approach");
_process_data.lookup_table->lookup(x, x_prev, _mesh.getNumberOfNodes());
return;
}
if (!_chemical_solver_interface)
{
return;
}
// Sequential non-iterative approach applied here to split the reactive
// transport process into the transport stage followed by the reaction
// stage.
std::vector<NumLib::LocalToGlobalIndexMap const*> dof_tables;
dof_tables.reserve(x.size());
std::generate_n(std::back_inserter(dof_tables), x.size(),
[&]() { return _local_to_global_index_map.get(); });
if (process_id == 0)
{
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::setChemicalSystem,
_local_assemblers, _chemical_solver_interface->getElementIDs(),
dof_tables, x, t, dt);
BaseLib::RunTime time_phreeqc;
time_phreeqc.start();
_chemical_solver_interface->setAqueousSolutionsPrevFromDumpFile();
_chemical_solver_interface->executeSpeciationCalculation(dt);
INFO("[time] Phreeqc took {:g} s.", time_phreeqc.elapsed());
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::
postSpeciationCalculation,
_local_assemblers, _chemical_solver_interface->getElementIDs(), t,
dt);
return;
}
auto const matrix_specification = getMatrixSpecifications(process_id);
std::size_t matrix_id = 0u;
auto& M = NumLib::GlobalMatrixProvider::provider.getMatrix(
matrix_specification, matrix_id);
auto& K = NumLib::GlobalMatrixProvider::provider.getMatrix(
matrix_specification, matrix_id);
auto& b =
NumLib::GlobalVectorProvider::provider.getVector(matrix_specification);
M.setZero();
K.setZero();
b.setZero();
ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::assembleReactionEquation,
_local_assemblers, pv.getActiveElementIDs(), dof_tables, x, t, dt, M, K,
b, process_id);
// todo (renchao): incorporate Neumann boundary condition
MathLib::finalizeMatrixAssembly(M);
MathLib::finalizeMatrixAssembly(K);
MathLib::finalizeVectorAssembly(b);
auto& A = NumLib::GlobalMatrixProvider::provider.getMatrix(
matrix_specification, matrix_id);
auto& rhs =
NumLib::GlobalVectorProvider::provider.getVector(matrix_specification);
A.setZero();
rhs.setZero();
MathLib::finalizeMatrixAssembly(A);
MathLib::finalizeVectorAssembly(rhs);
// compute A
MathLib::LinAlg::copy(M, A);
MathLib::LinAlg::aypx(A, 1.0 / dt, K);
// compute rhs
MathLib::LinAlg::matMult(M, *x[process_id], rhs);
MathLib::LinAlg::aypx(rhs, 1.0 / dt, b);
using Tag = NumLib::NonlinearSolverTag;
using EqSys = NumLib::NonlinearSystem<Tag::Picard>;
auto& equation_system = static_cast<EqSys&>(ode_sys);
equation_system.applyKnownSolutionsPicard(A, rhs, *x[process_id]);
auto& linear_solver =
_process_data.chemical_solver_interface->linear_solver;
MathLib::LinAlg::setLocalAccessibleVector(*x[process_id]);
linear_solver.solve(A, rhs, *x[process_id]);
NumLib::GlobalMatrixProvider::provider.releaseMatrix(M);
NumLib::GlobalMatrixProvider::provider.releaseMatrix(K);
NumLib::GlobalVectorProvider::provider.releaseVector(b);
NumLib::GlobalMatrixProvider::provider.releaseMatrix(A);
NumLib::GlobalVectorProvider::provider.releaseVector(rhs);
}
void ComponentTransportProcess::computeSecondaryVariableConcrete(
double const t,
double const dt,
std::vector<GlobalVector*> const& x,
GlobalVector const& x_dot,
int const process_id)
{
if (process_id != 0)
{
return;
}
std::vector<NumLib::LocalToGlobalIndexMap const*> dof_tables;
dof_tables.reserve(x.size());
std::generate_n(std::back_inserter(dof_tables), x.size(),
[&]() { return _local_to_global_index_map.get(); });
ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::computeSecondaryVariable,
_local_assemblers, pv.getActiveElementIDs(), dof_tables, t, dt, x,
x_dot, process_id);
if (!_chemical_solver_interface)
{
return;
}
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::
computeReactionRelatedSecondaryVariable,
_local_assemblers, _chemical_solver_interface->getElementIDs());
}
void ComponentTransportProcess::postTimestepConcreteProcess(
std::vector<GlobalVector*> const& x,
std::vector<GlobalVector*> const& x_dot,
const double t,
const double dt,
int const process_id)
{
if (process_id != 0)
{
return;
}
std::vector<NumLib::LocalToGlobalIndexMap const*> dof_tables;
dof_tables.reserve(x.size());
std::generate_n(std::back_inserter(dof_tables), x.size(),
[&]() { return _local_to_global_index_map.get(); });
ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
GlobalExecutor::executeSelectedMemberOnDereferenced(
&ComponentTransportLocalAssemblerInterface::postTimestep,
_local_assemblers, pv.getActiveElementIDs(), dof_tables, x, x_dot, t,
dt, _use_monolithic_scheme, process_id);
if (!_surfaceflux) // computing the surfaceflux is optional
{
return;
}
_surfaceflux->integrate(x, t, *this, process_id, _integration_order, _mesh,
pv.getActiveElementIDs());
}
} // namespace ComponentTransport
} // namespace ProcessLib