Element deletion by *DAMAGE INITIATION stops the run when nodes are left without elements (singular matrix, exit 255, no message; segfault if no element is left)

Summary

Per the manual (section 6.8.19), an element is deleted as soon as the damage criterion reaches the user-defined limit in any of its integration points. calcdamage.f does this at the end of a converged increment by setting ipkon(i)=-ipkon(i)-2. The active degrees of freedom (nactdof, mastruct.c) are only recomputed at the start of a step. So the DOFs of nodes that no remaining element uses stay in the system with zero stiffness.

The next increment then fails in the factorisation. ccx exits with status 255, and nothing on stdout or stderr says why; the only message is “matrix found to be singular” in spooles.out. If the deleted element was the last one in the model, ccx gets a segmentation fault instead. Removing the same element with *MODEL CHANGE, TYPE=ELEMENT, REMOVE at the start of a step works.

The failure is the same with CRITERION=JOHNSON COOK (with its second data line, the limit). Deletion works when every node of the deleted element is still used by another element. With two coincident C3D8 of which one is deleted, the other carries on and the reaction drops to its share. In a three-element bar whose plastic middle element is deleted between two elastic ones, the reaction drops to 0 and the step finishes. Master (9ba5d76) has the same calcdamage.f, and nonlingeo.c still calls it without updating the structure.

Version

ccx 2.23 (conda-forge build pl5321h484372f_2, macOS arm64).

Already reported or fixed?

Not reported: there is no matching GitHub issue (open or closed) or open pull request in Dhondtguido/CalculiX, and no calculix.discourse.group topic (searched 19 Oct 2026). Not fixed: the code involved is unchanged on master. conda-forge’s latest 2.23 build (pl5321hd17ca8b_8) stops the same way (exit 255, singular matrix); where no element is left, it stops with exit 255 instead of a segfault.

Minimal input

delete.inp is a bar of two C3D8 under a prescribed end displacement. Element 1 is elastic. Element 2, at the loaded end, is plastic with a Rice-Tracey limit of 0.01. Nodes 3, 7, 11 and 15 belong to element 2 only.

*HEADING
Bar of two C3D8; end element 2 (plastic, damage limit 0.01) is deleted
*NODE
1, 0., 0., 0.
2, 1., 0., 0.
3, 2., 0., 0.
5, 0., 1., 0.
6, 1., 1., 0.
7, 2., 1., 0.
9, 0., 0., 1.
10, 1., 0., 1.
11, 2., 0., 1.
13, 0., 1., 1.
14, 1., 1., 1.
15, 2., 1., 1.
*ELEMENT, TYPE=C3D8, ELSET=E1
1, 1, 2, 6, 5, 9, 10, 14, 13
*ELEMENT, TYPE=C3D8, ELSET=E2
2, 2, 3, 7, 6, 10, 11, 15, 14
*ELSET, ELSET=EALL
E1, E2
*NSET, NSET=X0
1, 5, 9, 13
*NSET, NSET=X2
3, 7, 11, 15
*NSET, NSET=Y0
1, 2, 3, 9, 10, 11
*NSET, NSET=Z0
1, 2, 3, 5, 6, 7
*MATERIAL, NAME=ELAST
*ELASTIC
200000., 0.3
*MATERIAL, NAME=DAMAGED
*ELASTIC
200000., 0.3
*PLASTIC
240., 0.
2240., 1.
*DAMAGE INITIATION, CRITERION=RICE TRACEY
1., 0.01
*SOLID SECTION, ELSET=E1, MATERIAL=ELAST
*SOLID SECTION, ELSET=E2, MATERIAL=DAMAGED
*BOUNDARY
X0, 1, 1
Y0, 2, 2
Z0, 3, 3
*STEP, NLGEOM, INC=100
*STATIC, DIRECT
0.1, 1.
*BOUNDARY
X2, 1, 1, 0.02
*NODE PRINT, NSET=X2, TOTALS=ONLY
RF
*EL PRINT, ELSET=E2
DUCT
*END STEP

In 2.23 a *DAMAGE INITIATION card always goes to the last *MATERIAL of the deck, whichever material it follows; master fixed this in 32fb01a. The damaged material is therefore placed last here, so the deck means the same in both.

How to run

ccx delete

Expected

Element 2 is deleted at the end of increment 4. At t = 0.3 its DUCT is at most 7.53e-3; at t = 0.4 it exceeds 0.01, and element 2 is missing from the t = 0.4 output. After that the bar is broken: the reaction on X2 is 0 and the step runs to t = 1.

That is what ccx gives for the same model with the element removed by *MODEL CHANGE (delete_modelchange.inp). It is delete.inp without the two *DAMAGE INITIATION lines, with the step replaced by:

*STEP, NLGEOM, INC=100
*STATIC, DIRECT
0.1, 0.4
*BOUNDARY
X2, 1, 1, 0.008
*NODE PRINT, NSET=X2, TOTALS=ONLY
RF
*END STEP
*STEP, NLGEOM, INC=100
*STATIC, DIRECT
0.1, 0.6
*MODEL CHANGE, TYPE=ELEMENT, REMOVE
E2
*BOUNDARY
X2, 1, 1, 0.02
*NODE PRINT, NSET=X2, TOTALS=ONLY
RF
*END STEP

It gives RF_x = 200.04, 274.47, 315.83 and 356.53 at t = 0.1 to 0.4, then 0 from t = 0.5 to 1.0, and Job finished.

Actual

Increments 1-4 match the *MODEL CHANGE run: RF_x = 200.04, 274.47, 315.83, and at t = 0.4, after the deletion, 0. Increment 5 then stops (exit status 255). The end of stdout is:

 increment 5 attempt 1 
 increment size= 1.000000e-01
 sum of previous increments=4.000000e-01
 actual step time=5.000000e-01
 actual total time=5.000000e-01

 iteration 1

 Using up to 1 cpu(s) for the stress calculation.

 Using up to 1 cpu(s) for the symmetric stiffness/mass contributions.

 Factoring the system of equations using the symmetric spooles solver
 Using 1 cpu for spooles.

spooles.out contains:



 matrix found to be singular

In delete_only.inp, element 2 is alone: element 1 and its material are left out, and X0 is moved to nodes 2, 6, 10, 14. Element 2 is deleted at the end of increment 6. ccx then gets a segmentation fault (signal 11) right after printing

 convergence

 Using up to 0 cpu(s) for the stress calculation.

Where it goes wrong

src/calcdamage.f (2.23 and master) deletes the element and nothing else:

            dam(jj,i)=dam(jj,i)+dpeq/ef
            if(dam(jj,i).gt.xlimit) then
              ipkon(i)=-ipkon(i)-2
              exit
            endif

It is called from src/nonlingeo.c after a converged increment:

    if((icutb==0)&&(*ndmat_>0)){
      FORTRAN(calcdamage,(ipkon,lakon,kon,co,mi,thicke,
			  ielmat,ielprop,prop,&ne0,ndmat_,ntmat_,
			  ndmcon,dmcon,dam,&dtime,sti,ithermal,t1,xstate,
			  xstateini,nstate_,vold));
    }

src/mastruct.c builds nactdof from the active elements, but only at the start of a step or when MPCs change:

  /* determining nactdof (only at start of step or if MPC's
     changed */

  if((*iit<0)||(*icascade!=0)){
    ...
	if(ipkon[i]<0) continue;

That is why *MODEL CHANGE works and a deletion in mid-step does not. The exit status 255 comes from spooles.c, which writes “matrix found to be singular” to spooles.out and calls exit(-1).

Workaround / fix

Workaround: none that keeps the deletion, apart from meshing so that every node of an element that can be deleted also belongs to an element that is never deleted. A limit that is never reached (e.g. 1.E30) turns the card into DUCT output only.

Fix: when calcdamage deletes an element, rebuild the active DOFs and the matrix structure before the next increment, as at the start of a step. Stop with an error message when no element is left.

In the official build of ccx 2.23 on Windows (note that these builds are not updated after the initial release), I get this output for delete.inp:

CSC Conversion Time: 0.000108
Init Time: 0.001247
Factorize Time: 0.003529
Solve Time: 0.089949
Clean up Time: 0.000000
---------------------------------
Sum: 0.094833

Total PaStiX Time: 0.517069
CCX without PaStiX Time: 0.633525
Share of PaStiX Time: 0.449393
Total Time: 1.150595
Reusability: 20 : 21
________________________________________

 Using up to 1 cpu(s) for the stress calculation.

 average force= 0.000000
 time avg. forc= 32.999435
 largest residual force= 0.000000 in node 14 and dof 3
 largest increment of disp= 1.110948e-03
 largest correction to disp= 2.325305e-47 in node 14 and dof 3

 convergence

 the increment size exceeds the remainder of the step and is decreased to 1.110223e-16

 Using up to 1 cpu(s) for the stress calculation.


 Job finished

________________________________________

Total CalculiX Time: 1,169311
________________________________________
                        S T E P       1


                                INCREMENT     1


 total force (fx,fy,fz) for set X2 and time  0.1000000E+00

        2.000424E+02 -5.031270E-03 -5.031270E-03

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.1000000E+00

         2   1  0.000000E+00
         2   2  0.000000E+00
         2   3  0.000000E+00
         2   4  0.000000E+00
         2   5  0.000000E+00
         2   6  0.000000E+00
         2   7  0.000000E+00
         2   8  0.000000E+00

                                INCREMENT     2


 total force (fx,fy,fz) for set X2 and time  0.2000000E+00

        2.744702E+02 -5.291282E+00 -5.291282E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.2000000E+00

         2   1  2.142784E-03
         2   2  1.391641E-03
         2   3  1.878601E-03
         2   4  1.255449E-03
         2   5  1.878601E-03
         2   6  1.255449E-03
         2   7  1.661171E-03
         2   8  1.141374E-03

                                INCREMENT     3


 total force (fx,fy,fz) for set X2 and time  0.3000000E+00

        3.158348E+02 -9.064652E+00 -9.064652E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.3000000E+00

         2   1  7.526958E-03
         2   2  3.009942E-03
         2   3  6.251518E-03
         2   4  2.666379E-03
         2   5  6.251518E-03
         2   6  2.666379E-03
         2   7  5.270646E-03
         2   8  2.389738E-03

                                INCREMENT     4


 total force (fx,fy,fz) for set X2 and time  0.4000000E+00

        0.000000E+00  0.000000E+00  0.000000E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.4000000E+00


                                INCREMENT     5


 total force (fx,fy,fz) for set X2 and time  0.5000000E+00

        0.000000E+00  0.000000E+00  0.000000E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.5000000E+00


                                INCREMENT     6


 total force (fx,fy,fz) for set X2 and time  0.6000000E+00

        0.000000E+00  0.000000E+00  0.000000E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.6000000E+00


                                INCREMENT     7


 total force (fx,fy,fz) for set X2 and time  0.7000000E+00

        0.000000E+00  0.000000E+00  0.000000E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.7000000E+00


                                INCREMENT     8


 total force (fx,fy,fz) for set X2 and time  0.8000000E+00

        0.000000E+00  0.000000E+00  0.000000E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.8000000E+00


                                INCREMENT     9


 total force (fx,fy,fz) for set X2 and time  0.9000000E+00

        0.000000E+00  0.000000E+00  0.000000E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.9000000E+00


                                INCREMENT    10


 total force (fx,fy,fz) for set X2 and time  0.1000000E+01

        0.000000E+00  0.000000E+00  0.000000E+00

 ductile damage initiation (elem, integ.pnt.,x,y,z) for set E2 and time  0.1000000E+01

Thanks for testing. Your output shows the difference: the official Windows build solves with PaStiX, and I ran it with SPOOLES (the conda-forge build of ccx 2.23 on macOS has no PaStiX or PARDISO, so SPOOLES is its default).

When element 2 is deleted, its four end nodes stay in the system of equations with zero stiffness. SPOOLES stops on that (“matrix found to be singular” in spooles.out, exit 255). PaStiX gets past it and leaves those DOFs at zero, so the step finishes, and your reactions are the expected ones. The iterative solvers behave like PaStiX: on my build, *STATIC, DIRECT, SOLVER=ITERATIVE CHOLESKY (or ITERATIVE SCALING) runs delete.inp to t = 1 with RF = 200.04, 274.47, 315.83, then 0.

So this case only stops the run with SPOOLES, though the DOFs are left in the system with any solver. Could you check two things on your build?

  1. delete.inp with *STATIC, DIRECT, SOLVER=SPOOLES.
  2. delete_only.inp below, where the deleted element is the only one. On my build it segfaults with every solver I have (SPOOLES and ITERATIVE CHOLESKY), right after “Using up to 0 cpu(s) for the stress calculation”.

*HEADING
Element 2 alone (element 1 left out): the only element is deleted
*NODE
1, 0., 0., 0.
2, 1., 0., 0.
3, 2., 0., 0.
5, 0., 1., 0.
6, 1., 1., 0.
7, 2., 1., 0.
9, 0., 0., 1.
10, 1., 0., 1.
11, 2., 0., 1.
13, 0., 1., 1.
14, 1., 1., 1.
15, 2., 1., 1.
*ELEMENT, TYPE=C3D8, ELSET=E2
2, 2, 3, 7, 6, 10, 11, 15, 14
*NSET, NSET=X0
2, 6, 10, 14
*NSET, NSET=X2
3, 7, 11, 15
*NSET, NSET=Y0
1, 2, 3, 9, 10, 11
*NSET, NSET=Z0
1, 2, 3, 5, 6, 7
*MATERIAL, NAME=DAMAGED
*ELASTIC
200000., 0.3
*PLASTIC
240., 0.
2240., 1.
*DAMAGE INITIATION, CRITERION=RICE TRACEY
1., 0.01
*SOLID SECTION, ELSET=E2, MATERIAL=DAMAGED
*BOUNDARY
X0, 1, 1
Y0, 2, 2
Z0, 3, 3
*STEP, NLGEOM, INC=100
*STATIC, DIRECT
0.1, 1.
*BOUNDARY
X2, 1, 1, 0.02
*NODE PRINT, NSET=X2, TOTALS=ONLY
RF
*EL PRINT, ELSET=E2
DUCT
*END STEP

Indeed, I can confirm it now, so it needs a GitHub ticket.

delete.inp with Spooles results in:

 Using up to 1 cpu(s) for the stress calculation.

 increment 5 attempt 1
 increment size= 1.000000e-01
 sum of previous increments=4.000000e-01
 actual step time=5.000000e-01
 actual total time=5.000000e-01

 iteration 1

 Using up to 1 cpu(s) for the stress calculation.

 Using up to 1 cpu(s) for the symmetric stiffness/mass contributions.

 Factoring the system of equations using the symmetric spooles solver
 Using up to 16 cpu(s) for spooles.

and delete_only.inp results in:

CSC Conversion Time: 0.000473
Init Time: 0.000986
Factorize Time: 0.003768
Solve Time: 0.009366
Clean up Time: 0.000000
---------------------------------
Sum: 0.014593

Total PaStiX Time: 0.285680
CCX without PaStiX Time: 0.399413
Share of PaStiX Time: 0.416995
Total Time: 0.685093
Reusability: 12 : 13
________________________________________

 Using up to 1 cpu(s) for the stress calculation.

 average force= 21.527018
 time avg. forc= 20.817165
 largest residual force= 0.000000 in node 14 and dof 3
 largest increment of disp= 9.829863e-04
 largest correction to disp= 1.500677e-09 in node 14 and dof 2

 convergence

 Using up to 0 cpu(s) for the stress calculation.