Point mass (MASS element) on a node with no other element gets no DOFs: stays at 0 in *DYNAMIC (exit 0); tied by *EQUATION it stops *FREQUENCY or loses mass terms

Summary

Section 6.2.44 (One-node mass element (MASS)) of the 2.23 manual: “This element is used to define nodal masses.” Section 7.82 (*MASS) assigns the mass value to each element of the set.

ccx assembles the mass of a MASS element (mafillsm.f treats it as a one-node element with three DOFs), but mastruct.c skips MASS elements both when it decides which DOFs are active and when it sets up the structure of the matrix. As long as the node also belongs to another element, nothing shows. When the MASS element is the only element at its node:

  1. Free point mass, *DYNAMIC: the node gets no degrees of freedom. A point mass of 2 pulled by a force (1, 0.5, 0) for one time unit stays at 0, with exit 0 and no message; F = m a is not satisfied. The same mass with a spring of stiffness 0 to a held node moves (0.25, 0.125, 0), exactly as it should. Implicit, NLGEOM and explicit *DYNAMIC give the same 0. (Only when the point mass is the whole model does ccx warn “no degrees of freedom in the model”.)
  2. Point mass tied by an *EQUATION, *FREQUENCY, one independent node held: the node gets its DOF through the equation, but the matrix has no place for the mass terms that the equation moves to the independent DOFs. ccx stops with “*ERROR in add_bo_st: coefficient should be 0” (exit 201), an internal message that names neither the element nor the node. The same stop occurs when the point mass is instead the only member of a *RIGID BODY whose reference node sits on a spring and whose rotation node is held.
  3. The same, both independent nodes free: ccx prints “*ERROR in add_sm_ei: coefficient should be 0”, drops the coupling mass term and goes on: “Job finished”, exit 0, and wrong eigenvalues (2000 and 2000 instead of 1000 and 10000). As an implicit *DYNAMIC step the same model stops with “*ERROR in add_sm_st: coefficient should be 0” (exit 201).

In each case a spring of stiffness 0 from the mass node to a held node, which changes nothing physically, gives the right answer.

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 element loops of mastruct.c are unchanged on master (9ba5d76).

Related:

Minimal input

mass_two.inp: two point masses of 2, each pulled by the same force. Node 1 has only its MASS element; node 3 also has a spring of stiffness 0 to the held node 2.

** two point masses of 2, each pulled by a force (1, 0.5, 0) in a *DYNAMIC step:
** node 1 carries only its MASS element; node 3 also has a spring of stiffness 0
** to the held node 2
*NODE, NSET=NALL
1, 0., 0., 0.
2, 1., 0., 0.
3, 2., 0., 0.
*ELEMENT, TYPE=MASS, ELSET=EM
1, 1
3, 3
*MASS, ELSET=EM
2.
*ELEMENT, TYPE=SPRINGA, ELSET=ES
2, 3, 2
*SPRING, ELSET=ES

0.
*BOUNDARY
2, 1, 3
*STEP, INC=1000
*DYNAMIC
0.01, 1.
*CLOAD
1, 1, 1.
1, 2, 0.5
3, 1, 1.
3, 2, 0.5
*NODE PRINT, NSET=NALL, FREQUENCY=100
U
*END STEP

lever_freq.inp: a point mass of 2 (node 1) tied by an *EQUATION to the middle of a rigid lever between node 2, on a spring of stiffness 1000, and the held node 4. Five separate spring-mass oscillators (nodes 11-15) only give the eigensolver enough unknowns.

** point mass 2 at node 1, tied by an *EQUATION to the middle of a rigid lever:
** u1(node 1) = 0.5 u1(node 2) + 0.5 u1(node 4); node 4 is held, node 2 sits
** on a spring of stiffness 1000 to the held node 3.
** Nodes 11-15: five more spring-mass oscillators, only to give the
** eigensolver enough unknowns
*NODE, NSET=NALL
1, 1., 0., 0.
2, 2., 0., 0.
3, 3., 0., 0.
4, 0., 0., 0.
11, 4., 0., 0.
12, 5., 0., 0.
13, 6., 0., 0.
14, 7., 0., 0.
15, 8., 0., 0.
*NSET, NSET=OSC
11, 12, 13, 14, 15
*ELEMENT, TYPE=MASS, ELSET=EM
1, 1
*MASS, ELSET=EM
2.
*ELEMENT, TYPE=MASS, ELSET=EMO
21, 11
22, 12
23, 13
24, 14
25, 15
*MASS, ELSET=EMO
1.
*ELEMENT, TYPE=SPRINGA, ELSET=ES
2, 2, 3
*SPRING, ELSET=ES

1000.
*ELEMENT, TYPE=SPRINGA, ELSET=ESO
31, 11, 3
32, 12, 3
33, 13, 3
34, 14, 3
35, 15, 3
*SPRING, ELSET=ESO

10000.
*EQUATION
3
1, 1, 1., 2, 1, -0.5, 4, 1, -0.5
*BOUNDARY
1, 2, 3
2, 2, 3
3, 1, 3
4, 1, 3
OSC, 2, 3
*STEP
*FREQUENCY
1
*END STEP

lever2_freq.inp: lever_freq.inp with node 4 on a spring of 1000 as well instead of held in x. Changed lines (... stands for unchanged lines):

 *ELEMENT, TYPE=SPRINGA, ELSET=ES
 2, 2, 3
+4, 4, 3
 ...
 *BOUNDARY
 ...
-4, 1, 3
+4, 2, 3
 ...
 *FREQUENCY
-1
+2

lever_freq_z.inp and lever2_freq_z.inp: the two lever decks with a spring of stiffness 0 from the mass node 1 to the held node 3, these lines added just before *EQUATION:

*ELEMENT, TYPE=SPRINGA, ELSET=EZ
41, 1, 3
*SPRING, ELSET=EZ

0.

lever2_dyn.inp: lever2_freq.inp with the step replaced by

*NSET, NSET=P
2, 4
*STEP, INC=1000
*DYNAMIC
0.001, 0.1
*CLOAD
2, 1, 1.
*NODE PRINT, NSET=P, FREQUENCY=100
U
*END STEP

lever_rb.inp: lever_freq.inp with the point mass as the only member of a *RIGID BODY on node 2 instead of the *EQUATION: node 5, 0., 0., 0. added after node 15, the three *EQUATION lines replaced by *NSET, NSET=RB / 1 / *RIGID BODY, NSET=RB, REF NODE=2, ROT NODE=5, and the boundary line 1, 2, 3 replaced by 5, 1, 3. lever_rb_z.inp: the same with the spring of stiffness 0 from node 1 to node 3 (the lines added in the _z decks) inserted before *NSET, NSET=RB.

How to run

ccx mass_two
ccx lever_freq
ccx lever_freq_z
ccx lever2_freq
ccx lever2_freq_z
ccx lever2_dyn
ccx lever_rb
ccx lever_rb_z

Expected

mass_two: both masses move u = F t²/(2m) = (0.25, 0.125, 0) at t = 1.

lever_freq: the mass moves half as far as node 2, so node 2 sees a mass of m/4 = 0.5 on a spring of 1000: ω² = 2000 (7.117625 cycles/time).

lever2_freq: in (u2, u4) the mass matrix is (m/4)·[[1, 1], [1, 1]] and the stiffness diag(1000, 1000). The only finite mode of the lever is u2 = u4 with ω² = 2k/m = 1000; the next eigenvalue is the oscillators’ 10000.

lever_rb: with the rotations held, the mass moves with node 2, so ω² = k/m = 500 (3.558813 cycles/time).

Actual

mass_two, t = 1:

         1  0.000000E+00  0.000000E+00  0.000000E+00
         2  0.000000E+00  0.000000E+00  0.000000E+00
         3  2.500000E-01  1.250000E-01  0.000000E+00

The same with *DYNAMIC, EXPLICIT (increment 0.001) or with NLGEOM on the *STEP card: node 1 stays at 0, node 3 moves (0.25, 0.125, 0). The only message is the usual “no elastic constants were assigned to material MASS” warning (and, in the explicit run, the note that the time increment is determined automatically).

lever_freq: stdout ends with

 Frequency analysis was selected

 *ERROR in add_bo_st: coefficient should be 0

and exit status 201. lever_freq_z gives 1 0.2000000E+04 0.4472136E+02 0.7117625E+01, as expected.

lever2_freq: stdout shows “*ERROR in add_sm_ei: coefficient should be 0”, then “Job finished” (exit 0), and the .dat file has

      1   0.2000000E+04   0.4472136E+02   0.7117625E+01   0.0000000E+00
      2   0.2000000E+04   0.4472136E+02   0.7117625E+01   0.0000000E+00

lever2_freq_z gives the expected values:

      1   0.1000000E+04   0.3162278E+02   0.5032921E+01   0.0000000E+00
      2   0.1000000E+05   0.1000000E+03   0.1591549E+02   0.0000000E+00

lever2_dyn: “*ERROR in add_sm_ei: coefficient should be 0”, then “*ERROR in add_sm_st: coefficient should be 0”, exit status 201.

lever_rb: “*ERROR in add_bo_st: coefficient should be 0”, exit status 201. lever_rb_z gives 1 0.5000000E+03 0.2236068E+02 0.3558813E+01, as expected.

Where it goes wrong

src/mastruct.c decides which DOFs are active from the element types (lines 123-180). It knows continuum elements, user elements and the E elements (springs, dashpots, contact); every other type, MASS included, ends in

	}else continue;

(line 180), so a node that only has a MASS element gets no DOFs. The loop that sets up the matrix structure (lines 354-411) has the same }else continue; (line 411), so the MASS element reserves no positions in the matrix either. src/mastructcs.c has the same two loops.

src/mafillsm.f, on the other hand, assembles MASS elements (lines 165-167: elseif(lakon(i)(1:4).eq.'MASS') then / nope=1 / ndof=3), and the LOGBOOK entry of 16 July 2020 (“made a change in mafillsmcs.f: MASS elements were not taken into account”) shows that MASS elements are meant to be in the matrices.

The three cases above follow from that:

  1. No DOFs at node 1: the force and the mass are both dropped.
  2. Through the *EQUATION, the MPC loop of mastruct.c (lines 239-275) activates u1 of node 1 as a dependent DOF. In mafillsm.f the mass of node 1 then goes, through the “MPC id1 / MPC id1” branch (line 447 on), to the pairs of the equation’s independent DOFs: (node 2, node 2), which is on the diagonal, and (node 2, held node 4), a “boundary stiffness” column of the frequency step. That position was never reserved, so add_bo_st.f stops (lines 35-36).
  3. With node 4 free, the pair (node 2, node 4) is an ordinary off-diagonal term. add_sm_ei.f finds no position for it, prints the message and returns without storing the value: its call exit(201) is commented out (lines 56-58). The eigenvalue problem is then solved with the diagonal terms only, which gives 4k/m = 2000 twice. In *DYNAMIC, add_sm_st.f (lines 50-51) stops at the same pair.

Fix

Possible fix (not built or tested): in both element loops of mastruct.c (and mastructcs.c), handle MASS elements like a one-node element with three DOFs, e.g. before the final else continue:

	else if(strcmp1(&lakon[8*i],"MASS")==0){nope=1;ndof=3;}

Then a point mass gets its DOFs, and the matrix gets the positions that its mass needs through MPCs. In a static step a free point mass then has three DOFs without stiffness, which is singular, as the model physically is. If that is not wanted, the DOFs of MASS-only nodes could be activated only in procedures that assemble a mass matrix.

Separately, add_sm_ei.f should not drop a nonzero coefficient silently after printing an *ERROR; the commented-out exit(201) makes case 3 a wrong result instead of a stop.

Workaround: give every MASS-only node a spring of stiffness 0 (SPRINGA to a held node, as in mass_two.inp and the _z decks).

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

mass_two.dat:

                        S T E P       1


                                INCREMENT    11


 displacements (vx,vy,vz) for set NALL and time  0.1000000E+01

         1  0.000000E+00  0.000000E+00  0.000000E+00
         2  0.000000E+00  0.000000E+00  0.000000E+00
         3  2.500000E-01  1.250000E-01  0.000000E+00

lever_freq:

STEP            1

 *WARNING in calinput: no elastic constants
  were assigned to material MASS
          in a (thermo)mechanical calculation

 *WARNING in calinput: no elastic constants
  were assigned to material MASS
          in a (thermo)mechanical calculation

 Frequency analysis was selected

 Decascading the MPC's

 Determining the structure of the matrix:
 Using up to 6 cpu(s) for setting up the structure of the matrix.
 number of equations
 6
 number of nonzero lower triangular matrix elements
 0

 Using up to 6 cpu(s) for setting up the structure of the matrix.
 Using up to 12 cpu(s) for the stress calculation.

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

 *ERROR in add_bo_st: coefficient should be 0

posted: Point mass (MASS element) on a node with no other element gets no DOFs: stays at 0 in *DYNAMIC (exit 0); tied by *EQUATION it stops *FREQUENCY or loses mass terms · Issue #279 · Dhondtguido/CalculiX · GitHub

1 Like