diff --git a/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.cpp b/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.cpp deleted file mode 100644 index cdfdecec8e2740576e9c07d3caf3b9a306669d63..0000000000000000000000000000000000000000 --- a/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/** - * \copyright - * Copyright (c) 2012-2016, 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 "UncoupledProcessesTimeLoop.h" -#include "BaseLib/RunTime.h" - -namespace ApplicationsLib -{ -std::unique_ptr<UncoupledProcessesTimeLoop> createUncoupledProcessesTimeLoop( - BaseLib::ConfigTree const& conf) -{ - //! \ogs_file_param{prj__time_stepping__type} - auto const type = conf.peekConfigParameter<std::string>("type"); - - std::unique_ptr<NumLib::ITimeStepAlgorithm> timestepper; - - if (type == "SingleStep") - { - //! \ogs_file_param_special{prj__time_stepping__SingleStep} - conf.ignoreConfigParameter("type"); - timestepper.reset(new NumLib::FixedTimeStepping(0.0, 1.0, 1.0)); - } - else if (type == "FixedTimeStepping") - { - timestepper = NumLib::FixedTimeStepping::newInstance(conf); - } - else - { - OGS_FATAL("Unknown timestepper type: `%s'.", type.c_str()); - } - - using TimeLoop = UncoupledProcessesTimeLoop; - return std::unique_ptr<TimeLoop>{new TimeLoop{std::move(timestepper)}}; -} - -std::vector<typename UncoupledProcessesTimeLoop::SingleProcessData> -UncoupledProcessesTimeLoop::initInternalData(ProjectData& project) -{ - auto const num_processes = - std::distance(project.processesBegin(), project.processesEnd()); - - std::vector<SingleProcessData> per_process_data; - per_process_data.reserve(num_processes); - - // create a time discretized ODE system for each process - for (auto p = project.processesBegin(); p != project.processesEnd(); ++p) - { - auto& pcs = **p; - auto& nonlinear_solver = pcs.getNonlinearSolver(); - auto& time_disc = pcs.getTimeDiscretization(); - - per_process_data.emplace_back( - makeSingleProcessData(nonlinear_solver, **p, time_disc)); - } - - return per_process_data; -} - -void UncoupledProcessesTimeLoop::setInitialConditions( - ProjectData& project, - double const t0, - std::vector<SingleProcessData>& per_process_data) -{ - auto const num_processes = - std::distance(project.processesBegin(), project.processesEnd()); - - _process_solutions.reserve(num_processes); - - unsigned pcs_idx = 0; - for (auto p = project.processesBegin(); p != project.processesEnd(); - ++p, ++pcs_idx) - { - auto& pcs = **p; - auto& time_disc = pcs.getTimeDiscretization(); - - auto& ppd = per_process_data[pcs_idx]; - auto& ode_sys = *ppd.tdisc_ode_sys; - auto const nl_tag = ppd.nonlinear_solver_tag; - - // append a solution vector of suitable size - _process_solutions.emplace_back( - &NumLib::GlobalVectorProvider::provider.getVector( - ode_sys.getMatrixSpecifications())); - - auto& x0 = *_process_solutions[pcs_idx]; - pcs.setInitialConditions(t0, x0); - MathLib::LinAlg::finalizeAssembly(x0); - - time_disc.setInitialState(t0, x0); // push IC - - if (time_disc.needsPreload()) - { - auto& nonlinear_solver = ppd.nonlinear_solver; - auto& mat_strg = ppd.mat_strg; - auto& conv_crit = pcs.getConvergenceCriterion(); - - setEquationSystem(nonlinear_solver, ode_sys, conv_crit, nl_tag); - nonlinear_solver.assemble(x0); - time_disc.pushState( - t0, x0, mat_strg); // TODO: that might do duplicate work - } - } -} - - -bool -UncoupledProcessesTimeLoop:: -solveOneTimeStepOneProcess( - GlobalVector& x, std::size_t const timestep, double const t, double const delta_t, - SingleProcessData& process_data, - UncoupledProcessesTimeLoop::Process& process, - ProcessLib::Output const& output_control) -{ - auto& time_disc = process.getTimeDiscretization(); - auto& conv_crit = process.getConvergenceCriterion(); - auto& ode_sys = *process_data.tdisc_ode_sys; - auto& nonlinear_solver = process_data.nonlinear_solver; - auto const nl_tag = process_data.nonlinear_solver_tag; - - setEquationSystem(nonlinear_solver, ode_sys, conv_crit, nl_tag); - - // Note: Order matters! - // First advance to the next timestep, then set known solutions at that - // time, afterwards pass the right solution vector and time to the - // preTimestep() hook. - - time_disc.nextTimestep(t, delta_t); - - applyKnownSolutions(ode_sys, nl_tag, x); - - process.preTimestep(x, t, delta_t); - - auto const post_iteration_callback = [&](unsigned iteration, - GlobalVector const& x) { - output_control.doOutputNonlinearIteration(process, timestep, t, x, iteration); - }; - - bool nonlinear_solver_succeeded = - nonlinear_solver.solve(x, post_iteration_callback); - - auto& mat_strg = process_data.mat_strg; - time_disc.pushState(t, x, mat_strg); - - process.postTimestep(x); - - return nonlinear_solver_succeeded; -} - -bool UncoupledProcessesTimeLoop::loop(ProjectData& project) -{ - auto per_process_data = initInternalData(project); - - auto& out_ctrl = project.getOutputControl(); - out_ctrl.initialize(project.processesBegin(), project.processesEnd()); - - auto const t0 = _timestepper->getTimeStep().current(); // time of the IC - - // init solution storage - setInitialConditions(project, t0, per_process_data); - - // output initial conditions - { - unsigned pcs_idx = 0; - for (auto p = project.processesBegin(); p != project.processesEnd(); - ++p, ++pcs_idx) - { - auto const& x0 = *_process_solutions[pcs_idx]; - out_ctrl.doOutput(**p, 0, t0, x0); - } - } - - double t = t0; - std::size_t timestep = 1; // the first timestep really is number one - bool nonlinear_solver_succeeded = true; - - while (_timestepper->next()) - { - BaseLib::RunTime time_timestep; - time_timestep.start(); - - auto const ts = _timestepper->getTimeStep(); - auto const delta_t = ts.dt(); - t = ts.current(); - timestep = ts.steps(); - - INFO("=== timestep #%u (t=%gs, dt=%gs) ==============================", - timestep, t, delta_t); - - // TODO use process name - unsigned pcs_idx = 0; - for (auto p = project.processesBegin(); p != project.processesEnd(); - ++p, ++pcs_idx) - { - BaseLib::RunTime time_timestep_process; - time_timestep_process.start(); - - auto& x = *_process_solutions[pcs_idx]; - - nonlinear_solver_succeeded = solveOneTimeStepOneProcess( - x, timestep, t, delta_t, per_process_data[pcs_idx], **p, - out_ctrl); - - INFO("[time] Solving process #%u took %g s in timestep #%u.", - timestep, time_timestep.elapsed(), pcs_idx); - - if (!nonlinear_solver_succeeded) - { - ERR("The nonlinear solver failed in timestep #%u at t = %g s" - " for process #%u.", - timestep, t, pcs_idx); - - // save unsuccessful solution - out_ctrl.doOutputAlways(**p, timestep, t, x); - - break; - } - else - { - out_ctrl.doOutput(**p, timestep, t, x); - } - } - - INFO("[time] Timestep #%u took %g s.", timestep, - time_timestep.elapsed()); - - if (!nonlinear_solver_succeeded) - break; - } - - // output last timestep - if (nonlinear_solver_succeeded) - { - unsigned pcs_idx = 0; - for (auto p = project.processesBegin(); p != project.processesEnd(); - ++p, ++pcs_idx) - { - auto const& x = *_process_solutions[pcs_idx]; - out_ctrl.doOutputLastTimestep(**p, timestep, t, x); - } - } - - return nonlinear_solver_succeeded; -} - -UncoupledProcessesTimeLoop::~UncoupledProcessesTimeLoop() -{ - for (auto* x : _process_solutions) - NumLib::GlobalVectorProvider::provider.releaseVector(*x); -} - -} // namespace ApplicationsLib diff --git a/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.h b/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.h deleted file mode 100644 index 7cdce87b19e3bdf3174e71552567ca30f67d6e1e..0000000000000000000000000000000000000000 --- a/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.h +++ /dev/null @@ -1,239 +0,0 @@ -/** - * \copyright - * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) - * Distributed under a Modified BSD License. - * See accompanying file LICENSE.txt or - * http://www.opengeosys.org/project/license - * - */ - -#ifndef APPLICATIONSLIB_UNCOUPLED_PROCESSES_TIMELOOP -#define APPLICATIONSLIB_UNCOUPLED_PROCESSES_TIMELOOP - -#include <memory> - -#include <logog/include/logog.hpp> - -#include "BaseLib/ConfigTree.h" -#include "NumLib/ODESolver/NonlinearSolver.h" -#include "NumLib/ODESolver/TimeDiscretizedODESystem.h" -#include "NumLib/TimeStepping/Algorithms/FixedTimeStepping.h" - -#include "ProjectData.h" - -namespace ApplicationsLib -{ -//! Time loop capable of time-integrating several uncoupled processes at once. -class UncoupledProcessesTimeLoop -{ -public: - explicit UncoupledProcessesTimeLoop( - std::unique_ptr<NumLib::ITimeStepAlgorithm>&& timestepper) - : _timestepper{std::move(timestepper)} - { - } - - bool loop(ProjectData& project); - - ~UncoupledProcessesTimeLoop(); - -private: - //! An abstract nonlinear solver - using AbstractNLSolver = NumLib::NonlinearSolverBase; - //! An abstract equations system - using EquationSystem = NumLib::EquationSystem; - //! An abstract process - using Process = ProcessLib::Process; - //! An abstract time discretization - using TimeDisc = NumLib::TimeDiscretization; - - std::vector<GlobalVector*> _process_solutions; - std::unique_ptr<NumLib::ITimeStepAlgorithm> _timestepper; - - struct SingleProcessData - { - template <NumLib::ODESystemTag ODETag, NumLib::NonlinearSolverTag NLTag> - SingleProcessData(NumLib::NonlinearSolver<NLTag>& nonlinear_solver, - TimeDisc& time_disc, - NumLib::ODESystem<ODETag, NLTag>& ode_sys) - : nonlinear_solver_tag(NLTag), - nonlinear_solver(nonlinear_solver), - tdisc_ode_sys(new NumLib::TimeDiscretizedODESystem<ODETag, NLTag>( - ode_sys, time_disc)), - mat_strg( - dynamic_cast<NumLib::InternalMatrixStorage&>(*tdisc_ode_sys)) - { - } - - SingleProcessData(SingleProcessData&& spd) - : nonlinear_solver_tag(spd.nonlinear_solver_tag), - nonlinear_solver(spd.nonlinear_solver), - tdisc_ode_sys(std::move(spd.tdisc_ode_sys)), - mat_strg(spd.mat_strg) - { - } - - //! Tag containing the missing type information necessary to cast the - //! other members of this struct to their concrety types. - NumLib::NonlinearSolverTag const nonlinear_solver_tag; - AbstractNLSolver& nonlinear_solver; - //! type-erased time-discretized ODE system - std::unique_ptr<EquationSystem> tdisc_ode_sys; - //! cast of \c tdisc_ode_sys to NumLib::InternalMatrixStorage - NumLib::InternalMatrixStorage& mat_strg; - }; - - /*! Creates a new instance of PerProcessData from the given data. - * - * \tparam ODETag tag indicating the type of ODE to be solved. - * - * \param nonlinear_solver the nonlinear solver to be used - * \param ode_sys the ODE (i.e. the process) to be integrated in time - * \param time_disc the time discretization used for integrating \c ode_sys - * - * \note Currently the \c ODETag is automatically inferred from the given - * \c ody_sys. This works as long as \c Process derives from - * \c ODESystem<GlobalMatrix, GlobalVector, ODETag>, i.e. as long we - * only deal with one type of ODE. When we introduce more types, this - * method will have to be extended slightly. - */ - template <NumLib::ODESystemTag ODETag> - SingleProcessData makeSingleProcessData( - AbstractNLSolver& nonlinear_solver, - NumLib::ODESystem<ODETag, NumLib::NonlinearSolverTag::Picard>& ode_sys, - TimeDisc& time_disc) - { - using Tag = NumLib::NonlinearSolverTag; - // A concrete Picard solver - using NonlinearSolverPicard = NumLib::NonlinearSolver<Tag::Picard>; - // A concrete Newton solver - using NonlinearSolverNewton = NumLib::NonlinearSolver<Tag::Newton>; - - if (auto* nonlinear_solver_picard = - dynamic_cast<NonlinearSolverPicard*>(&nonlinear_solver)) - { - // The Picard solver can also work with a Newton-ready ODE, - // because the Newton ODESystem derives from the Picard ODESystem. - // So no further checks are needed here. - - return SingleProcessData{*nonlinear_solver_picard, time_disc, - ode_sys}; - } - else if (auto* nonlinear_solver_newton = - dynamic_cast<NonlinearSolverNewton*>(&nonlinear_solver)) - { - // The Newton-Raphson method needs a Newton-ready ODE. - - using ODENewton = NumLib::ODESystem<ODETag, Tag::Newton>; - if (auto* ode_newton = dynamic_cast<ODENewton*>(&ode_sys)) - { - return SingleProcessData{*nonlinear_solver_newton, time_disc, - *ode_newton}; - } - else - { - OGS_FATAL( - "You are trying to solve a non-Newton-ready ODE with the" - " Newton-Raphson method. Aborting"); - } - } - else - { - OGS_FATAL("Encountered unknown nonlinear solver type. Aborting"); - } - } - - //! Constructs and returns a \c vector of SingleProcessData from the given - //! \c project. - std::vector<SingleProcessData> initInternalData(ProjectData& project); - - //! Sets initial conditions for the given \c project and \c - //! per_process_data. - void setInitialConditions(ProjectData& project, double const t0, - std::vector<SingleProcessData>& per_process_data); - - //! Solves one timestep for the given \c process. - bool solveOneTimeStepOneProcess( - GlobalVector& x, std::size_t const timestep, double const t, double const delta_t, - SingleProcessData& process_data, - Process& process, ProcessLib::Output const& output_control); - - //! Sets the EquationSystem for the given nonlinear solver, - //! which is Picard or Newton depending on the NLTag. - template <NumLib::NonlinearSolverTag NLTag> - static void setEquationSystem(AbstractNLSolver& nonlinear_solver, - EquationSystem& eq_sys, - NumLib::ConvergenceCriterion& conv_crit) - { - using Solver = NumLib::NonlinearSolver<NLTag>; - using EqSys = NumLib::NonlinearSystem<NLTag>; - - assert(dynamic_cast<Solver*>(&nonlinear_solver) != nullptr); - assert(dynamic_cast<EqSys*>(&eq_sys) != nullptr); - - auto& nl_solver_ = static_cast<Solver&>(nonlinear_solver); - auto& eq_sys_ = static_cast<EqSys&>(eq_sys); - - nl_solver_.setEquationSystem(eq_sys_, conv_crit); - } - - //! Sets the EquationSystem for the given nonlinear solver, - //! transparently both for Picard and Newton solvers. - static void setEquationSystem(AbstractNLSolver& nonlinear_solver, - EquationSystem& eq_sys, - NumLib::ConvergenceCriterion& conv_crit, - NumLib::NonlinearSolverTag nl_tag) - { - using Tag = NumLib::NonlinearSolverTag; - switch (nl_tag) - { - case Tag::Picard: - setEquationSystem<Tag::Picard>(nonlinear_solver, eq_sys, - conv_crit); - break; - case Tag::Newton: - setEquationSystem<Tag::Newton>(nonlinear_solver, eq_sys, - conv_crit); - break; - } - } - - //! Applies known solutions to the solution vector \c x, transparently - //! for equation systems linearized with either the Picard or Newton method. - template <NumLib::NonlinearSolverTag NLTag> - static void applyKnownSolutions(EquationSystem const& eq_sys, - GlobalVector& x) - { - using EqSys = NumLib::NonlinearSystem<NLTag>; - assert(dynamic_cast<EqSys const*>(&eq_sys) != nullptr); - auto& eq_sys_ = static_cast<EqSys const&>(eq_sys); - - eq_sys_.applyKnownSolutions(x); - } - - //! Applies known solutions to the solution vector \c x, transparently - //! for equation systems linearized with either the Picard or Newton method. - static void applyKnownSolutions(EquationSystem const& eq_sys, - NumLib::NonlinearSolverTag const nl_tag, - GlobalVector& x) - { - using Tag = NumLib::NonlinearSolverTag; - switch (nl_tag) - { - case Tag::Picard: - applyKnownSolutions<Tag::Picard>(eq_sys, x); - break; - case Tag::Newton: - applyKnownSolutions<Tag::Newton>(eq_sys, x); - break; - } - } -}; - -//! Builds an UncoupledProcessesTimeLoop from the given configuration. -std::unique_ptr<UncoupledProcessesTimeLoop> createUncoupledProcessesTimeLoop( - BaseLib::ConfigTree const& conf); - -} // namespace ApplicationsLib - -#endif // APPLICATIONSLIB_UNCOUPLED_PROCESSES_TIMELOOP diff --git a/ProcessLib/UncoupledProcessesTimeLoop.cpp b/ProcessLib/UncoupledProcessesTimeLoop.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b0bf1f736c23d9fbdee4a123244fa8626e2531e3 --- /dev/null +++ b/ProcessLib/UncoupledProcessesTimeLoop.cpp @@ -0,0 +1,563 @@ +/** + * \copyright + * Copyright (c) 2012-2016, 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 "UncoupledProcessesTimeLoop.h" +#include "BaseLib/uniqueInsert.h" +#include "BaseLib/RunTime.h" +#include "NumLib/ODESolver/TimeDiscretizationBuilder.h" +#include "NumLib/ODESolver/TimeDiscretizedODESystem.h" +#include "NumLib/ODESolver/ConvergenceCriterionPerComponent.h" +#include "NumLib/TimeStepping/Algorithms/FixedTimeStepping.h" + +std::unique_ptr<NumLib::ITimeStepAlgorithm> createTimeStepper( + BaseLib::ConfigTree const& config) +{ + //! \ogs_file_param{prj__time_loop__time_stepping__type} + auto const type = config.peekConfigParameter<std::string>("type"); + + std::unique_ptr<NumLib::ITimeStepAlgorithm> timestepper; + + if (type == "SingleStep") + { + //! \ogs_file_param_special{prj__time_stepping__SingleStep} + config.ignoreConfigParameter("type"); + timestepper.reset(new NumLib::FixedTimeStepping(0.0, 1.0, 1.0)); + } + else if (type == "FixedTimeStepping") + { + timestepper = NumLib::FixedTimeStepping::newInstance(config); + } + else + { + OGS_FATAL("Unknown timestepper type: `%s'.", type.c_str()); + } + + return timestepper; +} + +std::unique_ptr<ProcessLib::Output> createOutput( + BaseLib::ConfigTree const& config, std::string const& output_directory) +{ + //! \ogs_file_param{prj__time_loop__output__type} + config.checkConfigParameter("type", "VTK"); + DBUG("Parse output configuration:"); + + return ProcessLib::Output::newInstance(config, output_directory); +} + + +//! Sets the EquationSystem for the given nonlinear solver, +//! which is Picard or Newton depending on the NLTag. +template <NumLib::NonlinearSolverTag NLTag> +static void setEquationSystem(NumLib::NonlinearSolverBase& nonlinear_solver, + NumLib::EquationSystem& eq_sys, + NumLib::ConvergenceCriterion& conv_crit) +{ + using Solver = NumLib::NonlinearSolver<NLTag>; + using EqSys = NumLib::NonlinearSystem<NLTag>; + + assert(dynamic_cast<Solver*>(&nonlinear_solver) != nullptr); + assert(dynamic_cast<EqSys*>(&eq_sys) != nullptr); + + auto& nl_solver_ = static_cast<Solver&>(nonlinear_solver); + auto& eq_sys_ = static_cast<EqSys&>(eq_sys); + + nl_solver_.setEquationSystem(eq_sys_, conv_crit); +} + +//! Sets the EquationSystem for the given nonlinear solver, +//! transparently both for Picard and Newton solvers. +static void setEquationSystem(NumLib::NonlinearSolverBase& nonlinear_solver, + NumLib::EquationSystem& eq_sys, + NumLib::ConvergenceCriterion& conv_crit, + NumLib::NonlinearSolverTag nl_tag) +{ + using Tag = NumLib::NonlinearSolverTag; + switch (nl_tag) + { + case Tag::Picard: + setEquationSystem<Tag::Picard>(nonlinear_solver, eq_sys, + conv_crit); + break; + case Tag::Newton: + setEquationSystem<Tag::Newton>(nonlinear_solver, eq_sys, + conv_crit); + break; + } +} + +//! Applies known solutions to the solution vector \c x, transparently +//! for equation systems linearized with either the Picard or Newton method. +template <NumLib::NonlinearSolverTag NLTag> +static void applyKnownSolutions(NumLib::EquationSystem const& eq_sys, + GlobalVector& x) +{ + using EqSys = NumLib::NonlinearSystem<NLTag>; + assert(dynamic_cast<EqSys const*>(&eq_sys) != nullptr); + auto& eq_sys_ = static_cast<EqSys const&>(eq_sys); + + eq_sys_.applyKnownSolutions(x); +} + +//! Applies known solutions to the solution vector \c x, transparently +//! for equation systems linearized with either the Picard or Newton method. +static void applyKnownSolutions(NumLib::EquationSystem const& eq_sys, + NumLib::NonlinearSolverTag const nl_tag, + GlobalVector& x) +{ + using Tag = NumLib::NonlinearSolverTag; + switch (nl_tag) + { + case Tag::Picard: + applyKnownSolutions<Tag::Picard>(eq_sys, x); + break; + case Tag::Newton: + applyKnownSolutions<Tag::Newton>(eq_sys, x); + break; + } +} + +namespace ProcessLib +{ +struct SingleProcessData +{ + template <NumLib::NonlinearSolverTag NLTag> + SingleProcessData( + NumLib::NonlinearSolver<NLTag>& nonlinear_solver, + std::unique_ptr<NumLib::ConvergenceCriterion>&& conv_crit_, + std::unique_ptr<NumLib::TimeDiscretization>&& time_disc_, + Process& process_, + ProcessOutput&& process_output_); + + SingleProcessData(SingleProcessData&& spd); + + //! Tag containing the missing type information necessary to cast the + //! other members of this struct to their concrety types. + NumLib::NonlinearSolverTag const nonlinear_solver_tag; + NumLib::NonlinearSolverBase& nonlinear_solver; + std::unique_ptr<NumLib::ConvergenceCriterion> conv_crit; + + std::unique_ptr<NumLib::TimeDiscretization> time_disc; + //! type-erased time-discretized ODE system + std::unique_ptr<NumLib::EquationSystem> tdisc_ode_sys; + //! cast of \c tdisc_ode_sys to NumLib::InternalMatrixStorage + NumLib::InternalMatrixStorage* mat_strg = nullptr; + + Process& process; + ProcessOutput process_output; +}; + +template <NumLib::NonlinearSolverTag NLTag> +SingleProcessData::SingleProcessData( + NumLib::NonlinearSolver<NLTag>& nonlinear_solver, + std::unique_ptr<NumLib::ConvergenceCriterion>&& conv_crit_, + std::unique_ptr<NumLib::TimeDiscretization>&& time_disc_, + Process& process_, + ProcessOutput&& process_output_) + : nonlinear_solver_tag(NLTag), + nonlinear_solver(nonlinear_solver), + conv_crit(std::move(conv_crit_)), + time_disc(std::move(time_disc_)), + process(process_), + process_output(std::move(process_output_)) +{ +} + +SingleProcessData::SingleProcessData(SingleProcessData&& spd) + : nonlinear_solver_tag(spd.nonlinear_solver_tag), + nonlinear_solver(spd.nonlinear_solver), + conv_crit(std::move(spd.conv_crit)), + time_disc(std::move(spd.time_disc)), + tdisc_ode_sys(std::move(spd.tdisc_ode_sys)), + mat_strg(spd.mat_strg), + process(spd.process), + process_output(std::move(spd.process_output)) +{ + spd.mat_strg = nullptr; +} + +template <NumLib::ODESystemTag ODETag> +void setTimeDiscretizedODESystem( + SingleProcessData& spd, + NumLib::ODESystem<ODETag, NumLib::NonlinearSolverTag::Picard>& ode_sys) +{ + using Tag = NumLib::NonlinearSolverTag; + // A concrete Picard solver + using NonlinearSolverPicard = NumLib::NonlinearSolver<Tag::Picard>; + // A concrete Newton solver + using NonlinearSolverNewton = NumLib::NonlinearSolver<Tag::Newton>; + + if (auto* nonlinear_solver_picard = + dynamic_cast<NonlinearSolverPicard*>(&spd.nonlinear_solver)) + { + // The Picard solver can also work with a Newton-ready ODE, + // because the Newton ODESystem derives from the Picard ODESystem. + // So no further checks are needed here. + + spd.tdisc_ode_sys.reset( + new NumLib::TimeDiscretizedODESystem<ODETag, Tag::Picard>( + ode_sys, *spd.time_disc)); + } + else if (auto* nonlinear_solver_newton = + dynamic_cast<NonlinearSolverNewton*>(&spd.nonlinear_solver)) + { + // The Newton-Raphson method needs a Newton-ready ODE. + + using ODENewton = NumLib::ODESystem<ODETag, Tag::Newton>; + if (auto* ode_newton = dynamic_cast<ODENewton*>(&ode_sys)) + { + spd.tdisc_ode_sys.reset( + new NumLib::TimeDiscretizedODESystem<ODETag, Tag::Newton>( + *ode_newton, *spd.time_disc)); + } + else + { + OGS_FATAL( + "You are trying to solve a non-Newton-ready ODE with the" + " Newton-Raphson method. Aborting"); + } + } + else + { + OGS_FATAL("Encountered unknown nonlinear solver type. Aborting"); + } + + spd.mat_strg = + dynamic_cast<NumLib::InternalMatrixStorage*>(spd.tdisc_ode_sys.get()); +} + +void setTimeDiscretizedODESystem(SingleProcessData& spd) +{ + setTimeDiscretizedODESystem(spd, spd.process); +} + +std::unique_ptr<SingleProcessData> makeSingleProcessData( + NumLib::NonlinearSolverBase& nonlinear_solver, + Process& process, + std::unique_ptr<NumLib::TimeDiscretization>&& time_disc, + std::unique_ptr<NumLib::ConvergenceCriterion>&& conv_crit, + ProcessOutput&& process_output) +{ + using Tag = NumLib::NonlinearSolverTag; + + if (auto* nonlinear_solver_picard = + dynamic_cast<NumLib::NonlinearSolver<Tag::Picard>*>( + &nonlinear_solver)) + { + return std::unique_ptr<SingleProcessData>{new SingleProcessData{ + *nonlinear_solver_picard, std::move(conv_crit), + std::move(time_disc), process, std::move(process_output)}}; + } + else if (auto* nonlinear_solver_newton = + dynamic_cast<NumLib::NonlinearSolver<Tag::Newton>*>( + &nonlinear_solver)) + { + return std::unique_ptr<SingleProcessData>{new SingleProcessData{ + *nonlinear_solver_newton, std::move(conv_crit), + std::move(time_disc), process, std::move(process_output)}}; + } else { + OGS_FATAL("Encountered unknown nonlinear solver type. Aborting"); + } +} + +std::vector<std::unique_ptr<SingleProcessData>> createPerProcessData( + BaseLib::ConfigTree const& config, + const std::map<std::string, std::unique_ptr<Process>>& + processes, + std::map<std::string, std::unique_ptr<NumLib::NonlinearSolverBase>> const& + nonlinear_solvers) +{ + std::vector<std::unique_ptr<SingleProcessData>> per_process_data; + + //! \ogs_file_param{prj__time_loop__processes__process} + for (auto pcs_config : config.getConfigSubtreeList("process")) + { + //! \ogs_file_attr{prj__time_loop__processes__process__ref} + auto const pcs_name = pcs_config.getConfigAttribute<std::string>("ref"); + auto& pcs = *BaseLib::getOrError( + processes, pcs_name, + "A process with the given name has not been defined."); + + //! \ogs_file_param{prj__time_loop__processes__process__nonlinear_solver} + auto const nl_slv_name = + pcs_config.getConfigParameter<std::string>("nonlinear_solver"); + auto& nl_slv = *BaseLib::getOrError( + nonlinear_solvers, nl_slv_name, + "A nonlinear solver with the given name has not been defined."); + + auto time_disc = NumLib::createTimeDiscretization( + //! \ogs_file_param{prj__time_loop__processes__process__time_discretization} + pcs_config.getConfigSubtree("time_discretization")); + + auto conv_crit = NumLib::createConvergenceCriterion( + //! \ogs_file_param{prj__time_loop__processes__process__convergence_criterion} + pcs_config.getConfigSubtree("convergence_criterion")); + + //! \ogs_file_param{prj__time_loop__processes__process__output} + ProcessOutput process_output{ + pcs_config.getConfigSubtree("output")}; + + per_process_data.emplace_back(makeSingleProcessData( + nl_slv, pcs, std::move(time_disc), std::move(conv_crit), + std::move(process_output))); + } + + if (per_process_data.size() != processes.size()) + OGS_FATAL( + "Some processes have not been configured to be solved by this time " + "time loop."); + + return per_process_data; +} + +std::unique_ptr<UncoupledProcessesTimeLoop> createUncoupledProcessesTimeLoop( + BaseLib::ConfigTree const& config, std::string const& output_directory, + const std::map<std::string, std::unique_ptr<Process>>& + processes, + const std::map<std::string, std::unique_ptr<NumLib::NonlinearSolverBase>>& + nonlinear_solvers) +{ + //! \ogs_file_param{prj__time_loop__time_stepping} + auto timestepper = + createTimeStepper(config.getConfigSubtree("time_stepping")); + + //! \ogs_file_param{prj__time_loop__output} + auto output = + createOutput(config.getConfigSubtree("output"), output_directory); + + //! \ogs_file_param{prj__time_loop__processes} + auto per_process_data = createPerProcessData( + config.getConfigSubtree("processes"), processes, nonlinear_solvers); + + return std::unique_ptr<UncoupledProcessesTimeLoop>{ + new UncoupledProcessesTimeLoop{std::move(timestepper), + std::move(output), + std::move(per_process_data)}}; +} + +std::vector<GlobalVector*> setInitialConditions( + double const t0, + std::vector<std::unique_ptr<SingleProcessData>> const& per_process_data) +{ + std::vector<GlobalVector*> process_solutions; + + unsigned pcs_idx = 0; + for (auto& spd : per_process_data) + { + auto& pcs = spd->process; + auto& time_disc = *spd->time_disc; + + auto& ode_sys = *spd->tdisc_ode_sys; + auto const nl_tag = spd->nonlinear_solver_tag; + + // append a solution vector of suitable size + process_solutions.emplace_back( + &NumLib::GlobalVectorProvider::provider.getVector( + ode_sys.getMatrixSpecifications())); + + auto& x0 = *process_solutions[pcs_idx]; + pcs.setInitialConditions(x0); + MathLib::LinAlg::finalizeAssembly(x0); + + time_disc.setInitialState(t0, x0); // push IC + + if (time_disc.needsPreload()) + { + auto& nonlinear_solver = spd->nonlinear_solver; + auto& mat_strg = *spd->mat_strg; + auto& conv_crit = *spd->conv_crit; + + setEquationSystem(nonlinear_solver, ode_sys, conv_crit, nl_tag); + nonlinear_solver.assemble(x0); + time_disc.pushState( + t0, x0, mat_strg); // TODO: that might do duplicate work + } + + ++pcs_idx; + } + + return process_solutions; +} + +bool solveOneTimeStepOneProcess(GlobalVector& x, std::size_t const timestep, + double const t, double const delta_t, + SingleProcessData& process_data, + Output const& output_control) +{ + auto& process = process_data.process; + auto& time_disc = *process_data.time_disc; + auto& conv_crit = *process_data.conv_crit; + auto& ode_sys = *process_data.tdisc_ode_sys; + auto& nonlinear_solver = process_data.nonlinear_solver; + auto const nl_tag = process_data.nonlinear_solver_tag; + + setEquationSystem(nonlinear_solver, ode_sys, conv_crit, nl_tag); + + // Note: Order matters! + // First advance to the next timestep, then set known solutions at that + // time, afterwards pass the right solution vector and time to the + // preTimestep() hook. + + time_disc.nextTimestep(t, delta_t); + + applyKnownSolutions(ode_sys, nl_tag, x); + + process.preTimestep(x, t, delta_t); + + auto const post_iteration_callback = [&]( + unsigned iteration, GlobalVector const& x) { + output_control.doOutputNonlinearIteration( + process, process_data.process_output, timestep, t, x, iteration); + }; + + bool nonlinear_solver_succeeded = + nonlinear_solver.solve(x, post_iteration_callback); + + auto& mat_strg = *process_data.mat_strg; + time_disc.pushState(t, x, mat_strg); + + process.postTimestep(x); + + return nonlinear_solver_succeeded; +} + +UncoupledProcessesTimeLoop::UncoupledProcessesTimeLoop( + std::unique_ptr<NumLib::ITimeStepAlgorithm>&& timestepper, + std::unique_ptr<Output>&& output, + std::vector<std::unique_ptr<SingleProcessData>>&& per_process_data) + : _timestepper{std::move(timestepper)}, + _output(std::move(output)), + _per_process_data(std::move(per_process_data)) +{ +} + +bool UncoupledProcessesTimeLoop::loop() +{ + // initialize output, convergence criterion, etc. + { + unsigned pcs_idx = 0; + for (auto& spd : _per_process_data) + { + auto& pcs = spd->process; + _output->addProcess(pcs, pcs_idx); + + setTimeDiscretizedODESystem(*spd); + + if (auto* conv_crit = + dynamic_cast<NumLib::ConvergenceCriterionPerComponent*>( + spd->conv_crit.get())) { + conv_crit->setDOFTable(pcs.getDOFTable(), pcs.getMesh()); + } + + ++pcs_idx; + } + } + + auto const t0 = _timestepper->getTimeStep().current(); // time of the IC + + // init solution storage + _process_solutions = setInitialConditions(t0, _per_process_data); + + // output initial conditions + { + unsigned pcs_idx = 0; + for (auto& spd : _per_process_data) + { + auto& pcs = spd->process; + auto const& x0 = *_process_solutions[pcs_idx]; + + pcs.preTimestep(x0, t0, _timestepper->getTimeStep().dt()); + _output->doOutput(pcs, spd->process_output, 0, t0, x0); + ++pcs_idx; + } + } + + double t = t0; + std::size_t timestep = 1; // the first timestep really is number one + bool nonlinear_solver_succeeded = true; + + while (_timestepper->next()) + { + BaseLib::RunTime time_timestep; + time_timestep.start(); + + auto const ts = _timestepper->getTimeStep(); + auto const delta_t = ts.dt(); + t = ts.current(); + timestep = ts.steps(); + + INFO("=== timestep #%u (t=%gs, dt=%gs) ==============================", + timestep, t, delta_t); + + // TODO use process name + unsigned pcs_idx = 0; + for (auto& spd : _per_process_data) + { + auto& pcs = spd->process; + BaseLib::RunTime time_timestep_process; + time_timestep_process.start(); + + auto& x = *_process_solutions[pcs_idx]; + + nonlinear_solver_succeeded = solveOneTimeStepOneProcess( + x, timestep, t, delta_t, *spd, *_output); + + INFO("[time] Solving process #%u took %g s in timestep #%u.", + timestep, time_timestep.elapsed(), pcs_idx); + + if (!nonlinear_solver_succeeded) + { + ERR("The nonlinear solver failed in timestep #%u at t = %g s" + " for process #%u.", + timestep, t, pcs_idx); + + // save unsuccessful solution + _output->doOutputAlways(pcs, spd->process_output, timestep, t, x); + + break; + } + else + { + _output->doOutput(pcs, spd->process_output, timestep, t, x); + } + + ++pcs_idx; + } + + INFO("[time] Timestep #%u took %g s.", timestep, + time_timestep.elapsed()); + + if (!nonlinear_solver_succeeded) + break; + } + + // output last timestep + if (nonlinear_solver_succeeded) + { + unsigned pcs_idx = 0; + for (auto& spd : _per_process_data) + { + auto& pcs = spd->process; + auto const& x = *_process_solutions[pcs_idx]; + _output->doOutputLastTimestep(pcs, spd->process_output, timestep, t, x); + + ++pcs_idx; + } + } + + return nonlinear_solver_succeeded; +} + +UncoupledProcessesTimeLoop::~UncoupledProcessesTimeLoop() +{ + for (auto* x : _process_solutions) + NumLib::GlobalVectorProvider::provider.releaseVector(*x); +} + +} // namespace ProcessLib diff --git a/ProcessLib/UncoupledProcessesTimeLoop.h b/ProcessLib/UncoupledProcessesTimeLoop.h new file mode 100644 index 0000000000000000000000000000000000000000..992ab4624e02b1b16c03b20a3a6952e76ce3648d --- /dev/null +++ b/ProcessLib/UncoupledProcessesTimeLoop.h @@ -0,0 +1,56 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#ifndef PROCESSLIB_UNCOUPLED_PROCESSES_TIMELOOP +#define PROCESSLIB_UNCOUPLED_PROCESSES_TIMELOOP + +#include <memory> +#include <logog/include/logog.hpp> + +#include "NumLib/ODESolver/NonlinearSolver.h" +#include "NumLib/TimeStepping/Algorithms/ITimeStepAlgorithm.h" + +#include "Output.h" +#include "Process.h" + +namespace ProcessLib +{ +struct SingleProcessData; + +//! Time loop capable of time-integrating several uncoupled processes at once. +class UncoupledProcessesTimeLoop +{ +public: + explicit UncoupledProcessesTimeLoop( + std::unique_ptr<NumLib::ITimeStepAlgorithm>&& timestepper, + std::unique_ptr<Output>&& output, + std::vector<std::unique_ptr<SingleProcessData>>&& per_process_data); + + bool loop(); + + ~UncoupledProcessesTimeLoop(); + +private: + std::vector<GlobalVector*> _process_solutions; + std::unique_ptr<NumLib::ITimeStepAlgorithm> _timestepper; + std::unique_ptr<Output> _output; + std::vector<std::unique_ptr<SingleProcessData>> _per_process_data; +}; + +//! Builds an UncoupledProcessesTimeLoop from the given configuration. +std::unique_ptr<UncoupledProcessesTimeLoop> createUncoupledProcessesTimeLoop( + BaseLib::ConfigTree const& config, std::string const& output_directory, + std::map<std::string, std::unique_ptr<Process>> const& + processes, + std::map<std::string, std::unique_ptr<NumLib::NonlinearSolverBase>> const& + nonlinear_solvers); + +} // namespace ProcessLib + +#endif // PROCESSLIB_UNCOUPLED_PROCESSES_TIMELOOP