Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GeomechanicsApplication] Geo/linear elastic strategy #12479

Open
wants to merge 70 commits into
base: master
Choose a base branch
from

Conversation

aronnoordam
Copy link
Member

@aronnoordam aronnoordam commented Jun 25, 2024

added another strategy and builder and solver meant for linear elastic problems.

Mostly, this strategy exploits the fact that the internal forces dont have to be recalculated every iteration.

  • stiffness matrix is constant
  • damping matrix is constant
  • mass matrix is constant
  • rhs, only condition contributions are updated
  • the linear system is pre factorized in the first iteration/ time-step, if possible

Only external forces are updated

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont forget to revert this

@aronnoordam aronnoordam self-assigned this Sep 13, 2024
Copy link
Contributor

@rfaasse rfaasse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @aronnoordam, thanks for processing the first round of feedback. This helps a lot in understanding the code and giving better feedback and in my opinion both the tests and production code improved a lot: less duplication, clearer responsibilities of classes and more specific testing on what the strategy does and (more importantly) does not do/call.

This round, I tried to be more detailed in my comments, due to larger scale design becoming more clear. For the builder and solver, I would like to discuss a bit more with the team about if it makes sense to move more newmark functionality to the scheme and reduce a bit more duplication (which is hard as you mentioned, due to the differences in private non-virtual functions).

Next to that, @avdg81 and @WPK4FEM will also have another look at the PR 👍

: ResidualBasedNewtonRaphsonStrategy<TSparseSpace, TDenseSpace, TLinearSolver>(
rModelPart, pScheme, pNewConvergenceCriteria, pNewBuilderAndSolver, MaxIterations, CalculateReactions, false, MoveMeshFlag)
{
// new constructor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment can be removed


BaseType::GetScheme()->Predict(BaseType::GetModelPart(), r_dof_set, rA, rDx, rb);

// Note that constraints are not applied in this predict, nor is an update performed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's good to mention here that the constraints are applied in the builder and solver.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And did you already find what the reason is that the constraints cannot be applied in the same way as for the base strategy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes actually, as for constraints, there is a dotproduct with the constraint transformation matrix and the RHS, now in my case, i already transformed the mass and damping matrix, thus when applying a constraint again to the rhs, part of the RHS is transformed twice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the formulations are different for this new incremental scheme, this should be documented in the README.md with the other schemes.

}

if (is_converged) {
// here only the derivatives are updated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good to add here that only the derivatives are updated in the specific scheme we're using. In general schemes will update both the derivatives and the instance variable

Comment on lines 552 to 572
void AddDynamicsToLhs(TSystemMatrixType& rA, const ModelPart& rModelPart)
{
const double delta_time = rModelPart.GetProcessInfo()[DELTA_TIME];

double* a_values = rA.value_data().begin();
const double* m_values = mMassMatrix.value_data().begin();
const double* c_values = mDampingMatrix.value_data().begin();

// add mass and damping contribution to LHS sparse matrix
// mass contribution: 1.0 / (mBeta * delta_time * delta_time) * M
// damping contribution: mGamma / (mBeta * delta_time) * C
for (std::size_t i = 0; i < rA.size1(); i++) {
const std::size_t col_begin = rA.index1_data()[i];
const std::size_t col_end = rA.index1_data()[i + 1];

for (std::size_t j = col_begin; j < col_end; ++j) {
a_values[j] += (1.0 / (mBeta * delta_time * delta_time)) * m_values[j];
a_values[j] += (mGamma / (mBeta * delta_time)) * c_values[j];
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion the code is much more readable (we don't need the comments, because the code itself just shows the formula), so could we just try it? If the performance drops significantly, we can revert it, but it's worth checking in case it is similar.

"rayleigh_m": 0.02,
"rayleigh_k": 6e-6,
"strategy_type": "newton_raphson_linear_elastic",
"convergence_criterion": "displacement_criterion",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, but there are still functions copied from the base and moved around, so I think it's a good idea to use residual criterion for one of the test cases (shouldn't change the results anyway, just a json change), just to make sure. But I leave it up to you 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also rename the file-name (.cpp and .h) to geo_mock_element to align with the class name?

//
// Main authors: Aron Noordam
//
#include "custom_utilities/dof_utilities.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just include dof.h instead of dof_utilities? or are we using something from the utils?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will check

Comment on lines +257 to +265
std::vector<double> expected_displacement_x = {0.00673, 0.0505, 0.189, 0.485, 0.961, 1.58,
2.23, 2.76, 3.0, 2.85, 2.28, 1.40};
std::vector<double> expected_displacement_y = {0.364, 1.35, 2.68, 4.00, 4.95, 5.34,
5.13, 4.48, 3.64, 2.90, 2.44, 2.31};

double delta_time = 0.28;
bool use_iterations = false;
bool calculate_initial_acceleration = true;
bool use_direct_solver = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can all be const (same holds for the other tests)

Comment on lines 228 to 243
KRATOS_EXPECT_EQ(geo_custom_element.GetCountCalculateLeftHandSideCalled(), 1);
KRATOS_EXPECT_EQ(geo_custom_element.GetCountCalculateMassMatrixCalled(), 1);
KRATOS_EXPECT_EQ(geo_custom_element.GetCalculateDampingMatrixCalled(), 1);
KRATOS_EXPECT_EQ(geo_custom_element.GetCountCalculateRightHandSideCalled(), 0);

KRATOS_EXPECT_EQ(geo_custom_condition.GetCountCalculateLeftHandSideCalled(), 1);
KRATOS_EXPECT_EQ(geo_custom_condition.GetCountCalculateMassMatrixCalled(), 1);
KRATOS_EXPECT_EQ(geo_custom_condition.GetCalculateDampingMatrixCalled(), 1);

// rhs for conditions is called each solution step and during initialisation
if (UseIterations) {
// Two iterations are performed per solution step, thus rhs for conditions is called twice each solution step step and during initialisation
KRATOS_EXPECT_EQ(geo_custom_condition.GetCountCalculateRightHandSideCalled(), n_steps * 2 + 1);
} else {
KRATOS_EXPECT_EQ(geo_custom_condition.GetCountCalculateRightHandSideCalled(), n_steps + 1);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding these checks, it also functions as documentation (it's really clear what is called how many times from this test)

Copy link
Contributor

@avdg81 avdg81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a big piece of work that, as I understood from you, yields a significant performance improvement, which is great. As you can see, I have several suggestions. For me the most important ones are aimed at making the new scheme's initialization and finalization behavior symmetric. Also, I have a suggestion to remove the duplication of the nonlinear iteration, which nicely ties in with my previous suggestion. I hope my suggestions will contribute to making your code clearer and more maintainable. Feel free to reach out when my comments are unclear or when you believe that some of them are not feasible. Thanks.

typename TLinearSolver::Pointer pNewLinearSolver,
typename TConvergenceCriteriaType::Pointer pNewConvergenceCriteria,
typename TBuilderAndSolverType::Pointer pNewBuilderAndSolver,
Parameters& rParameters,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, also pNewLinearSolver is no longer (?) used. So I would suggest to remove it as well (including the corresponding Doxygen description).

*/
void Initialize() override
{
KRATOS_TRY;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the empty statement:

Suggested change
KRATOS_TRY;
KRATOS_TRY

// initialize the system matrices and the initial second derivative
this->InititalizeSystemAndState();

KRATOS_CATCH("");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the empty statement:

Suggested change
KRATOS_CATCH("");
KRATOS_CATCH("")

{
KRATOS_TRY;

BaseType::Initialize();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation of the base class's Initialize suggests that it may be called more than once, since there is a flag (mInitializeWasPerformed) that makes sure the actual initialization is performed only once. Would we need a similar guarding mechanism here?


BaseType::Initialize();

// Note that FindNeighbourElementsOfConditionsProcess and DeactivateConditionsOnInactiveElements are required to be perfomed before initializing the System and State
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A typo:

Suggested change
// Note that FindNeighbourElementsOfConditionsProcess and DeactivateConditionsOnInactiveElements are required to be perfomed before initializing the System and State
// Note that FindNeighbourElementsOfConditionsProcess and DeactivateConditionsOnInactiveElements are required to be performed before initializing the System and State


DofsArrayType& r_dof_set = BaseType::GetBuilderAndSolver()->GetDofSet();

BaseType::GetScheme()->Predict(BaseType::GetModelPart(), r_dof_set, rA, rDx, rb);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since rA, rDx, and rb are only used once, I'd suggest to eliminate this local variables:

Suggested change
BaseType::GetScheme()->Predict(BaseType::GetModelPart(), r_dof_set, rA, rDx, rb);
BaseType::GetScheme()->Predict(BaseType::GetModelPart(), r_dof_set, *BaseType::mpA, *BaseType::mpDx, *BaseType::mpb);

You could take this one step further and eliminate r_dof_set in the same way.

KRATOS_CATCH("")
}

void FinalizeNonLinIteration(ModelPart& rModelPart, TSystemMatrixType&, TSystemVectorType&, TSystemVectorType&) override
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to make the initialization and finalization of a nonlinear iteration symmetric. At present, it isn't, because the member function that initializes a nonlinear iteration initializes both the elements and the conditions. (See GeoMechanicsTimeIntegrationScheme<...>::InitializeNonLinIteration). My suggestion would be to add an override for InitializeNonLinIteration that only initializes the conditions. Then in your new strategy, you can initialize the elements prior to carrying out the first nonlinear iteration.

Comment on lines +411 to +415
block_for_each(r_model_part.Conditions(), [&r_current_process_info](Condition& r_condition) {
if (r_condition.IsActive()) {
r_condition.FinalizeNonLinearIteration(r_current_process_info);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not mistaken, this is exactly what your new scheme does...? So I think you can replace it by:

Suggested change
block_for_each(r_model_part.Conditions(), [&r_current_process_info](Condition& r_condition) {
if (r_condition.IsActive()) {
r_condition.FinalizeNonLinearIteration(r_current_process_info);
}
});
p_scheme->FinalizeNonLinIteration(r_model_part, rA, rDx, rb);

Comment on lines +368 to +373
bool PerformIterationCycle(TSystemMatrixType& rA,
TSystemVectorType& rDx,
TSystemVectorType& rb,
TSystemVectorType& rDxTot,
std::vector<Vector>& rNonconvergedSolutions,
unsigned int& rIterationNumber)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to extract a member function that does a single nonlinear iteration, and then reuse that for the first iteration as well as for any subsequent iterations. In terms of your implementation, it would roughly look like this:

r_model_part.GetProcessInfo()[NL_ITERATION_NUMBER] = rIterationNumber;

p_scheme->InitializeNonLinIteration(r_model_part, rA, rDx, rb);
BaseType::mpConvergenceCriteria->InitializeNonLinearIteration(r_model_part, r_dof_set, rA, rDx, rb);

is_converged = BaseType::mpConvergenceCriteria->PreCriteria(r_model_part, r_dof_set, rA, rDx, rb);

// call the linear system solver to find the correction mDx for the
// it is not called if there is no system to solve
if (SparseSpaceType::Size(rDx) != 0) {
    TSparseSpace::SetToZero(rDx);
    TSparseSpace::SetToZero(rb);

    p_builder_and_solver->BuildRHSAndSolve(p_scheme, r_model_part, rA, rDx, rb);

} else {
    KRATOS_WARNING("NO DOFS") << "ATTENTION: no free DOFs!! " << std::endl;
}

// Debugging info
BaseType::EchoInfo(rIterationNumber);

// Updating the results stored in the database
this->UpdateSolutionStepValue(rDx, rDxTot);

p_scheme->FinalizeNonLinIteration(r_model_part, rA, rDx, rb);

BaseType::mpConvergenceCriteria->FinalizeNonLinearIteration(r_model_part, r_dof_set, rA, rDx, rb);

if (BaseType::mStoreNonconvergedSolutionsFlag) {
    Vector ith;
    BaseType::GetCurrentSolution(r_dof_set, ith);
    rNonconvergedSolutions.push_back(ith);
}

if (is_converged) {
    if (BaseType::mpConvergenceCriteria->GetActualizeRHSflag()) {
        TSparseSpace::SetToZero(rb);

        p_builder_and_solver->BuildRHS(p_scheme, r_model_part, rb);
    }

    is_converged =
        BaseType::mpConvergenceCriteria->PostCriteria(r_model_part, r_dof_set, rA, rDx, rb);
}

This will take away quite a bit of duplication.

Comment on lines 208 to 214
// only initialize conditions, not that this cannot be put in the scheme, as the scheme is required to InitializeNonLinearIteration both elements and conditions
const auto& r_current_process_info = r_model_part.GetProcessInfo();
block_for_each(r_model_part.Conditions(), [&r_current_process_info](Condition& r_condition) {
if (r_condition.IsActive()) {
r_condition.InitializeNonLinearIteration(r_current_process_info);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to do the initialization of the elements here, and leave the initialization of the conditions to new your new scheme (see one of my other suggestions). That would make you new scheme symmetric in terms of initialization and finalization. But it also highlights that the handling of elements is special and needs to be done only once rather than in each nonlinear iteration.

# Conflicts:
#	applications/GeoMechanicsApplication/custom_conditions/U_Pw_normal_lysmer_absorbing_condition.cpp
#	applications/GeoMechanicsApplication/custom_conditions/U_Pw_normal_lysmer_absorbing_condition.hpp
# Conflicts:
#	applications/GeoMechanicsApplication/custom_constitutive/incremental_linear_elastic_law.cpp
#	applications/GeoMechanicsApplication/custom_constitutive/incremental_linear_elastic_law.h
#	applications/GeoMechanicsApplication/custom_strategies/schemes/backward_euler_quasistatic_U_Pw_scheme.hpp
#	applications/GeoMechanicsApplication/tests/cpp_tests/custom_constitutive/test_inremental_linear_elastic_3D_law.cpp
#	applications/GeoMechanicsApplication/tests/cpp_tests/custom_strategies/test_backward_euler_quasistatic_U_Pw_scheme.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants