*MODAL DAMPING: the ratio table is sized by the deck's last card (out-of-bounds write, SIGABRT), and RAYLEIGH discards later ratio cards and the *DAMPING values

Summary

Section 7.87 (*MODAL DAMPING) of the 2.23 manual: “The *MODAL DAMPING keyword can be used in any step to redefine damping values defined in a previous step.” Three things go wrong when a deck has more than one such card:

  1. The ratio table is sized by the last ratio card. allocation.f sets the size of the table to the highest mode of the last *MODAL DAMPING card in the deck, not of all cards. An earlier card for more modes then writes past the end of the table, and its step applies the ratios only up to that size. In damp_two.inp below (ratio 0.05 for modes 1-6 in step 2, 0.02 for mode 1 in a later step 3), step 2 runs with mode 1 damped only: u = 4.555879E-04 at node 3, which is exactly the result with 1, 1, 0.05 (damp_mode1.inp), instead of 4.291461E-04 (damp_one.inp, the same deck without step 3). A later step changes the result of an earlier one. The run also crashes in almost every run (46 of 48: SIGABRT, exit 134, once SIGSEGV), at a different point each time (before step 1, in step 2 or after step 3), with no message. Under macOS Guard Malloc it stops every time at the out-of-bounds store in modaldampings_, called from calinput_.
  2. RAYLEIGH switches the ratios off for the rest of the run. After a *MODAL DAMPING, RAYLEIGH card, a later card with ratios has no effect: the step runs undamped.
  3. RAYLEIGH overwrites *DAMPING. The α and β of a *MODAL DAMPING, RAYLEIGH card replace those of the model’s *DAMPING card (section 7.33: “It applies to the whole model”), so a later *DYNAMIC step runs with them.

Version

ccx 2.23 (macOS arm64) and current master (537aa81, built on macOS arm64): the same numbers. Source: master 537aa81.

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 (checked 3 Nov 2026). Not fixed: allocation.f, modaldampings.f, dampings.f and dyna.c are unchanged in this respect on master (537aa81).

Minimal input

damp_two.inp: a chain of 10 unit masses and 11 springs, 6 modes, two *MODAL DYNAMIC steps:

** chain of 10 unit masses (nodes 1-10) and 11 springs of stiffness 1000 between
** the held nodes 100 and 101; only x is free
*NODE, NSET=NALL
1, 1.0, 0., 0.
2, 2.0, 0., 0.
3, 3.0, 0., 0.
4, 4.0, 0., 0.
5, 5.0, 0., 0.
6, 6.0, 0., 0.
7, 7.0, 0., 0.
8, 8.0, 0., 0.
9, 9.0, 0., 0.
10, 10.0, 0., 0.
100, 0., 0., 0.
101, 11.0, 0., 0.
*NSET, NSET=MASSES, GENERATE
1, 10, 1
*ELEMENT, TYPE=MASS, ELSET=EM
101, 1
102, 2
103, 3
104, 4
105, 5
106, 6
107, 7
108, 8
109, 9
110, 10
*MASS, ELSET=EM
1.
*ELEMENT, TYPE=SPRINGA, ELSET=ES
200, 100, 1
201, 1, 2
202, 2, 3
203, 3, 4
204, 4, 5
205, 5, 6
206, 6, 7
207, 7, 8
208, 8, 9
209, 9, 10
210, 10, 101
*SPRING, ELSET=ES

1000.
*BOUNDARY
100, 1, 3
101, 1, 3
MASSES, 2, 3
*STEP
*FREQUENCY, STORAGE=YES
6
*CLOAD
1, 1, 0.
*END STEP
*STEP
*MODAL DYNAMIC
0.001, 0.1
*MODAL DAMPING
1, 6, 0.05
*CLOAD
1, 1, 1.
*NODE PRINT, NSET=MASSES, FREQUENCY=20
U
*END STEP
*STEP
*MODAL DYNAMIC
0.001, 0.1
*MODAL DAMPING
1, 1, 0.02
*CLOAD
1, 1, 1.
*NODE PRINT, NSET=MASSES, FREQUENCY=20
U
*END STEP

Variants:

  • damp_one.inp: damp_two.inp without step 3.
  • damp_mode1.inp: damp_one.inp with 1, 1, 0.05 instead of 1, 6, 0.05.
  • zero_then_direct.inp: damp_one.inp with 1, 6, 0. in step 2, followed by a copy of step 2 with 1, 6, 0.05 as step 3. ray_then_direct.inp: the same with *MODAL DAMPING, RAYLEIGH / , , 0., 0. in step 2. zero_then_zero.inp: zero_then_direct.inp with 1, 6, 0. in step 3.
  • direct_then_dynamic.inp: damp_one.inp with 1, 6, 0. in step 2; *MATERIAL, NAME=DUMMY / *DAMPING, ALPHA=10. / *ELASTIC / 1., 0. before *BOUNDARY; *EL PRINT, ELSET=ES, TOTALS=ONLY / ELSE in step 1 (ccx asks for energy requests in the first step when a *DYNAMIC step follows); and a step 3 *STEP / *DYNAMIC / 0.001, 0.1 / *CLOAD, OP=NEW / 1, 1, 1. / *NODE PRINT, NSET=MASSES, FREQUENCY=20 / U / *END STEP. ray_then_dynamic.inp: the same with *MODAL DAMPING, RAYLEIGH / , , 0., 0. in step 2.

How to run

ccx damp_two (and the variants the same way)

Expected

  1. Step 2 of damp_two gives the same result as damp_one: no later card can change it. No crash.
  2. Step 3 of ray_then_direct is damped like step 3 of zero_then_direct.
  3. Step 3 of ray_then_dynamic is damped by *DAMPING, ALPHA=10. like step 3 of direct_then_dynamic.

Actual

u1 of node 3 at the end of the step (ccx 2.23 and master alike):

deck step 2 (t = 0.1) step 3 (t = 0.2)
damp_one 4.291461E-04
damp_mode1 4.555879E-04
damp_two 4.555879E-04, then SIGABRT (23 of 24 runs)
zero_then_direct 4.608200E-04 9.329898E-04
ray_then_direct 4.608200E-04 9.407754E-04
zero_then_zero 4.608200E-04 9.407754E-04
direct_then_dynamic 4.608200E-04 8.226046E-04
ray_then_dynamic 4.608200E-04 9.797238E-04
direct_then_dynamic without the *DAMPING line 4.608200E-04 9.797238E-04

damp_two: in two series of 12 runs each, 2.23 crashed 12 and 11 times and master 11 and 12 times (SIGABRT, once SIGSEGV; nothing on stdout or stderr). Where step 2 was written completely, it holds 4.555879E-04.

With Guard Malloc (DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib), master stops in every run of damp_two (3 of 3) and damp_one runs. Under lldb:

stop reason = EXC_BAD_ACCESS (code=1, address=0x105d80000)
  * frame #0: ccx`modaldampings_ + 880     str d31, [x0, #0x48]
    frame #1: ccx`calinput_ + 20620
    frame #2: ccx`main + 13324

i.e. a store past the end of xmodal while step 2’s *MODAL DAMPING card is read (the *FREQUENCY step has run by then).

Where it goes wrong

  1. allocation.f (master lines 1654-1675) restarts the count at every card:

          elseif(textpart(1)(1:13).eq.'*MODALDAMPING') then
            if(textpart(2)(1:8).ne.'RAYLEIGH') then
              nevdamp_=0
              do
                ...
                nevdamp_ = max(nevdamp_,i)
    

    CalculiX.c allocates xmodal with 11+nevdamp_ entries and stores the number of damped modes once (lines 383-384: NNEW(xmodal,double,11+nevdamp_); / xmodal[10]=nevdamp_+0.5;). modaldampings.f writes xmodal(11+k)=zeta for every mode of the card (line 112) without a bound, so a card for more modes than the last one writes past the array, and dyna.c applies ratios only to the first (ITG)xmodal[10] modes (lines 868-882).

  2. RAYLEIGH sets xmodal(11)=-0.5d0 (modaldampings.f line 58), which makes dyna.c use Rayleigh damping (line 848: if(xmodal[10]<0)). A later ratio card only writes xmodal(11+k) and never sets xmodal(11) back, so the Rayleigh branch stays active (with α = β = 0 here).

  3. RAYLEIGH reads α and β into xmodal(1) and xmodal(2) (modaldampings.f lines 69 and 75), the same entries in which dampings.f stores the model’s *DAMPING, ALPHA=, BETA= (lines 52 and 59) and from which nonlingeo.c takes the damping of *DYNAMIC steps (lines 336-337).

Fix

Possible fix (not built or tested):

  1. In allocation.f, set nevdamp_=0 once before reading the deck (or keep the maximum over all cards) instead of at each card; and let modaldampings.f stop with an *ERROR for a mode above nevdamp_.
  2. In modaldampings.f, set xmodal(11) back to the number of damped modes when a ratio card follows RAYLEIGH (the size from item 1 + 0.5).
  3. Keep the α and β of *MODAL DAMPING, RAYLEIGH in their own entries of xmodal, separate from *DAMPING.

Workaround for item 1: let the last ratio card of the deck name the highest mode of all cards. With step 3’s card written as 1, 1, 0.02 / 6, 6, 0.05, damp_two runs without a crash in 3 of 3 runs on both versions and step 2 gives 4.291461E-04, as damp_one does.

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

damp_two indeed crashes at the second step.