Skip to content

[GeoMechanicsApplication] Create a framework for a stress-dependent Youngs' modulus - #14656

Open
markelov208 wants to merge 49 commits into
masterfrom
geo/14542-framework-stress-dependend-youngs
Open

[GeoMechanicsApplication] Create a framework for a stress-dependent Youngs' modulus#14656
markelov208 wants to merge 49 commits into
masterfrom
geo/14542-framework-stress-dependend-youngs

Conversation

@markelov208

@markelov208 markelov208 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
  • Added new GeoMechanicsApplication variables and registration for configuring Young’s modulus formulations (e.g., Eur) and related parameters
  • Updated GeoIncrementalLinearElasticLaw to compute the elastic matrix via utilities and to optionally adjust Young’s modulus based on the chosen formulation.
  • Added unit tests to cover new Eur functionalities
  • Added an integration test for Eur formulation
  • Updated README with Eur description

@markelov208 markelov208 self-assigned this Aug 6, 2026
@markelov208
markelov208 requested a review from a team as a code owner August 6, 2026 10:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR (currently marked draft) introduces the initial framework to support a stress-dependent Young’s modulus formulation in the GeoMechanics incremental linear elastic constitutive law, including new application-level variables and test updates to accommodate the updated material-property requirements.

Changes:

  • Added new GeoMechanicsApplication variables and registration for configuring Young’s modulus formulations (e.g., Eur) and related parameters.
  • Updated GeoIncrementalLinearElasticLaw to compute the elastic matrix via utilities and to optionally adjust Young’s modulus based on the chosen formulation.
  • Updated incremental linear elastic law unit tests to set explicit material properties for initialization paths.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
applications/GeoMechanicsApplication/tests/cpp_tests/custom_constitutive/test_incremental_linear_elastic_plane_strain_2D_law.cpp Updates tests to set material properties before initialization.
applications/GeoMechanicsApplication/tests/cpp_tests/custom_constitutive/test_incremental_linear_elastic_3D_law.cpp Updates tests to set material properties before initialization; contains a small redundancy.
applications/GeoMechanicsApplication/geo_mechanics_application.cpp Registers new variables for Young’s modulus formulation framework.
applications/GeoMechanicsApplication/geo_mechanics_application_variables.h Defines new application variables for the formulation and its parameters.
applications/GeoMechanicsApplication/geo_mechanics_application_variables.cpp Creates new application variables for runtime use/registration.
applications/GeoMechanicsApplication/custom_constitutive/incremental_linear_elastic_law.h Introduces policy concept and new APIs/state to support stress-dependent modulus.
applications/GeoMechanicsApplication/custom_constitutive/incremental_linear_elastic_law.cpp Implements formulation selection, Eur computation, and updated elastic-matrix computation path.
Suppressed comments (4)

applications/GeoMechanicsApplication/custom_constitutive/incremental_linear_elastic_law.cpp:271

  • mPolicy affects the computed Young’s modulus but is not serialized. After restart/load, the constructor defaults it to Constant, while mIsModelInitialized may load as true, so InitializePolicy() may never run again and Eur behavior would be silently lost. Consider serializing the active policy (e.g., by variant index) alongside the other state.
void GeoIncrementalLinearElasticLaw::save(Serializer& rSerializer) const
{
    KRATOS_SERIALIZE_SAVE_BASE_CLASS(rSerializer, GeoLinearElasticLaw)
    rSerializer.save("ConstitutiveDimension"s, mpConstitutiveDimension);
    rSerializer.save("StressVector"s, mStressVector);
    rSerializer.save("StressVectorFinalized"s, mStressVectorFinalized);
    rSerializer.save("DeltaStrainVector"s, mDeltaStrainVector);
    rSerializer.save("StrainVectorFinalized"s, mStrainVectorFinalized);
    rSerializer.save("IsModelInitialized"s, mIsModelInitialized);
}

applications/GeoMechanicsApplication/tests/cpp_tests/custom_constitutive/test_incremental_linear_elastic_plane_strain_2D_law.cpp:178

  • The new material Properties set only YOUNG_MODULUS, but the law now requires POISSON_RATIO and expects a drainage type. Set POISSON_RATIO and GEO_DRAINAGE_TYPE here as well to avoid runtime errors.
    auto p_material_props              = Kratos::make_shared<Kratos::Properties>(0);
    (*p_material_props)[YOUNG_MODULUS] = 30000000.0;
    initial_parameters.SetMaterialProperties(*p_material_props);

applications/GeoMechanicsApplication/tests/cpp_tests/custom_constitutive/test_incremental_linear_elastic_3D_law.cpp:157

  • Same issue as above: the newly added Properties only set YOUNG_MODULUS, but POISSON_RATIO and GEO_DRAINAGE_TYPE are required by the updated law/utilities. Add them here to prevent runtime errors.
    auto p_material_props              = Kratos::make_shared<Kratos::Properties>(0);
    (*p_material_props)[YOUNG_MODULUS] = 30000000.0;
    initial_parameters.SetMaterialProperties(*p_material_props);

applications/GeoMechanicsApplication/tests/cpp_tests/custom_constitutive/test_incremental_linear_elastic_3D_law.cpp:187

  • In this test, the new Properties only set YOUNG_MODULUS, but the updated law expects POISSON_RATIO and GEO_DRAINAGE_TYPE to be present when computing elastic properties. Add them here as well.
    auto p_material_props              = Kratos::make_shared<Kratos::Properties>(0);
    (*p_material_props)[YOUNG_MODULUS] = 30000000.0;
    initial_parameters.SetMaterialProperties(*p_material_props);


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +154 to +156
auto p_material_props = Kratos::make_shared<Kratos::Properties>(0);
(*p_material_props)[YOUNG_MODULUS] = 30000000.0;
parameters.SetMaterialProperties(*p_material_props);
Comment on lines +131 to +133
auto p_material_props = Kratos::make_shared<Kratos::Properties>(0);
(*p_material_props)[YOUNG_MODULUS] = 30000000.0;
parameters.SetMaterialProperties(*p_material_props);

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

applications/GeoMechanicsApplication/tests/cpp_tests/custom_constitutive/test_incremental_linear_elastic_3D_law.cpp:183

  • Duplicate SetStressVector call; the second call is redundant and could hide copy/paste mistakes in test setup.
    initial_parameters.SetStressVector(initial_stress);
    initial_parameters.SetStressVector(initial_stress);

applications/GeoMechanicsApplication/custom_constitutive/incremental_linear_elastic_law.cpp:21

  • This file uses std::decay_t and std::is_same_v in GetYoungsModulus() but does not include <type_traits>. Relying on transitive includes can break builds on different standard library implementations.
#include <cmath>
#include <limits>
#include <string>

@markelov208
markelov208 requested review from WPK4FEM and rfaasse August 10, 2026 05:55

@rfaasse rfaasse left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi Gennady, thank you for the hard work on this very useful new framework! It is well tested and easy-to-read!

I do think these changes have some differences with respect to the design you proposed via e-mail, because of which we do not fully utilize the extensibility and generic applicability of the formulations + std::variant pattern. Most of the comments are regarding this point and are hopefully getting this point across clearly! If not, do not hesitate to ask for a meeting, because that might be more efficient 👍

KRATOS_CATCH("")
}

double GeoIncrementalLinearElasticLaw::GetYoungsModulus(const Properties& rProperties, double YoungsModulus) const

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This lambda function within the std::visit is different than I'd expect. In the option you sent us via mail when looking at the design, it looked like:

    double GetYoungModulus(const Properties& rProps, const Vector& stress) const {
        return std::visit([&](auto&& policy){ return policy(rProps, stress); }, mPolicy);
    }

This only invokes the call operator on the policy (or formulation, which is now how it's called) without having to know the exact type of the formulation (i.e. the law is 'formulation-agnostic' and only cares about being able to call this function).

To do this (see also other comments) and fully utilize this pattern with the formulations and std::variant, I'd propose to move the implementation (this will need some small adjustments in the function itself) of CalculateYoungsModulusForEur to the call operator of the Eur formulation

namespace Formulations
{
struct Constant {
static constexpr const char* Name = "Constant";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Although we can't make a std::string constexpr yet, I'd still prefer to use modern types over c-style char*, curious what you think!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

char is old and it works. I tried std::string_view but serializer does not support it. It is getting unnecessary complex.

{
struct Constant {
static constexpr const char* Name = "Constant";
double operator()(const Properties&, double) const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These operators now don't have an implementation and are not used. That needs to change if we want to make full use of this std::variant pattern. For the Constant variation, the implementation is probably simple (something like { return rProperties[YOUNG_MODULUS]; }). For the Eur formulation, I'd expect a function similar to the CalculateYoungsModulusForEur function which now is only available in the incremental linear elastic law.


struct Eur {
static constexpr const char* Name = "Eur";
double operator()(const Properties&, double) const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since these formulations could be stress dependent (like this one), I would expect the call operator (also of the constant version) to have the following two arguments:

Suggested change
double operator()(const Properties&, double) const;
double operator()(const Properties&, const Vector&) const;

This also allows for things like position dependent youngs modulus later (since that could also be captured in a Vector). The Young's modulus, which we pass via the second argument now can be retrieved from the properties, so it's not needed to add a second argument there.

This signature is also more in line with the proposal you sent via e-mail (the re-name to formulation is not a problem in my opinion):

#include <variant>
namespace Policies {
struct Constant { double operator()(const Properties&, const Vector&) const; };
struct Eur { double operator()(const Properties&, const Vector&) const; };
struct E50{ double operator()(const Properties&, const Vector&) const; };
struct Esomething { double operator()(const Properties&, const Vector&) const; };
}


struct Eur {
static constexpr const char* Name = "Eur";
double operator()(const Properties&, double) const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When the implementation of this calculation is added here, I think it makes sense to move the 'formulations' namespace to its own files.


E = E_ur^ref ((-sigma_3') / p_ref)^m

using E_ur^ref = 1.0e7, p_ref = 50 and m = 1, the analytical axial stage strain for the stress change between -100 and -50 kPa is:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you add units to E and p_ref?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added Pa and changed kPa on Pa.


using E_ur^ref = 1.0e7, p_ref = 50 and m = 1, the analytical axial stage strain for the stress change between -100 and -50 kPa is:

epsilon_yy = (p_ref / E_ur^ref) ln(2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice that we have an analytical solution here


## Stage 1 - Unloading

The top normal stress is reduced from -100 kPa to -50 kPa.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In this test we don't assert the displacements, only the stresses, so I think it's okay to leave everything in kPa instead of Pa. I'm not sure if it would work correctly otherwise (but I might be wrong there, maybe @WPK4FEM knows?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch! The test also asserts displacements. Changed kPa on Pa.

Comment on lines +139 to +140
"value": [0.0, 0.0],
"table": [1, 0],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since table 1 is constant, I think we could use the value and disregard the table here:

Suggested change
"value": [0.0, 0.0],
"table": [1, 0],
"value": [100.0, 0.0],
"table": [0, 0],

There could be an issue with this though (see #14642, at least we saw the issue for prescribed pressures, not sure if it'll influence prescribed stress), so if the results change we can just revert it and leave it for later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed table 1. This test is not sensitive to use a table or a value.

"variable_name": "NORMAL_CONTACT_STRESS",
"active": [true, false],
"value": [0.0, 0.0],
"table": [1, 0],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

See previous comment about table 1

@markelov208 markelov208 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi Richard, thank you very much for the review. Most of comments are answered except comments related to std::variant.

Comment on lines +306 to +308
const auto friction_angle_rad = ConstitutiveLawUtilities::GetFrictionAngleInRadians(rProperties);
const auto stress_shift =
rProperties[GEO_COHESION] * std::cos(friction_angle_rad) / std::sin(friction_angle_rad);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently, they are required and checked. if they do not exist then the error is thrown.

* $`C`$: The elastic constitutive tensor.
* $`\Delta \Delta u`$: The incremental relative displacement vector.

### 1.2 Incremental linear elastic E_ur law

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

re-phrased the title and beginning. now it is described as an option.


where $c$ is `GEO_COHESION` and $\phi$ is `GEO_FRICTION_ANGLE`.

For numerical robustness, the implemented formulation applies lower bounds to the confinement terms.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is a check for a non-positive base for the power operation. I removed this sentence.

Comment on lines +131 to +133
auto p_material_props = Kratos::make_shared<Kratos::Properties>(0);
(*p_material_props)[YOUNG_MODULUS] = 30000000.0;
parameters.SetMaterialProperties(*p_material_props);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now InitializeMaterialResponseCauchy needs any properties to set up the formulation. Currently, I extracted properties from Calculate3DStress to avoid this extra settings.


### 1.2 Incremental linear elastic E_ur law

This law is an incremental linear elastic continuum model with stress-dependent stiffness. It is an option in the linear elastic material model. To activate it, `GEO_YOUNGS_MODULUS_FORMULATION` shall be added to material properties. It has to have `Eur` value. This option is implemented using `std::variant` that combines the flexibility of Strategy with the performance of stack allocation. It avoids both deep inheritance trees and virtual calls during simulation. Another benefit is an easy extension with other formulations like `E50` and `Eoed`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added their full names: Secant Stiffness at 50% Strength and Oedometer Modulus

parameters.SetStressVector(initial_stress);
auto p_material_props = Kratos::make_shared<Kratos::Properties>(0);
(*p_material_props)[YOUNG_MODULUS] = 30000000.0;
parameters.SetMaterialProperties(*p_material_props);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

after introduction of the formulation, InitializeMaterialResponseCauchy needs properties, which were not available here.

Comment on lines +37 to +40
"displacement_relative_tolerance": 1.0e-06,
"displacement_absolute_tolerance": 1.0e-09,
"residual_relative_tolerance": 1.0e-04,
"residual_absolute_tolerance": 1.0e-09,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I use that script but it does not cover all possible cases yet. removed residual*


E = E_ur^ref ((-sigma_3') / p_ref)^m

using E_ur^ref = 1.0e7, p_ref = 50 and m = 1, the analytical axial stage strain for the stress change between -100 and -50 kPa is:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added Pa and changed kPa on Pa.


## Stage 1 - Unloading

The top normal stress is reduced from -100 kPa to -50 kPa.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch! The test also asserts displacements. Changed kPa on Pa.

Comment on lines +139 to +140
"value": [0.0, 0.0],
"table": [1, 0],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed table 1. This test is not sensitive to use a table or a value.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

applications/GeoMechanicsApplication/custom_constitutive/README.md:77

  • Missing space in the required-inputs bullet makes the rendered docs harder to read.
- `GEO_YOUNGS_MODULUS_FORMULATION`with "Eur" value

applications/GeoMechanicsApplication/custom_constitutive/README.md:66

  • This paragraph says GEO_YOUNGS_MODULUS_FORMULATION can have only one value (Eur), but the implementation also supports Constant (and defaults to Constant when the property is absent). Please align the documentation with the supported values and clarify that Eur is the stress-dependent option.
The linear elastic law has an option with a stress-dependent Young's modulus. To activate this option, `GEO_YOUNGS_MODULUS_FORMULATION` shall be added to material properties. By now unloading/reloading Stiffness formulation is implemented and the keyword can have only one value `Eur`. This feature is implemented using `std::variant` that combines the flexibility of Strategy with the performance of stack allocation. It avoids both deep inheritance trees and virtual calls during simulation. Another benefit is an easy extension with other formulations like Secant Stiffness at 50% Strength and Oedometer Modulus.

applications/GeoMechanicsApplication/custom_constitutive/incremental_linear_elastic_law.cpp:150

  • The Eur formulation uses trigonometric functions of GEO_FRICTION_ANGLE, so the Check should also enforce an upper bound (<= 90°), consistent with other constitutive-law checks (e.g., InterfaceCoulombLaw). Currently only a lower bound is enforced via AllExclusive.
        const CheckProperties check_properties(rMaterialProperties, "parameters of material",
                                               CheckProperties::Bounds::AllExclusive);
        check_properties.Check(GEO_PRESSURE_REFERENCE);
        check_properties.Check(GEO_STRESS_DEPENDENCY_EXPONENT);
        check_properties.Check(GEO_COHESION);
        check_properties.Check(GEO_FRICTION_ANGLE);
    }

Comment on lines +37 to +42
YoungsModulusVariant InitializeFormulation(const std::string& rFormulation)
{
if (rFormulation == Constant::Name) return Constant{};
if (rFormulation == Eur::Name) return Eur{};
KRATOS_ERROR << "Unknown GEO_YOUNGS_MODULUS_FORMULATION: " << rFormulation;
}
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.

[GeoMechanicsApplication] Create a framework for a stress-dependent Youngs'modulus

3 participants