Skip to content

Fix instability of force-length coupling in electromechanics, and uniform formulation of active stress tensor - #650

Open
michelebucelli wants to merge 18 commits into
SimVascular:mainfrom
michelebucelli:experiment/implicit-force-length-feedback-newton
Open

michelebucelli wants to merge 18 commits into
SimVascular:mainfrom
michelebucelli:experiment/implicit-force-length-feedback-newton

Conversation

@michelebucelli

Copy link
Copy Markdown
Collaborator

Fixes #635; fixes #641.

Current situation

  1. Different passive material models apply the active stress in different ways, although in principle active and passive models could be fully independent (see discussion on Uniform application of active stress across material models #635);
  2. Explicit coupling between active stress and structural mechanics through the fiber stretch $\lambda$ may give rise to temporal instabilities (see discussion on Temporal instability in electromechanics force-fiber-stretch feedback #641).

Release Notes

The PR addresses both issues above. Active tension, in particular, is now evaluated implicitly, and appropriate terms are added to the (u)struct matrices to account for the coupling. A detailed list of changes follows.

  1. Modified compute_pk2cc from mat_models.cpp so that all material models use the same formulation for the active stress tensor.

  2. Added the purely virtual function ActiveStress::compute_active_tension_derivative_local to compute the partial derivative of the active tension $T_\text{act}$ with respect to fiber stretch $\lambda$. The function was implemented in concrete models, with all models returning 0.0 except for ActiveStressRegazzoni.

  3. Added the helper function ActiveStress::compute_tension, to return active tensions along principal directions and their derivatives with respect to $\lambda$, bundled in an object of the new type ActiveStress::ActiveTension, introduced to be easily passed to downstream functions.

  4. The previous code computed active tension at integration points by

    1. projecting $\lambda$ from integration points to mesh nodes with a lumped $L^2$ projection;
    2. computing $T_\text{act}$ at mesh nodes;
    3. interpolating $T_\text{act}$ at integration points.

    In this framework, computing the tangent contribution of active stress would have required computing the derivative of $T_\text{act}$ wrt displacement, which would have meant differentiating through the $L^2$ projection. This would be a nonlocal, and thus nontrivial, operation.

    The new code evaluates $\lambda$ and $T_\text{act}$ directly at integration points. The nodal evaluation of $T_\text{act}$ is kept for the sake of output.

  5. Added helper class ActiveStress::evaluator to compute active tension and its derivative at integration points.

  6. Modified compute_pk2cc from mat_models.cpp to also account for the derivative of active tension when computing the tensor Dm. This also required modifying cc_to_voigt_eigen to relax its symmetry assumption.

  7. By default, the active stress model state is still advanced explicitly (i.e. implicit coupling is only used to evaluate the active tension function). Optionally, it can be made implicit by setting the XML parameter Active_stress/Implicit_state_coupling to true. This coupling does not contribute to the tangent, and is thus a fixed-point coupling between the two models. I implemented this experimentally, but opted to keep it in view of exploring its effect on force-velocity coupling stability (see Fiber stretch rate always evaluated to zero for active stress coupling #639).

  8. Refactoring of Integrator to accomodate for the implicit coupling.

  9. Updated all downstream consumers of active stress to accomodate for changed interfaces.

Documentation

All modified/new classes or functions were given Doxygen documentation.

Testing

A few tests (electromechanics/slab/Regazzoni, struct/tensile_adventitia_Guccione_active, ustruct/LV_Guccione_active) failed due to the change to the active stress formulation. I have regenerated their reference solution, as per the discussion in #635.

Besides that, all automatic tests are passing.

Additional context

  1. In principle, the uniform formulation of active stress and the instabilities are independent changes; however, fixing the instabilities required evaluating the tangent of active stress, for which having a uniform formulation was helpful. This is why the two changes are merged into a single PR.
  2. A unified formulation for active stress will also be beneficial when we will introduce the option to switch between different active stress tensor definitions, as discussed here.

Code of Conduct & Contributing Guidelines

@michelebucelli
michelebucelli requested review from aabrown100-git, dseyler, javijv4 and kko27 and a lite review from Copilot September 21, 2026 16:11
@michelebucelli michelebucelli self-assigned this Sep 21, 2026

@claude claude Bot 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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Unresolved critical restart-state and directional-validation issues, along with moderate solver coupling inconsistencies.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity · 1 Medium severity

Open (3)
What changed in this PR

This pull request standardizes active-stress formulation and improves electromechanical coupling stability through quadrature-point evaluation, stretch derivatives, and optional implicit state coupling.

Changes:

  • Unifies active-stress evaluation across material models.
  • Adds active-tension derivatives and quadrature-point evaluators.
  • Updates solver integration, tangents, postprocessing, tests, and reference outputs.
File Reviewed change / status
tests/​unitTests/​material_model_tests/​test_material_common.h Updates material-test helpers for the new active-stress API.
tests/​cases/​ustruct/​LV_Guccione_active/​result_001.vtu Regenerated regression reference output.
tests/​cases/​struct/​tensile_adventitia_Guccione_active/​result_002.vtu Regenerated regression reference output.
tests/​cases/​electromechanics/​slab/​result_Regazzoni_001.vtu Regenerated regression reference output.
Code/​Source/​solver/​ustruct.h Integrates active stress into incompressible assembly interfaces.
Code/​Source/​solver/​ustruct.cpp Passes evaluated active stress into incompressible assembly.
Code/​Source/​solver/​sv_struct.h Integrates active stress into structural assembly interfaces.
Code/​Source/​solver/​sv_struct.cpp Passes evaluated active stress into structural assembly.
Code/​Source/​solver/​post.cpp Uses quadrature-point active stress during postprocessing.
Code/​Source/​solver/​Parameters.h Declares implicit state-coupling configuration.
Code/​Source/​solver/​Parameters.cpp Implements implicit state-coupling configuration.
Code/​Source/​solver/​mat_models.h Updates constitutive active-stress APIs.
Code/​Source/​solver/​mat_models.cpp Applies the unified active stress and tangent. Critical (2 votes): sheet-normal stress with one fiber can access fl.col(1) without validating the number of fiber directions. Nit (1 vote): add nonzero active-tension finite-difference tangent coverage.
Code/​Source/​solver/​Integrator.h Declares stretch and active-state coupling helpers.
Code/​Source/​solver/​Integrator.cpp Integrates stretch computation and implicit state updates. Moderate (2 votes): stretch rate is computed before the displacement predictor, disabling Regazzoni force–velocity feedback. Moderate (1 vote): final Newton correction can leave mechanics and active state inconsistent.
Code/​Source/​solver/​fsi.cpp Updates FSI structural assembly interfaces.
Code/​Source/​solver/​ActiveStressUniformUnsteady.h Implements the zero direct stretch derivative.
Code/​Source/​solver/​ActiveStressUniformSteady.h Implements the zero direct stretch derivative.
Code/​Source/​solver/​ActiveStressRegazzoni.h Declares Regazzoni stretch-derivative support.
Code/​Source/​solver/​ActiveStressRegazzoni.cpp Implements Regazzoni stretch-derivative support.
Code/​Source/​solver/​ActiveStressNashPanfilov.h Implements the zero direct stretch derivative.
Code/​Source/​solver/​ActiveStress.h Defines active-tension data, evaluator, and lifecycle APIs.
Code/​Source/​solver/​ActiveStress.cpp Implements state gathering and tension evaluation. Critical (1 vote): restart and bin-to-VTK paths do not restore model state arrays, so freshly initialized state may be used instead of checkpoint state.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Code/Source/solver/ActiveStress.cpp
Comment thread Code/Source/solver/mat_models.cpp
Comment thread Code/Source/solver/Integrator.cpp
@codecov

codecov Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.55814% with 193 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.15%. Comparing base (c3f0bb8) to head (40df8bc).

Files with missing lines Patch % Lines
Code/Source/solver/post.cpp 72.35% 175 Missing ⚠️
Code/Source/solver/ActiveStressRegazzoni.cpp 71.42% 4 Missing ⚠️
Code/Source/solver/Integrator.cpp 94.11% 4 Missing ⚠️
Code/Source/solver/ActiveStressUniformSteady.h 0.00% 2 Missing ⚠️
Code/Source/solver/initialize.cpp 0.00% 2 Missing ⚠️
Code/Source/solver/sv_struct.cpp 81.81% 2 Missing ⚠️
Code/Source/solver/ustruct.cpp 77.77% 2 Missing ⚠️
Code/Source/solver/fsi.cpp 80.00% 1 Missing ⚠️
Code/Source/solver/mat_models.cpp 97.67% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #650      +/-   ##
==========================================
+ Coverage   73.08%   73.15%   +0.06%     
==========================================
  Files         268      268              
  Lines       40270    40336      +66     
  Branches     6738     6752      +14     
==========================================
+ Hits        29433    29507      +74     
+ Misses      10594    10586       -8     
  Partials      243      243              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread Code/Source/solver/Integrator.cpp Outdated
Comment on lines +721 to 724
// @todo[michelebucelli] Tfa is unused in this call, and it should
// probably be removed from the function signature. Active stress is
// added below in any case.
CANNModel.computeInvariantsAndDerivatives<nsd>(C, fl, nfd, J2d, J4d, Ci, Idm, Tfa, N1, psi, Inv, dInv, ddInv);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Is there anyone more familiar with this material model that can confirm removing Tfa from the arguments here is safe? The code would be equivalent, I'm not sure if there's some design reason or planned development that would justify keeping it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@msbazzi and @divya-adil should weigh in here. they are actively working on the CANN model implementation for more than one paper - so yes it is important and we need to keep it. They can explain the structure.

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.

@michelebucelli, you are correct that Tfa is not currently used in computeInvariantsAndDerivatives, so in the present CANN implementation, removing it from this particular call would not change the results.

The reason I would like to preserve the active-tension capability is because of where we are taking the CANN model next. We have been developing the CANN framework as a more flexible constitutive representation for vascular tissue, particularly for vein mechanics, rather than treating it only as another passive material model. The next step is to use the CANN-based constitutive description within a growth and remodeling framework.

My current plan is to build on the Fortran-based constrained-mixture/FSG implementation we discussed in #2, while making the changes needed to make it clearer and more general within svMultiPhysics. In that setting, the passive constitutive response and active smooth-muscle tension will ultimately need to coexist as part of the evolving wall mechanics.

That said, I’m also open to suggestions on the cleanest way to handle this. If Tfa does not need to be passed into computeInvariantsAndDerivatives yet, we can refactor it while keeping the active-tension pathway for the planned G&R implementation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@msbazzi thanks for explaining!

I think that, for the purpose of this PR, the question to address is whether the CANN-based stress tensor evaluation pipeline returns (now or in the planned future) the passive stress tensor only (option A) or the sum of passive and active stress tensor (option B).

Under option A, keeping Tfa as an input argument would mean that the passive stress can depend on the active tension, which is maybe counterintuitive but plausible in principle, and the code of this PR can be left as it is. Tfa can maybe be removed in the interest of code hygiene, but it is somewhat of a minor issue.

On the other hand, under option B the code of this PR is incorrect, because the lines after the switch add active stress regardless of the passive constitutive model, and the CANN-based model would end up having the active stress added to it twice (perhaps with two different formulations even).

I am not familiar with the CANN-based model, so there may be something I'm missing, but I think option A would be the cleanest, both conceptually and in the code, because it neatly follows the additive decomposition of stress which is behind the active stress formalism, and keeps passive and active constitutive models separate, which is a good step towards #178. However, if option B is necessary, then I'd have to rework this PR a little bit to accomodate for that.

I also note that the CANN model currently evaluates the stress as the derivative of an energy function (through CANNModel.evaluate), but active stress in general cannot be expressed in that way (I think), which makes me lean even more towards option A.

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.

@michelebucelli, currently (and I would say likely for the near future), CANN-based models are used only to describe passive stress, while active stress will come from a different formulation, given that I agree with you that A is the best and cleanest option.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I see, thanks! I think the current code for this PR is mostly fine then.

I propose we remove the argument Tfa from this call, since it is currently unused and it might be a bit counterintuitive (as in, it might suggest option B to the reader, like it did for me), and we can bring it back if necessary in the future.

@kko27 kko27 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.

@michelebucelli Thanks for making these changes! This definitely simplifies many of the function signatures. I mostly have clarification questions and comments related to future PRs.

std::shared_ptr<IonicModel> ionic_model;
};

/// @brief Cardiac electromechanics model type

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.

@michelebucelli do you know why we have this cemModelType class? It looks like legacy code for stretch activated currents...

/// @brief Compute active tension along fibers, sheets and sheet normals at
/// every mesh node.
//
void active_tension(const ComMod &com_mod, const int iEq, const mshType &lM,

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.

@michelebucelli. Will the eventual goal be to make active_tension, fib_stretch, and fib_stretch_rate purely post-processing functions (just for writing to output files)? I see active_tension is already just for post-processing, and fib_stretch_rate is a bug if it is used in the integrator (as discussed previously).

// Compute body forces
set_body_forces();

// Implicit state coupling of the active stress: re-advance the state of the

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.

@michelebucelli Just wanted to check if this is intended behavior. So if one of my domains has implicit_state_coupling set to true, then all of my domains get treated implicitly

// If we need to compute fiber stretch, we iterate through all meshes, compute
// the stretch for each mesh, and then copy the mesh-local resulting vector
// into the global vector.
if (have_active_stress || need_fiber_stretch) {

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.

We may at some point want to figure out how much computation time is spent from calling fib_stretch and fib_stretch_rate, especially for larger meshes. Maybe for implicit coupling, we don't always have to call this at every iteration

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.

@michelebucelli Does this mean that by default, all active stress models will treat the direct dependence on active tension implicitly? (i.e., there is no need to specify a xml parameter for implicit coupling, unless I want the states to implicitly depend on the fiber stretch).

double Ja;
mat_models::compute_pk2cc(com_mod, cep_mod, dmn, F, nFn, fN, ya_g_f, ya_g_s,
ya_g_n, S, Dm, Ja);
mat_models::compute_pk2cc(com_mod, cep_mod, dmn, F, nFn, fN, Ta, S, Dm, Ja);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we move the active stress contribution outside mat_models::compute_pk2cc? I think this would be beneficial because it would isolate the use of ' mat_models ' to only passive constitutive relationships, and because then adding the active component would follow a similar logic to adding the viscous terms (S = S + Svis), which would make the different contributions to PK2 very clear in the struct_* functions.

That being said, I am guessing the reason not to do this is to avoid recomputing the structural tensors Hff, Hss, Hnn. If the loss in time is too important, maybe we should leave it as is and revisit this once the struct/ustruct physics are refactored.

@javijv4

javijv4 commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

@michelebucelli, another question: this PR adds derivatives to the active stress models. Should we add tests for these terms as well? I think we can do this in another PR, but it might be good to include this in future plans.

This branch has not been deployed

No deployments
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.

Temporal instability in electromechanics force-fiber-stretch feedback Uniform application of active stress across material models

6 participants