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
*BUCKLEstep (with or without PERTURBATION): the static response to the buckling step’s reference load; - after a
*MODAL DYNAMICstep: 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:
- Issues · Dhondtguido/CalculiX · GitHub moments after a
*BUCKLEstep, and (second point there) the buckling load of a*BUCKLEstep without PERTURBATION stays active in a later general step. That concerns the load entries the*BUCKLEstep removes or keeps; this report concerns the displacement field it leaves behind, also with PERTURBATION. - *BUCKLE and PERTURBATION: zero the point loads instead of removing their entries by sandervdbroek · Pull Request #256 · Dhondtguido/CalculiX · GitHub (for #231) changes how the point load entries are reset around
*BUCKLEand PERTURBATION steps. It does not touch the displacement field.
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) inpert_after_buckle,pert_after_buckle_001andpert_after_plain_buckle.pert_buckle_first.inphas no static step at all, so there is no preload either (section 6.9.4: “if any”); - the frequencies of
freq_after_buckle.inpandfreq_after_modal.inpare those offreq_alone.inp(section 6.9.2: “the last non-perturbative static step”), asfreq_after_freq.inpgives; *BOUNDARY, FIXEDholds the top at u1 = 0, as infixed_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: savevoldinarpackbu.cbefore 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*FREQUENCYor*BUCKLEstep the reference state of the last static step, e.g. by savingvoldat the start of the*MODAL DYNAMICstep 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.