PERTURBATION *BUCKLE / *FREQUENCY after a *BUCKLE step, or *FREQUENCY after a *MODAL DYNAMIC step, takes that step's displacements as reference state (exit 0)

Summary

The 2.23 manual says which state a PERTURBATION step starts from:

  • Section 7.125 (*STEP): “If it is specified in a *FREQUENCY, *BUCKLE or *GREEN procedure, the last *STATIC step is taken as reference state and used to calculate the stiffness matrix.”
  • Section 6.9.4 (Buckling analysis): “the initial stiffness matrix includes the deformation and stress stiffness matrix corresponding to the deformation and stress at the end of the last static or dynamic step performed previous to the buckling step”. And: the buckling step’s loading “is reset to zero at the end of the buckling step. Thus, to continue a static step interrupted by a buckling step, the load has to be reapplied after the buckling step.”
  • Section 6.9.2 (Frequency analysis): “the stiffness matrix is augmented by contributions resulting from the displacements and stresses at the end of the last non-perturbative static step, if any”.

A *BUCKLE step is neither a static nor a dynamic step, and a *MODAL DYNAMIC step is not a static step. ccx 2.23 nevertheless takes the displacements that such a step leaves in the solution field as the reference state of a later PERTURBATION step:

  • after a *BUCKLE step (with or without PERTURBATION): the static response to the buckling step’s reference load;
  • after a *MODAL DYNAMIC step: the modal response at the end of that step.

The results of the later step therefore depend on the size of the earlier buckling step’s reference load, which in a linear buckling step only scales that step’s own factors. In the main deck below (an unloaded *STATIC step, a PERTURBATION *BUCKLE with a lateral load, then a PERTURBATION *BUCKLE with an axial load), the axial buckling factor is

  • 1.050396 without the lateral buckling step (the reference state is the unloaded column);
  • 1.079351 after a lateral buckling step with reference load 0.01;
  • 2.319850 after one with reference load 0.1.

A PERTURBATION *FREQUENCY step gives 1.503692 instead of 1.285551 cycles/time after the same lateral buckling step, and 1.480580 instead of 1.285551 after a *FREQUENCY + *MODAL DYNAMIC pair (a *FREQUENCY step alone in between changes nothing). Exit 0 in every case, with no message.

The same field is what *BOUNDARY, FIXED holds after a *BUCKLE step: the reference load’s deflection (0.7511661) instead of the unloaded state (0). Section 7.4 (*BOUNDARY) says FIXED “freezes the deformation from the previous step”; the buckling step’s load “is reset to zero at the end of the buckling step” (section 6.9.4), so the deformation to freeze is that of the static step before it.

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 22 Oct 2026). Not fixed: the end of arpackbu.c and of dyna.c is unchanged on master (9ba5d76).

Related:

Minimal input

All decks include the same model, column.inp: one C3D8I element (a short column 0.2 x 0.2 x 1), clamped at the bottom. Save it next to the decks.

** Model for all decks: one C3D8I element 0.2 x 0.2 x 1 (a short column),
** E = 1000, nu = 0.3, density 1, clamped at the bottom (nodes 1-4).
*NODE, NSET=NALL
1, 0., 0., 0.
2, 0.2, 0., 0.
3, 0.2, 0.2, 0.
4, 0., 0.2, 0.
5, 0., 0., 1.
6, 0.2, 0., 1.
7, 0.2, 0.2, 1.
8, 0., 0.2, 1.
*ELEMENT, TYPE=C3D8I, ELSET=E
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.3
*DENSITY
1.
*SOLID SECTION, ELSET=E, MATERIAL=M
*BOUNDARY
BOT, 1, 3

pert_after_buckle.inp (the main deck):

** Unloaded static step, a PERTURBATION buckling step with a lateral load
** (reference load 0.1), then the axial PERTURBATION buckling step.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 1, 0.1
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 3, -0.1
*END STEP

The other decks (the zero load in the *FREQUENCY, STORAGE=YES steps is there because a *MODAL DYNAMIC step should not introduce point loads that the frequency step does not have, section 6.9.5):

pert_alone.inp:

** Unloaded static step, then a PERTURBATION buckling step with an axial load.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 3, -0.1
*END STEP

pert_after_buckle_001.inp:

** As pert_after_buckle.inp, lateral reference load 0.01.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 1, 0.01
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 3, -0.1
*END STEP

pert_after_plain_buckle.inp:

** As pert_after_buckle.inp, lateral buckling step without PERTURBATION.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP
*BUCKLE
2
*CLOAD
TOP, 1, 0.1
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 3, -0.1
*END STEP

pert_buckle_first.inp:

** As pert_after_buckle.inp without the static step.
*INCLUDE, INPUT=column.inp
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 1, 0.1
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 3, -0.1
*END STEP

freq_alone.inp:

** Unloaded static step, then a PERTURBATION frequency step.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*FREQUENCY
2
*END STEP

freq_after_buckle.inp:

** As freq_alone.inp with the lateral buckling step in between.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 1, 0.1
*END STEP
*STEP, PERTURBATION
*FREQUENCY
2
*END STEP

freq_after_freq.inp:

** As freq_alone.inp with a frequency step (STORAGE=YES) in between.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP
*FREQUENCY, STORAGE=YES
4
*CLOAD
TOP, 1, 0.
*END STEP
*STEP, PERTURBATION
*FREQUENCY
2
*END STEP

freq_after_modal.inp:

** As freq_after_freq.inp with a modal dynamic step after the
** frequency step.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP
*FREQUENCY, STORAGE=YES
4
*CLOAD
TOP, 1, 0.
*END STEP
*STEP
*MODAL DYNAMIC
0.1, 1.
*CLOAD
TOP, 1, 0.1
*END STEP
*STEP, PERTURBATION
*FREQUENCY
2
*END STEP

fixed_alone.inp:

** Unloaded static step, then a static step that freezes u1 at the top.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP
*STATIC
*BOUNDARY, FIXED
TOP, 1, 1
*NODE PRINT, NSET=TOP
U
*END STEP

fixed_after_buckle.inp:

** As fixed_alone.inp with the lateral buckling step in between.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 1, 0.1
*END STEP
*STEP
*STATIC
*BOUNDARY, FIXED
TOP, 1, 1
*NODE PRINT, NSET=TOP
U
*END STEP

How to run

ccx <name> for each deck (with column.inp in the same directory), then read the buckling factors, eigenvalues or displacements of the last step in <name>.dat.

Expected

Step 1 is the last static step in every deck except pert_buckle_first.inp, and it is unloaded. So every perturbation step after it uses the unloaded column as reference state, whatever buckling or modal step comes in between:

  • the axial buckling factor is that of pert_alone.inp (1.050396) in pert_after_buckle, pert_after_buckle_001 and pert_after_plain_buckle. pert_buckle_first.inp has no static step at all, so there is no preload either (section 6.9.4: “if any”);
  • the frequencies of freq_after_buckle.inp and freq_after_modal.inp are those of freq_alone.inp (section 6.9.2: “the last non-perturbative static step”), as freq_after_freq.inp gives;
  • *BOUNDARY, FIXED holds the top at u1 = 0, as in fixed_alone.inp.

The lateral buckling step’s own factor is not the subject: it scales with its load as it should (29.48876 for 0.01, 2.948876 for 0.1).

Actual

Exit 0 for every deck, no warning.

deck step(s) between the static step and the last step last step expected
pert_alone - 1.050396 1.050396
pert_after_buckle_001 PERTURBATION *BUCKLE, 0.01 1.079351 1.050396
pert_after_buckle PERTURBATION *BUCKLE, 0.1 2.319850 1.050396
pert_after_plain_buckle *BUCKLE, 0.1 2.319850 1.050396
pert_buckle_first (no static step) PERTURBATION *BUCKLE, 0.1 2.319850 1.050396
freq_alone - 1.285551, 1.285551 1.285551, 1.285551
freq_after_buckle PERTURBATION *BUCKLE, 0.1 1.503692, 2.268764 1.285551, 1.285551
freq_after_freq *FREQUENCY 1.285551, 1.285551 1.285551, 1.285551
freq_after_modal *FREQUENCY + *MODAL DYNAMIC 1.480580, 2.373384 1.285551, 1.285551

The frequencies are in cycles/time.

fixed_after_buckle.inp, step 3:

         5  7.511661E-01  5.688817E-03  1.450332E-01
         6  7.511661E-01 -5.688817E-03 -1.450332E-01
         7  7.511661E-01  5.688817E-03 -1.450332E-01
         8  7.511661E-01 -5.688817E-03  1.450332E-01

fixed_alone.inp: all zero.

A PERTURBATION *BUCKLE after the *FREQUENCY + *MODAL DYNAMIC pair changes as well: freq_after_modal.inp with its last step replaced by the last step of pert_alone.inp gives an axial factor of 2.584485 instead of 1.050396. I leave that case out of the expected results above, since section 6.9.4 speaks of “the last static or dynamic step” for buckling.

Where it goes wrong

arpackbu.c computes the static response to the buckling step’s load (the second results() call, which writes it to the .frd file as the step’s displacements) and then copies it into the solution field (lines 415-417):

  for(k=0;k<mt**nk;++k){
    vold[k]=v[k];
  }

vold is the field that a later PERTURBATION step takes as its reference state, and that *BOUNDARY, FIXED freezes. Nothing restores it at the end of the *BUCKLE step: CalculiX.c (lines 1753-1761) resets the loads and temperatures after a PERTURBATION *BUCKLE step, but not vold. The base-state stresses are not affected: the buckling load case’s stresses go into a local array (stx), not into sti.

dyna.c does the same at the end of a *MODAL DYNAMIC step (lines 1950-1953, “copying the actual displacements into field vold serves as initial conditions in the next step”). That is what a following *MODAL DYNAMIC step needs (section 6.9.5), but a later PERTURBATION step takes it as its reference state as well.

arpack.c (*FREQUENCY) leaves vold alone, which is why freq_after_freq.inp is right.

Fix

Not built or tested:

  • *BUCKLE: save vold in arpackbu.c before the copy and restore it after the eigenvalue output, so that the step leaves the reference state as it found it.
  • *MODAL DYNAMIC: keep the copy for the step that follows, but give a following PERTURBATION *FREQUENCY or *BUCKLE step the reference state of the last static step, e.g. by saving vold at the start of the *MODAL DYNAMIC step and restoring it only when the next step is such a perturbation step.

Workaround: run the *BUCKLE and *MODAL DYNAMIC steps in separate input decks, or repeat the general step (*STATIC, with its loads given again) right before the PERTURBATION step. pert_restatic.inp:

** Workaround: pert_after_buckle.inp with an empty static step before
** the axial buckling step.
*INCLUDE, INPUT=column.inp
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 1, 0.1
*END STEP
*STEP
*STATIC
*END STEP
*STEP, PERTURBATION
*BUCKLE
2
*CLOAD
TOP, 3, -0.1
*END STEP

gives 1.050396.

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

pert_alone.dat:

 MODE NO       BUCKLING
                FACTOR

      1   0.1050396E+01
      2   0.1050396E+01

pert_after_buckle_001.dat:

 MODE NO       BUCKLING
                FACTOR

      1   0.1079351E+01
      2   0.1155386E+01

pert_after_buckle.dat:

 MODE NO       BUCKLING
                FACTOR

      1   0.2319850E+01
      2   0.1446006E+02

pert_after_plain_buckle.dat:

 MODE NO       BUCKLING
                FACTOR

      1   0.2319850E+01
      2   0.1446006E+02

pert_buckle_first.dat:

 MODE NO       BUCKLING
                FACTOR

      1   0.2319850E+01
      2   0.1446006E+02
1 Like

posted: PERTURBATION *BUCKLE / *FREQUENCY after a *BUCKLE step, or *FREQUENCY after a *MODAL DYNAMIC step, takes that step's displacements as reference state (exit 0) · Issue #283 · Dhondtguido/CalculiX · GitHub

1 Like