Implicit *DYNAMIC step after a strained step: internal energy (ELSE) and the step's "initial energy" start at twice the value, with nothing moving

Summary

At the start of an implicit *DYNAMIC step, the internal energy (*EL PRINT ELSE) is raised by the full strain energy of the current state. A cube carries a static load with a strain energy of 5.0E-04. A *DYNAMIC step then holds the same load, and nothing moves. ELSE is 1.0E-03 from the first increment on, and so is the “initial energy (at start of step)” that ccx prints for its energy balance. A second *DYNAMIC step adds the same amount again (1.5E-03). If the load is released instead, ELSE oscillates between 5.0E-04 and 1.0E-03, and ELSE + ELKE stays at 1.0E-03, twice the energy the cube holds.

The equivalent deck with a second *STATIC step keeps ELSE at 5.0E-04. A *DYNAMIC first step starts from zero. Section 7.53 (*EL PRINT) of the 2.23 manual says that in nonlinear calculations “the internal energy at any time depends on the accumulated energy at all previous times”. With no motion and no work done, that energy cannot change. The step after an NLGEOM static step (static_dyn.inp with *STEP, NLGEOM on step 1) is doubled in the same way (4.99E-04 to 9.98E-04).

Version

ccx 2.23 (macOS arm64). Source: master 9ba5d76 (16 Oct 2026).

Already reported or fixed?

Not reported: I found no matching GitHub issue (open or closed) or pull request in Dhondtguido/CalculiX, and no calculix.discourse.group topic (searched 21 Oct 2026). Not fixed: nonlingeo.c and resultsmech.f are unchanged on master (9ba5d76).

Related:

Minimal input

static_dyn.inp is one C3D8 cube. Only the z-displacements of the four top nodes are free. E = 1000, nu = 0, so k = 1000, and 4 x 0.25 on the top gives u = 1.0E-03 and a strain energy of k u^2 / 2 = 5.0E-04. Step 2 keeps the load (carried over) and runs ten increments of the dynamic step.

*NODE
1, 0., 0., 0.
2, 1., 0., 0.
3, 1., 1., 0.
4, 0., 1., 0.
5, 0., 0., 1.
6, 1., 0., 1.
7, 1., 1., 1.
8, 0., 1., 1.
*ELEMENT, TYPE=C3D8, ELSET=EALL
1, 1, 2, 3, 4, 5, 6, 7, 8
*NSET, NSET=BOT
1, 2, 3, 4
*NSET, NSET=TOP
5, 6, 7, 8
*MATERIAL, NAME=M
*ELASTIC
1000., 0.
*DENSITY
0.3
*SOLID SECTION, ELSET=EALL, MATERIAL=M
*BOUNDARY
BOT, 1, 3
TOP, 1, 2
*STEP
*STATIC
*CLOAD
TOP, 3, 0.25
*EL PRINT, ELSET=EALL
ELSE
*END STEP
*STEP
*DYNAMIC, DIRECT
2.51327412287E-4, 2.51327412287E-3
*END STEP

Variants:

  • static_static.inp: step 2 is *STATIC instead of *DYNAMIC, DIRECT with its data line.
  • static_dyn_dyn.inp: static_dyn.inp with a copy of step 2 appended as step 3.
  • static_release.inp: step 2 is replaced by this step, which removes the load and runs one period (250 increments):
*STEP, INC=1000
*DYNAMIC, DIRECT
2.51327412287E-4, 0.0628318530718
*CLOAD, OP=NEW
*EL PRINT, ELSET=EALL, FREQUENCY=25
ELSE, ELKE
*END STEP

How to run

ccx static_dyn (and the variants the same way)

Expected

ELSE stays 5.000000E-04 in step 2 of static_dyn.inp, as in step 2 of static_static.inp: nothing moves and no work is done. In static_release.inp, ELSE + ELKE = 5.0E-04 at all times, with ELSE going from 5.0E-04 to 0 and back.

Actual

static_dyn.dat:

 internal energy (element, energy) for set EALL and time  0.1000000E+01

         1  5.000000E-04
 ...
 internal energy (element, energy) for set EALL and time  0.1000251E+01

         1  1.000000E-03

ELSE stays at 1.000000E-03 up to the end of step 2. The displacement does not change. ccx prints on stdout for step 2:

 initial energy (at start of step) = 1.000000e-03
 ...
 internal energy = 1.000000e-03
 kinetic energy = 3.892867e-39

static_static.inp: 5.000000E-04 in both steps. static_dyn_dyn.inp: 5.0E-04, 1.0E-03, 1.5E-03 in steps 1 to 3.

static_release.inp (ELSE, ELKE, sum):

 time 1.006283   8.272721E-04  1.727255E-04  9.999976E-04
 time 1.012566   5.477679E-04  4.522258E-04  9.999937E-04
 time 1.031416   9.999997E-04  1.769592E-11  9.999997E-04

At every output time, ELKE + (ELSE - 5.0E-04) = 5.0E-04. So the kinetic energy is right, and ELSE carries an extra 5.0E-04 throughout.

Where it goes wrong

nonlingeo.c allocates the start-of-increment fields at the start of the step. stiini and emeini are zero (lines 846-847), and enerini is a copy of ener, the energy at the end of the previous step (lines 849-856). For *DYNAMIC the initial acceleration block follows (line 909: if((*nmethod==4)&&(*ithermal!=2)&&(icfd==0)){). In it, results() is called at line 1497 with iout=0 (set at line 1015), passing stiini, emeini and enerini. With nener==1 and iout==0, resultsmech.f updates the energy (lines 1049-1073):

!     sigma.d(epsilon)=(sigma_ini+sigma)*(epsilon-epsilon_ini)/2
              ener(1,jj,i)=enerini(1,jj,i)+
     &             ((emec(1)-emeini(1,jj,i))*
     &             (stre(1)+stiini(1,jj,i))+

With emeini = 0 and stiini = 0, this adds emec*stre/2, the full strain energy of the current state, to the energy of the previous step. stiini and emeini only get the values of sti and eme at the start of the first increment (lines 1640-1641). By then ener already holds the doubled value, and the first increment copies it into enerini.

The update in the initial-acceleration pass does not hinge on the ((iout.eq.0).and.(nener.eq.1)) term added on 07.07.2023 (resultsmech.f line 1057, LOGBOOK “energy was not calculated in an increment for which no printing output was requested”). *DYNAMIC sets iperturb(1)=2 (dynamics.f line 68), so the older term (nmethod.eq.4).and.((iperturb(1).gt.1).and.(nlgeom_undo.eq.0)) (lines 1058-1060) already lets this pass through. Removing the 2023 term alone would not fix it.

Fix

After allocating stiini and emeini in nonlingeo.c (lines 846-847), copy sti and eme into them, as lines 1640-1641 do at the start of each increment. Then the energy update in the initial-acceleration pass adds (emec-eme)*(stre+sti)/2, which is zero for an unchanged state. Not built or tested here.

Confirmed (using official build of ccx 2.23 on Windows), needs a GitHub ticket.

static_dyn.dat:

                        S T E P       1


                                INCREMENT     1


 internal energy (element, energy) for set EALL and time  0.1000000E+01

         1  5.000000E-04

                        S T E P       2


                                INCREMENT     1


 internal energy (element, energy) for set EALL and time  0.1000251E+01

         1  1.000000E-03

                                INCREMENT     2


 internal energy (element, energy) for set EALL and time  0.1000503E+01

         1  1.000000E-03

                                INCREMENT     3


 internal energy (element, energy) for set EALL and time  0.1000754E+01

         1  1.000000E-03

                                INCREMENT     4


 internal energy (element, energy) for set EALL and time  0.1001005E+01

         1  1.000000E-03

                                INCREMENT     5


 internal energy (element, energy) for set EALL and time  0.1001257E+01

         1  1.000000E-03

                                INCREMENT     6


 internal energy (element, energy) for set EALL and time  0.1001508E+01

         1  1.000000E-03

                                INCREMENT     7


 internal energy (element, energy) for set EALL and time  0.1001759E+01

         1  1.000000E-03

                                INCREMENT     8


 internal energy (element, energy) for set EALL and time  0.1002011E+01

         1  1.000000E-03

                                INCREMENT     9


 internal energy (element, energy) for set EALL and time  0.1002262E+01

         1  1.000000E-03

                                INCREMENT    10


 internal energy (element, energy) for set EALL and time  0.1002513E+01

         1  1.000000E-03