-
Notifications
You must be signed in to change notification settings - Fork 250
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
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
# Conflicts: # applications/GeoMechanicsApplication/custom_conditions/U_Pw_normal_lysmer_absorbing_condition.hpp # applications/GeoMechanicsApplication/custom_python/add_custom_processes_to_python.cpp # applications/GeoMechanicsApplication/custom_python/add_custom_strategies_to_python.cpp # applications/GeoMechanicsApplication/python_scripts/geomechanics_analysis.py
# Conflicts: # applications/GeoMechanicsApplication/custom_conditions/U_Pw_normal_lysmer_absorbing_condition.cpp
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
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]; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will check
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; |
There was a problem hiding this comment.
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)
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); | ||
} |
There was a problem hiding this comment.
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)
There was a problem hiding this 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, |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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:
KRATOS_TRY; | |
KRATOS_TRY |
// initialize the system matrices and the initial second derivative | ||
this->InititalizeSystemAndState(); | ||
|
||
KRATOS_CATCH(""); |
There was a problem hiding this comment.
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:
KRATOS_CATCH(""); | |
KRATOS_CATCH("") |
{ | ||
KRATOS_TRY; | ||
|
||
BaseType::Initialize(); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A typo:
// 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); |
There was a problem hiding this comment.
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:
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 |
There was a problem hiding this comment.
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.
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); | ||
} | ||
}); |
There was a problem hiding this comment.
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:
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); |
bool PerformIterationCycle(TSystemMatrixType& rA, | ||
TSystemVectorType& rDx, | ||
TSystemVectorType& rb, | ||
TSystemVectorType& rDxTot, | ||
std::vector<Vector>& rNonconvergedSolutions, | ||
unsigned int& rIterationNumber) |
There was a problem hiding this comment.
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.
// 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); | ||
} | ||
}); |
There was a problem hiding this comment.
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.
…vent calculation of internal forces
…uasi static scheme
# 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
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.
Only external forces are updated