*MODEL CHANGE toggles elements: ADD switches an active element off, a second REMOVE switches it back on; GENERATE element sets are misread (random segfault)

Summary

Section 7.89 (*MODEL CHANGE) of the 2.23 manual: “With this option one can activate or deactivate elements”, with “two mutually exclusive parameters ADD and REMOVE”, and the data lines are a “List of elements and/or sets of elements to be activated or deactivated”.

Section 7.89 does not limit ADD to removed elements or REMOVE to active ones. For both parameters, modelchanges.f does the same thing: it flips the sign of the element’s ipkon entry, whatever state the element is in. ADD and REMOVE therefore both mean “switch”. This has three consequences, all with exit 0 and no message:

  1. REMOVE of an element that is already removed switches it back on. In the deck below, step 2 removes element 2 and step 3 removes it again. Step 3 has element 2 active again: u_x = 0.0005 instead of 0.001.
  2. ADD of an active element switches it off. Step 2 with ADD=WITH STRAIN for element 2, which was never removed, gives u_x = 0.001: element 2 is gone.
  3. An element listed twice in one card is switched twice, so it does not change. Here element 2 is in both sets of REMOVE / SETA, SETB, and it stays active: u_x = 0.0005.

Overlapping sets are a natural input. Examples are a cumulative set (“everything excavated so far”) removed in each step, or a layer-by-layer ADD whose sets overlap.

The same loop also misreads element sets defined with GENERATE. It treats the stored range start, end, -increment as three element numbers, so it switches the first and the last element and writes outside the array (ipkon(-1) for increment 1). The elements between the bounds stay active, and some runs end in a segmentation fault or a bus error.

Version

ccx 2.23 (macOS arm64) and current master (537aa81, 2 Nov 2026): the same numbers. modelchanges.f is identical in both.

Already reported or fixed?

Not reported: I found no matching GitHub issue (open or closed) or pull request in Dhondtguido/CalculiX (searched 3 Nov 2026). Not fixed: modelchanges.f is unchanged on master (537aa81), and none of the open input-check pull requests (#189-#196) touch it.

Minimal input

mc_toggle.inp: a unit cube made of two C3D8 elements on the same nodes, so each element carries half the load.

** Unit cube made of two C3D8 elements on the same eight nodes (E = 1000,
** nu = 0 each), pulled in x by 1 in total: u_x = 0.0005 with both elements
** active, 0.001 with element 2 removed.
** Step 2 removes element 2, step 3 removes it again.
*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
2, 1, 2, 3, 4, 5, 6, 7, 8
*NSET, NSET=XMAX
2, 3, 6, 7
*MATERIAL, NAME=MAT
*ELASTIC
1000., 0.
*SOLID SECTION, ELSET=EALL, MATERIAL=MAT
*BOUNDARY
1, 1, 3
4, 1, 1
4, 3, 3
5, 1, 2
8, 1, 1
*STEP
*STATIC
*CLOAD
XMAX, 1, 0.25
*NODE PRINT, NSET=XMAX
U
*END STEP
*STEP
*STATIC
*MODEL CHANGE, TYPE=ELEMENT, REMOVE
2
*NODE PRINT, NSET=XMAX
U
*END STEP
*STEP
*STATIC
*MODEL CHANGE, TYPE=ELEMENT, REMOVE
2
*NODE PRINT, NSET=XMAX
U
*END STEP

Variants (steps 1 and 2 only, i.e. the last step deleted):

  • mc_addactive.inp: *MODEL CHANGE, TYPE=ELEMENT, ADD=WITH STRAIN instead of REMOVE in step 2.
  • mc_twosets.inp: *ELSET, ELSET=SETA / 2 / *ELSET, ELSET=SETB / 2 added after the elements, and SETA, SETB instead of 2 under *MODEL CHANGE in step 2.

The decks are linear so that the numbers are exact. With *STEP, NLGEOM on every step the outcome is the same (4.996255E-04 and 9.985040E-04). The default ADD (STRAIN FREE) of an active element under NLGEOM also switches it off.

mc_generate.inp: four elements on the same nodes; step 2 removes the set EREM = 1, 3, 1 (GENERATE)
** Unit cube made of four C3D8 elements on the same eight nodes (E = 1000,
** nu = 0 each), pulled in x by 1: u_x = 0.00025 with all four active,
** 0.001 after elements 1-3 are removed. The removed set uses GENERATE.
*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
2, 1, 2, 3, 4, 5, 6, 7, 8
3, 1, 2, 3, 4, 5, 6, 7, 8
4, 1, 2, 3, 4, 5, 6, 7, 8
*ELSET, ELSET=EREM, GENERATE
1, 3, 1
*NSET, NSET=XMAX
2, 3, 6, 7
*MATERIAL, NAME=MAT
*ELASTIC
1000., 0.
*SOLID SECTION, ELSET=EALL, MATERIAL=MAT
*BOUNDARY
1, 1, 3
4, 1, 1
4, 3, 3
5, 1, 2
8, 1, 1
*STEP
*STATIC
*CLOAD
XMAX, 1, 0.25
*NODE PRINT, NSET=XMAX
U
*END STEP
*STEP
*STATIC
*MODEL CHANGE, TYPE=ELEMENT, REMOVE
EREM
*NODE PRINT, NSET=XMAX
U
*END STEP

mc_generate_ref.inp defines the same set as *ELSET, ELSET=EREM / 1, 2, 3.

How to run

ccx mc_toggle

Expected

deck step 1 step 2 step 3
mc_toggle 5.0E-04 1.0E-03 1.0E-03 (element 2 stays removed)
mc_addactive 5.0E-04 5.0E-04 (element 2 stays active)
mc_twosets 5.0E-04 1.0E-03
mc_generate 2.5E-04 1.0E-03

Actual

u_x at node 7 (nodes 2, 3 and 6 are the same). All runs exit 0, with no warning, except where a crash is noted.

deck step 1 step 2 step 3
mc_toggle 5.000000E-04 1.000000E-03 5.000000E-04
mc_addactive 5.000000E-04 1.000000E-03
mc_twosets 5.000000E-04 5.000000E-04
mc_generate 2.500000E-04 5.000000E-04 (only elements 1 and 3 removed)
mc_generate_ref 2.500000E-04 1.000000E-03

mc_generate also fails at random, as expected from a write outside an array. In 40 runs, ccx 2.23 ended 4 times with a segmentation fault, once with a bus error and once with “*ERROR in add_sm_st: coefficient should be 0” (exit 201). Master, in 40 runs, ended 4 times with a segmentation fault and once with a bus error. All other runs printed the numbers above with exit 0.

Where it goes wrong

src/modelchanges.f (master), set entries at lines 200-208 and single elements at lines 231-234:

                  m=iendset(j)-istartset(j)+1
                  do k=1,m
                    nelem=ialset(istartset(j)+k-1)
                    if((add).or.(remove)) then 
                      ipkon(nelem)=-2-ipkon(nelem)
                    endif
                    if(strainfree) ielmat(1,nelem)=
     &                   -ielmat(1,nelem)
                  enddo
  1. ipkon(nelem)=-2-ipkon(nelem) flips between active (ipkon >= 0) and inactive (ipkon <= -2) for ADD and REMOVE alike. It never checks which state the element is in. The ielmat sign flag for STRAIN FREE is flipped in the same way.
  2. The loop reads every ialset entry as an element number. noelsets.f (line 265) stores a GENERATE range as start, end, -increment, and the other set readers expand it, e.g. transforms.f lines 119-130:
      do j=istartset(i),iendset(i)
         if(ialset(j).gt.0) then
            inotr(1,ialset(j))=ntrans
         else
            k=ialset(j-2)
            do
               k=k-ialset(j)
               if(k.ge.ialset(j-1)) exit
               inotr(1,k)=ntrans
            enddo
         endif
      enddo

For EREM = 1, 3, -1 the loop in modelchanges.f switches elements 1 and 3 and writes ipkon(-1), outside the array.

Fix

In both branches, change an element only if it is in the other state: for REMOVE, only when ipkon(nelem).ge.0; for ADD, only when ipkon(nelem).le.-2. Do the same for the ielmat flip. A warning for an element that is already in the requested state would help. Expand GENERATE ranges as the other set readers do.

Also (documented restriction without a check)

Section 7.89 also says: “One- and two-dimensional elements which are expanded in CalculiX (such as plane stress or shell elements) cannot be removed in the first step (to circumvent this restriction a dummy first step doing nothing can be inserted)”. ccx does not check this.

Two S4 elements on the same four nodes (thickness 0.1, E = 1000, nu = 0, pulled by 0.1): element 2 is removed in step 1 and added with ADD=WITH STRAIN in step 2. Step 2 gives u_x = 1.000000E-03, the result for one element, with exit 0 and no message. Element 2 was never expanded, so it never comes back. With the dummy first step the manual suggests, the last step gives 5.000000E-04. The same deck with CPS4 stops in step 2 with “*ERROR in e_c3d: nonpositive jacobian determinant in element 2” (exit 201), which does not point to the cause.

An *ERROR when a REMOVE in step 1 lists an element that will be expanded would make the restriction visible.

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

mc_toggle.dat:

                        S T E P       3


                                INCREMENT     1


 displacements (vx,vy,vz) for set XMAX and time  0.3000000E+01

         2  5.000000E-04 -2.080053E-17  2.033805E-17
         3  5.000000E-04 -2.545023E-17 -1.380053E-18
         6  5.000000E-04  1.803009E-18  1.361171E-17
         7  5.000000E-04  1.659240E-18 -1.987301E-18