Implementation of the Gonzalez time scheme for dynamic hyperelasticity

I would like to implement in GetFEM the following time discretization scheme, proposed by Oscar Gonzalez in 2000, in order to conserve elastic energy over a time step for a hyperelastic body in dynamic regime. The method hinges on the definition of a discrete derivative of the hyperelastic energy density to express the second Piola-Kirchhoff tensor.

See the following paper, equations (10) to (12) on page 4:

https://hal.science/hal-01363585v1/document

I was wondering if the implementation of this scheme in GetFEM is doable… Would it require the analytical computation of all tangent matrices or not? Can I just solve a nonlinear elasticity problem at each time step in a more automated way?

Implementing all tangent matrices seems a very hard and time-consuming task…

Thanks.

Dear Francesco,
No, there is no need to compute explicitely the tangent terms. For this kind of problem with multiples variables, you need to express the weak formulation in the term of GetFEM weak form language (GWFL) corresponding to the problem to be solved at each time step. GetFEM will compute the tangent term automatically.

Best regards,

Yves

Thank you very much, Yves. We did exchange a bit by email on this already, but I also wanted to share this on the GetFEM forum, just to make sure that my question was clear, and perhaps to let it know to the whole GetFEM community.

My main concern about Gonzalez’ scheme was the fact that, unlike Newmark, finite difference, or whatever else standard time discretization, it is in some sense “coupled” with the underlying space discretization, and, as such, I believe that its implementation is not independent of the finite element scheme at hand.

Anyway, I will check this out.

Have a nice day.

Hi Francesco, I would dare to say that Eqs. (10)-(12) are very trivial to implement, using GWFL in GetFEM.

It has become a habit in science to introduce and try to give meaning to a ton of intermediate quantities, but if we eliminate all those and keep only some basic quantities, Eqs. (11),(12) should be equivalent to

\begin{split} S(\varphi_n,\varphi_{n+1}) =~&S\left((C_{n}+C_{n+1})/2\right)\\+ &\dfrac{2W(C_{n+1})-2W(C_n)-S\left((C_{n}+C_{n+1})/2\right):\left(C_{n+1}-C_{n}\right)))} {\left\lVert C_{n+1}-C_{n}\right\rVert^2} \left(C_{n+1}-C_{n}\right) \end{split}

There is nothing special about this expression, it is a quite simple expression that you can express in GWFL.

You can see a similar example with the implementation of large strain viscoelasticity in this file

https://git.savannah.nongnu.org/gitweb/?p=getfem.git;a=blob;f=contrib/continuum_mechanics/QLV_viscoelasticity.py

The theory is from
https://royalsocietypublishing.org/doi/10.1098/rspa.2014.0058

but a better help for that code is found in the pdf file in

https://www.researchgate.net/publication/353917920_QLV_viscoelasticity_implementation_in_GetFEM

The fact that in your case there are 2 fields, velocity and displacements, does not make the implementation more difficult. It is just about typing the equations in the GWFL format correctly and you will have a working implementation.

that’s more or less, how I would type these equations in GetFEM

# Model
md = gf.Model("real")
md.add_fem_variable("u", mfu)    # displacements
md.add_fem_variable("v", mfu)    # velocities
md.add_fem_data("u_old", mfu)
md.add_fem_data("v_old", mfu)

md.add_initialized_data("K", E/(3*(1-2*nu)))
md.add_initialized_data("G", E/(2*(1+nu)))

md.add_macro("F", "Id(3)+Grad(u)")
md.add_macro("Fold", "Id(3)+Grad(u_old)")
md.add_macro("C", "F'F") #"Right_Cauchy_Green(F)")
md.add_macro("Cold", "Fold'*Fold") #"Right_Cauchy_Green(Fold)")

md.add_macro("Favg", "0.5*(F+Fold)")
md.add_macro("Cavg", "0.5*(C+Cold)")

md.add_macro("S(CC)", "K/2*log(Det(CC))*Inv(CC)+G*pow(Det(CC),-4/3)*(Id(3)-1/3*Trace(CC)*Inv(CC))")
md.add_macro("W(CC)", "K/8*sqr(log(Det(CC)))+G/2*(pow(Det(CC),-4/3)*(Trace(CC))-3)")

md.add_macro("SS", "S(Cavg)+(2*W(C)-2*W(Cold)-S(Cavg):(C-Cold))/Norm_sqr(C-Cold)*(C-Cold)")

# elasticity + inertia
md.add_initialized_data("rho", 500.)
md.add_initialized_data("dt", 0.1)
md.add_linear_term(mim, "(u-uold-dt/2*(v+vold)).Test_u")
md.add_nonlinear_term(mim, "(rho/dt*(v-vold)-(b+bold)/2).Test_v+(Favg*SS):Grad(Test_v)")
md.add_linear_term(mim, "((f+fold)/2).Test_v", RG_SURF_LOAD)

Thank you very much, Konstantinos, for all these infos. I am doing my first steps with GetFEM and did not expect such a compact and elegant Python syntax was possible to implement such formulations. I mostly went through the Python documentation (where bricks are described, predominantly), but as mentioned there, this is not independent from the C++ one, where the GWFL is explained in detail.

I will be using GetFEM on a remote HPC server for now, until the clang compatibility is fixed, in order to use it also locally on an Apple Silicon Mac. By the way, I tried to completely override clang with gcc and g++, uninstalled and rebuilt Python, but even though I succeeded in setting those two as the default compilers, the header when I type python in my Terminal still mentions clang, unfortunately.

Thank you again!

So, I tried to implement the Gonzalez scheme as follows, but at every time step, the residual is “not a number”… This might seem like a naïve/awkward situation, but I am not yet an advanced user of GetFEM, and I don’t know the correct way to update the variables in the time loop. The boundary conditions seem to be ok, on the other hand.

The situation is that of a 3D hyperelastic beam, characterized by the constitutive law given in the previous message, with a square section (of side 1) and axis aligned with the y-axis, of length L=4. The beam is clamped on the left end section (y=0) and loaded on the right end section (y=4) by a constant surface load parallel to the section, which is why I declared both f_old and f outside of the time loop (both are constant and equal to -10).

I made a grep in the GetFEM directory to try to find an example where analogous time loops are implemented, but every time I found “automated” things like Previous_Dot_u or md.shift_variables_for_time_integration. I would like to use less automated procedures, though, as described in the code below.

Is that possible? If yes, what am I missing?

Thanks for your help!

import numpy as np
import getfem as gf

def SetMeshRegionFromCondition(mesh,regionTag,condition):
    pidCondition=np.compress(condition, range(mesh.nbpts()))
    facesFromCondition=mesh.faces_from_pid(pidCondition)
    mesh.set_region(regionTag,facesFromCondition)


gf.util('trace level', 1)


md = gf.Model("real")

E =   1e2                    # Young's modulus [Pa]
nu =  0.49                   # Poisson's ratio [-]
kappa = E/(3*(1-2*nu))
mu = E/(2*(1+nu))
rho = 500.0                    # density

nbElems = 10
meshSize = 1/nbElems
fem_order = 2

# traction_force = 10 #Surfacic load on the Neumann boundary of the body in the vertical direction
# surfaceLoad=[0, 0, -traction_force]
# volumeLoad=[0, 0, 0]

m=gf.Mesh('regular simplices', np.arange(0,1+meshSize,meshSize), 
        np.arange(0,4+meshSize,meshSize),np.arange(0,1+meshSize,meshSize))

meshPoints=m.pts()
xCoord,yCoord,zCoord=meshPoints[0,:],meshPoints[1,:],meshPoints[2,:]

m.export_to_pos('Gonzalez.pos')

# Integration method
mim=gf.MeshIm(m, gf.Integ('IM_TETRAHEDRON(5)'))


# Create a MeshFem for u field of dimension 3 (i.e. a vector field)
mfu = gf.MeshFem(m,3) # Displacement
mfrhs = gf.MeshFem(m,3) # Right-hand side


# Choice of the finite element method
mfu.set_classical_fem(fem_order)
mfrhs.set_classical_fem(fem_order)

# mff=gf.MeshFem(mesh,1) # plot scalar Von-mises field

#Time related parameters
steps = 100
T_max = 1.0
dt = T_max/steps

#Dirichlet and Neumann conditions

#Boundaries definition
cleft=(abs(yCoord) < 1e-6)
cright=(abs(yCoord-4) < 1e-6)


#Boundaries creation
DIRICHLET_BOUNDARY = 1
NEUMANN_BOUNDARY = 2

SetMeshRegionFromCondition(mesh=m,regionTag=DIRICHLET_BOUNDARY,condition=cleft)
SetMeshRegionFromCondition(mesh=m,regionTag=NEUMANN_BOUNDARY,condition=cright)

md.add_fem_variable("u", mfu)    # displacements
md.add_fem_variable("v", mfu)    # velocities
md.add_fem_data("u_old", mfu)
md.add_fem_data("v_old", mfu)

# Initial conditions
U_0 = mfu.eval('[0, 0, 0]')
V_0 = 0.*U_0

md.set_variable('u_old', U_0)
md.set_variable('v_old', V_0)

f = mfrhs.eval('[0, 0, -10]')
f_old = mfrhs.eval('[0, 0, -10]')

md.add_initialized_data("K", E/(3*(1-2*nu)))
md.add_initialized_data("G", E/(2*(1+nu)))

md.add_macro("F", "Id(3)+Grad(u)")
md.add_macro("F_old", "Id(3)+Grad(u_old)")
md.add_macro("C", "F'*F") # "Right_Cauchy_Green(F)")
md.add_macro("C_old", "F_old'*F_old") # "Right_Cauchy_Green(F_old)")

md.add_macro("F_avg", "0.5*(F+F_old)")
md.add_macro("C_avg", "0.5*(C+C_old)")

md.add_macro("S(CC)", "K/2*log(Det(CC))*Inv(CC)+G*pow(Det(CC),-4/3)*(Id(3)-1/3*Trace(CC)*Inv(CC))")
md.add_macro("W(CC)", "K/8*sqr(log(Det(CC)))+G/2*(pow(Det(CC),-4/3)*(Trace(CC))-3)")

md.add_macro("SS", "S(C_avg)+(2*W(C)-2*W(C_old)-2*S(C_avg):(C-C_old))/Norm_sqr(C-C_old)*(C-C_old)")

# elasticity + inertia
md.add_initialized_data("rho", rho)
md.add_initialized_data("dt", dt)
md.add_linear_term(mim, "(u-u_old-dt/2*(v+v_old)).Test_u")
md.add_nonlinear_term(mim, "rho/dt*(v-v_old).Test_v + (F_avg*SS):Grad(Test_v)")

# boundary conditions
md.add_Dirichlet_condition_with_multipliers(mim, 'u', 0, DIRICHLET_BOUNDARY)
md.add_initialized_fem_data('f_old', mfrhs, f_old)
md.add_initialized_fem_data('f', mfrhs, f)
md.add_source_term_brick(mim, 'v', '(f+f_old)/2', NEUMANN_BOUNDARY)
# md.add_linear_term(mim, "((f+f_old)/2).Test_v", NEUMANN_BOUNDARY)

# Time loop
for timeStepIndex,timeStep in enumerate(np.arange(0.,T_max+dt,dt)):
    print('Time step: %g' % timeStep)
    md.solve('max_res', 1E-6, 'very noisy')
    U = md.variable('u')
    V = md.variable('v')
    #Post processing of the solution
    mfu.export_to_vtk('Displacement_%d.vtk' % timeStepIndex, mfu, U, 'Displacement')

    # Update u_old and v_old
    md.set_variable('u_old', U)
    md.set_variable('v_old', V)

ok, just replace

md.add_macro("SS", "S(C_avg)+(2*W(C)-2*W(C_old)-2*S(C_avg):(C-C_old))/Norm_sqr(C-C_old)*(C-C_old)")

with

md.add_macro("SS", "S(C_avg)+(2*W(C)-2*W(C_old)-2*S(C_avg):Normalized(C-C_old))*Normalized(C-C_old)")

Some more recommendations

  • Do not make your own region detection methods, use GetFEM’s outer_faces_with_direction and outer_faces_in_box
  • Naming something mfrhs is not a very helpful modeling strategy. In nonlinear problems there is no left and right hand side, there is only left hand side equal to zero
  • Instead of vtk use vtu, and if you have big files, compress them afterwards with this script
  • Avoid using mf.eval method, it is outdated, use model.interpolation(“…”,mf) instead
  • For storing data you can do this directly on integration points directly with MeshImData object instead of MeshFEM

An adapted version of your script

import numpy as np
import getfem as gf


gf.util('trace level', 1)


md = gf.Model("real")

E =   1e2                    # Young's modulus [Pa]
nu =  0.49                   # Poisson's ratio [-]
rho = 500.0                    # density

nbElems = 10
meshSize = 1/nbElems
fem_order = 2

# traction_force = 10 #Surfacic load on the Neumann boundary of the body in the vertical direction
# surfaceLoad=[0, 0, -traction_force]
# volumeLoad=[0, 0, 0]

m=gf.Mesh('regular simplices', np.arange(0,1+meshSize,meshSize), 
        np.arange(0,4+meshSize,meshSize),np.arange(0,1+meshSize,meshSize))

m.export_to_pos('Gonzalez.pos')

# Integration method
mim=gf.MeshIm(m, gf.Integ('IM_TETRAHEDRON(5)'))


# Create a MeshFem for u field of dimension 3 (i.e. a vector field)
mf = gf.MeshFem(m,3) # Displacement

# Choice of the finite element method
mf.set_classical_fem(fem_order)

# mff=gf.MeshFem(mesh,1) # plot scalar Von-mises field

#Time related parameters
steps = 100
T_max = 1.0
dt = T_max/steps

#Dirichlet and Neumann conditions
#Boundaries creation
DIRICHLET_BOUNDARY = 1
NEUMANN_BOUNDARY = 2

m.set_region(DIRICHLET_BOUNDARY,
             m.outer_faces_with_direction([0., -1., 0.], np.pi/10)) 
m.set_region(NEUMANN_BOUNDARY,
             m.outer_faces_with_direction([0., 1., 0.], np.pi/10))

md.add_fem_variable("u", mf)    # displacements
md.add_fem_variable("v", mf)    # velocities
md.add_fem_data("u_old", mf)
md.add_fem_data("v_old", mf)

# Initial conditions
md.set_variable('u_old', md.interpolation("[0,0,0]", mf))
md.set_variable('v_old', md.interpolation("[0,0,0]", mf))

md.add_initialized_data("K", E/(3*(1-2*nu)))
md.add_initialized_data("G", E/(2*(1+nu)))

md.add_macro("F", "Id(3)+Grad(u)")
md.add_macro("F_old", "Id(3)+Grad(u_old)")
md.add_macro("C", "F'*F") # "Right_Cauchy_Green(F)")
md.add_macro("C_old", "F_old'*F_old") # "Right_Cauchy_Green(F_old)")

md.add_macro("F_avg", "0.5*(F+F_old)")
md.add_macro("C_avg", "0.5*(C+C_old)")

md.add_macro("S(CC)", "K/2*log(Det(CC))*Inv(CC)+G*pow(Det(CC),-4/3)*(Id(3)-1/3*Trace(CC)*Inv(CC))")
md.add_macro("W(CC)", "K/8*sqr(log(Det(CC)))+G/2*(pow(Det(CC),-4/3)*(Trace(CC))-3)")

md.add_macro("SS", "S(C_avg)+(2*W(C)-2*W(C_old)-2*S(C_avg):Normalized(C-C_old))*Normalized(C-C_old)")

# elasticity + inertia
md.add_initialized_data("rho", rho)
md.add_initialized_data("dt", dt)
md.add_linear_term(mim, "(u-u_old-dt/2*(v+v_old)).Test_u")
md.add_nonlinear_term(mim, "rho/dt*(v-v_old).Test_v + (F_avg*SS):Grad(Test_v)")

# boundary conditions
md.add_Dirichlet_condition_with_multipliers(mim, 'u', 0, DIRICHLET_BOUNDARY)
md.add_initialized_fem_data('f_old', mf, md.interpolation('[0,0,-10]', mf))
md.add_initialized_fem_data('f', mf, md.interpolation('[0,0,-10]', mf))
#md.add_source_term_brick(mim, 'v', '(f+f_old)/2', NEUMANN_BOUNDARY)
md.add_linear_term(mim, "((f+f_old)/2).Test_v", NEUMANN_BOUNDARY)

# Time loop
for timeStepIndex,timeStep in enumerate(np.arange(0.,T_max+dt,dt)):
    print('Time step: %g' % timeStep)
#    md.assembly('build_rhs')
#    print(md.rhs())
#    exit()
    md.solve('max_res', 1E-6, 'very noisy')
    U = md.variable('u')
    V = md.variable('v')
    #Post processing of the solution
    mf.export_to_vtu('Displacement_%d.vtu' % timeStepIndex, mf, U, 'Displacement')

    # Update u_old and v_old
    md.set_variable('u_old', U)
    md.set_variable('v_old', V)

Thanks so much for the recommendations, Kostas.

I would like to check if the total mechanical energy is constant over time. For this, I was thinking about something like

mechanical_energy = md.compute_integral(mim, "0.5*rho*Norm_sqr(v) + W(C)")

after computing the displacement and the velocity at a given time step.
Of course, md.compute_integral does not exist. Is there any function in GetFEM that computes integrals over the whole mesh of quantities related to the computed solution?

yes, the syntax is

energy = gf.asm_generic(mim, 0, "0.5*rho*Norm_sqr(v) + W(C)", -1, md)

for example if you want to calculate the area of a region with identifier RG_DIRICHLET, you write

area = gf.asm_generic(mim, 0, "1", RG_DIRICHLET, md)

Same method can be used for computing residual vectors and tangent matrices.

https://getfem.org/python/cmdref_Module%20asm.html

For more tricks and modeling techniques in general, I would also recommend to browse my snippet collection here:

https://gitlab.gbar.dtu.dk/dashboard/snippets

Thanks, Kostas. With that implementation, I get an increasing mechanical energy, although the outputs of the simulation seem in some sense “meaningful”. Decreasing the Poisson ratio to 0.2 (instead of 0.49, which was perhaps too close to the incompressible regime) did not help.

I found at least two points that were not clear to me in the implementation, and which I suppose you will agree on.

  • If I understood correctly the expression of the elastic energy density you proposed in your first message, it is the following:
    \displaystyle W(\mathbf C) = \frac{K}{8}(\log(\det\,\mathbf C))^2 + \frac{G}{2}((\det\,\mathbf C)^{-4/3}(\mathrm{Tr}\,\mathbf C)-3).
    I made the computation several times to be sure, and obtained the following expression for the second Piola–Kirchhoff tensor:
    \displaystyle \mathbf S(\mathbf C) = 2\frac{\partial W}{\partial \mathbf C}(\mathbf C) = \frac{K}{2}\log(\det\,\mathbf C)\mathbf C^{-1} + G(\det \mathbf C)^{-4/3}\left(\mathbf I - \frac{4}{3}(\mathrm{Tr}\,\mathbf C) \mathbf C^{-1}\right),
    and so I believe there is an error in the expression of S(CC) in the first snippet of this thread: instead of md.add_macro("S(CC)", "K/2*log(Det(CC))*Inv(CC)+G*pow(Det(CC),-4/3)*(Id(3)-1/3*Trace(CC)*Inv(CC))")
    one should have
    md.add_macro("S(CC)", "K/2*log(Det(CC))*Inv(CC)+G*pow(Det(CC),-4/3)*(Id(3)-4/3*Trace(CC)*Inv(CC))"). Do you agree on this?

  • It seems to me that the expression you proposed, i.e., md.add_macro("SS", "S(C_avg)+(2*W(C)-2*W(C_old)-S(C_avg):Normalized(C-C_old))*Normalized(C-C_old)"), is not exactly equivalent to the Gonzalez scheme (I removed the “2” in front of “S(C_avg)”, my error). Indeed, if I understand right, this last expression is equivalent to
    \displaystyle\tilde{\mathbf S} = \mathbf S(\mathbf C_{avg}) + \left(2W(\mathbf C) - 2W(\mathbf C_{old}) - \mathbf S(\mathbf C_{avg}):\frac{\Delta \mathbf C}{\|\Delta\mathbf C\|} \right)\frac{\Delta \mathbf C}{\|\Delta\mathbf C\|},
    where \Delta\mathbf C := \mathbf C - \mathbf C_{old}.
    But this is not the Gonzalez scheme. Indeed, after rearranging terms, the actual Gonzalez scheme can be rewritten as
    \displaystyle\tilde{\mathbf S} = \mathbf S(\mathbf C_{avg}) + \left(\frac{2W(\mathbf C) - 2W(\mathbf C_{old})}{\|\Delta \mathbf C\|} - \mathbf S(\mathbf C_{avg}):\frac{\Delta \mathbf C}{\|\Delta\mathbf C\|} \right)\frac{\Delta \mathbf C}{\|\Delta\mathbf C\|}.
    Do you agree also on this? I tried to implement this expression, but GetFEM doesn’t like the \|\Delta \mathbf C\| at the denominator and gives again a NaN at every time step. By replacing the denominator in the first term after the sum by \|\Delta \mathbf C\| +\epsilon, with \epsilon = 10^{-30}, that seems to work, but I still get an increasing mechanical energy over time when the external loads are zero and only initial conditions are taken into account.

By the way, thanks for recalling that every nonlinear problem in GetFEM has to be implemented in the form F(\mathcal U)=0, this explains why I was getting an “upward” motion of the beam with md.add_linear_term(mim, "((f+f_old)/2).Test_v", NEUMANN_BOUNDARY): even though both f and f_old already have a negative z-component, I had to put a minus in front of this term!

Have a nice day!

I figured it out: replacing all the \frac{4}{3} coefficients and exponents in W(\mathbf C) and in \mathbf S(\mathbf C) by \frac{1}{3}, everything works fine for the null solution (vanishing loads and initial data).

Indeed, in this case, W(\mathbf I) = 0 and \mathbf S(\mathbf I) = \mathbf 0, whereas in the previous case, because of the presence of \frac{4}{3} in the elastic energy and in the stress, a null initial displacement would give rise to a residual stress \mathbf S(\mathbf I) = -3G\,\mathbf I, even though W(\mathbf I) = 0 was satisfied. This is why I was obtaining inconsistent results.

On the other hand, for non-vanishing external loads or initial data (in agreement with Dirichlet boundary conditions, of course), the Newton method does not converge, or perhaps converges but very slowly. I believe this might be due to the Gonzalez scheme implementation, and in particular to the treatment of \|\Delta \mathbf C\| at the denominator…

Nice that you figured this out. I hope you have the entire derivation that justifies the 1/3 coefficient, and not just relying on try-n-error. Could you please provide the final expressions you are using?

Regarding debugging Newton solver issues, I would recommend you to implement your own Newton loop based on
https://gitlab.gbar.dtu.dk/-/snippets/46

The model.solve() method uses quite some adhoc line search techniques.

Thanks for your suggestion, Kostas. That was precisely what I was looking for: I think the problem with the Newton algorithm concerns the initial guess, at every time step. For a spatially constant initial displacement (here I mean “initial condition”, not “initial guess”), since \mathbf C_{old} = \mathbf I, if the initial guess is also constant, then clearly \Delta \mathbf C = \mathbf 0 and that gives a problem with the denominator.

Concerning the derivation: of course, I recomputed the second Piola-Kirchhoff stress from the energy

\displaystyle W(\mathbf C) = \frac{K}{8}(\log(\det\,\mathbf C))^2 + \frac{G}{2}((\det\,\mathbf C)^{-1/3}(\mathrm{Tr}\,\mathbf C)-3)

and that gives exactly

\displaystyle \mathbf S(\mathbf C) = 2\frac{\partial W}{\partial \mathbf C}(\mathbf C) = \frac{K}{2}\log(\det\,\mathbf C)\mathbf C^{-1} + G(\det \mathbf C)^{-1/3}\left(\mathbf I - \frac{1}{3}(\mathrm{Tr}\,\mathbf C) \mathbf C^{-1}\right),

so that W(\mathbf I) = 0 and \mathbf S(\mathbf I) = \mathbf 0.

I will also try to use other laws, such as Ogden, Ciarlet-Geymonat, or Neo-Hookean…

For the Ogden material, you can implement it in GetFEM rather easily using the matrix logarithm and exponential operators in GWFL (Logm and Expm), see Eq. (33) in my hyperelasticity notes. Not the most efficient implementation but elegant in avoiding the spectral decomposition of the deformation gradient,

Thank you. Yet, numerical energy conservation (without external loads) should be law-independent, when adopting the Gonzalez scheme, as long as hyperelastic framework is considered. So I believe the actual issue really comes from the Newton…

what is the remaining issue now, could you remind me?

Newton either converges or it does not converge. If it does not converge it can either be because the problem is non-convex or due to an error in the computation of the tangent system.

If Newton converges then all posed equations are fulfilled and it does not matter how the solution to these equations was obtained, whether it was by applying numerical algorithm or by asking a fortune teller.

There is one case in hyperelastic systems, at least in quasistatic ones where energy is not preserved: if you have a bistable system and you jump from one equilibrium to the other one.