Two *KINEMATIC couplings on one REF NODE: the surfaces turn independently, and a moment on the reference node reaches only one, chosen by card order (exit 0)

Summary

Section 7.27 (*COUPLING) of the 2.23 manual: “With REF NODE a reference node is chosen, the degrees of freedom of which are used to define the constraint. In the reference node six degrees of freedom are available: 1 to 3 for translations […] and 4 to 6 for rotations”. Section 7.80 (*KINEMATIC): the displacement of a coupling node “corresponds to the rigid body motion of this node about a reference node”, and a moment (DOFs 4 to 6) can be applied to the reference node by *CLOAD. Neither section rules out several *KINEMATIC couplings on the same reference node. Section 7.27 does say that “it is not recommended to use the reference node in any other construct”, but in its paragraph on *DISTRIBUTING couplings. So the nodes of all couplings on one reference node should follow that node’s translations and rotations, i.e. move as one rigid body; if this is not meant to be supported, I would expect a message.

ccx gives each *KINEMATIC card its own internal rotation node. With two cards on one reference node the two surfaces share the reference node’s translations but not its rotations: they behave like two rigid bodies joined by a ball joint at the reference node. A moment on the reference node acts on only one of the two surfaces, and swapping the two cards in the deck swaps which one, so the sign of the response flips. The same goes for a *BOUNDARY on DOFs 4 to 6: with the reference node clamped in all six DOFs, one of the two surfaces can still turn about it, again chosen by card order. Exit 0, no message.

Test: two separate cantilevers (two C3D8 each), their free end faces coupled to one reference node between them. With one *COUPLING over both end faces, or with a *RIGID BODY over the same nodes, or with two *RIGID BODY cards sharing the reference and rotation node, a force in z at the reference node moves it 0.03444 and a moment about x does not move it in z. With one *COUPLING per end face: 0.05004 (+45 %) and ∓0.0156.

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 *KINEMATIC branch of couplings.f is unchanged on master (9ba5d76); master changed only the REF NODE parsing and the *DISTRIBUTING part.

Minimal input

two_kin.inp: bar A (elements 1, 2; y from 0 to 1) and bar B (elements 11, 12; y from 2 to 3), both clamped at x = 0. Their end faces at x = 2 are coupled to node 100 at (3, 1.5, 0.5) by one *KINEMATIC coupling each. A force of 1 in z acts on node 100.

** two separate cantilevers A (elements 1, 2) and B (elements 11, 12), both
** clamped at x = 0; their end faces at x = 2 are coupled to reference node
** 100 by one *KINEMATIC coupling each; load on node 100
*NODE, NSET=NALL
1, 0., 0., 0.
2, 1., 0., 0.
3, 2., 0., 0.
4, 0., 1., 0.
5, 1., 1., 0.
6, 2., 1., 0.
7, 0., 0., 1.
8, 1., 0., 1.
9, 2., 0., 1.
10, 0., 1., 1.
11, 1., 1., 1.
12, 2., 1., 1.
21, 0., 2., 0.
22, 1., 2., 0.
23, 2., 2., 0.
24, 0., 3., 0.
25, 1., 3., 0.
26, 2., 3., 0.
27, 0., 2., 1.
28, 1., 2., 1.
29, 2., 2., 1.
30, 0., 3., 1.
31, 1., 3., 1.
32, 2., 3., 1.
100, 3., 1.5, 0.5
*ELEMENT, TYPE=C3D8, ELSET=EALL
1, 1, 2, 5, 4, 7, 8, 11, 10
2, 2, 3, 6, 5, 8, 9, 12, 11
11, 21, 22, 25, 24, 27, 28, 31, 30
12, 22, 23, 26, 25, 28, 29, 32, 31
*NSET, NSET=CLAMP
1, 4, 7, 10, 21, 24, 27, 30
*NSET, NSET=REF
100
*MATERIAL, NAME=M
*ELASTIC
1000., 0.3
*SOLID SECTION, ELSET=EALL, MATERIAL=M
*SURFACE, NAME=SA
2, S4
*SURFACE, NAME=SB
12, S4
*COUPLING, REF NODE=100, SURFACE=SA, CONSTRAINT NAME=CA
*KINEMATIC
1, 3
*COUPLING, REF NODE=100, SURFACE=SB, CONSTRAINT NAME=CB
*KINEMATIC
1, 3
*BOUNDARY
CLAMP, 1, 3
*STEP
*STATIC
*CLOAD
100, 3, 1.
*NODE PRINT, NSET=REF
U
*NODE PRINT, NSET=NALL
U
*END STEP

Variants (each changes only the lines shown):

  • two_kin_swap.inp: the two *COUPLING cards (each with its *KINEMATIC and 1, 3 line) in the opposite order, CB first.

  • one_kin.inp: one coupling over both end faces; the two *SURFACE cards and the two couplings replaced by

    *SURFACE, NAME=SAB
    2, S4
    12, S4
    *COUPLING, REF NODE=100, SURFACE=SAB, CONSTRAINT NAME=CAB
    *KINEMATIC
    1, 3
    
  • one_rb.inp: a *RIGID BODY over the same eight nodes instead; node 101, 3., 1.5, 0.5 added after node 100, and the surfaces and couplings replaced by

    *NSET, NSET=ENDS
    3, 6, 9, 12, 23, 26, 29, 32
    *RIGID BODY, NSET=ENDS, REF NODE=100, ROT NODE=101
    
  • two_rb.inp: as one_rb.inp, but with one *RIGID BODY card per end face, both on REF NODE 100 and ROT NODE 101:

    *NSET, NSET=EA
    3, 6, 9, 12
    *NSET, NSET=EB
    23, 26, 29, 32
    *RIGID BODY, NSET=EA, REF NODE=100, ROT NODE=101
    *RIGID BODY, NSET=EB, REF NODE=100, ROT NODE=101
    

Loads (the *CLOAD line): 100, 3, 1. (force in z, as printed), 100, 1, 1. (force in x), 100, 4, 1. (moment about x), 100, 6, 1. (moment about z). In the *RIGID BODY decks a moment goes on the rotation node: 101, 1, 1. for the moment about x, 101, 3, 1. for the moment about z.

Clamped reference node: two_kin_held.inp, two_kin_swap_held.inp and one_kin_held.inp are two_kin.inp, two_kin_swap.inp and one_kin.inp with the line 100, 1, 6 added after CLAMP, 1, 3 and the *CLOAD line replaced by 29, 3, 1. (a force in z on node 29, on the coupled end face of bar B).

How to run

ccx two_kin
ccx two_kin_swap
ccx one_kin
ccx one_rb
ccx two_rb

(once per load), and

ccx two_kin_held
ccx two_kin_swap_held
ccx one_kin_held

Expected

All five decks describe the same structure: the end faces of both bars moving rigidly with node 100. They should give the same displacements, and by symmetry a moment about x or z should not move node 100 in z or x. In the _held decks node 100 is clamped, so the coupled end faces cannot move at all: node 29 should stay at 0.

Actual

Displacement of node 100 (exit 0 in every run):

load on node 100 component one_kin, one_rb two_kin two_kin_swap
force in x u1 9.100000E-04 1.857040E-03 1.857040E-03
force in z u3 3.444381E-02 5.004381E-02 5.004381E-02
moment about x u3 -1.950326E-17 -1.560000E-02 1.560000E-02
moment about z u1 -5.362901E-19 9.470405E-04 -9.470405E-04

two_rb gives the one_rb values for every load. A force in y and a moment about y give the same u at node 100 in all decks (7.580758E-03 and -1.505524E-02).

Under the force in z, one_kin moves the end faces without turning them about x (nodes 3, 6, 23 and 26 all 1.938857E-02 in z), while in two_kin they turn in opposite directions about x: node 3 1.158857E-02, node 6 2.718857E-02, node 23 2.718857E-02, node 26 1.158857E-02.

With the reference node clamped (exit 0 in all three runs), one_kin_held and two_kin_held keep nodes 23, 26, 29 and 32 at 0. In two_kin_swap_held the end face of bar B turns about the clamped node 100: node 29 moves (6.518225E-04, -6.754082E-04, 1.979053E-03) and node 26 (-6.518225E-04, 6.754082E-04, 3.329870E-03).

Where it goes wrong

src/couplings.f, for each *KINEMATIC card (master lines 334-390, 2.23 lines 311-367), creates a new rotation node irotnode_kin (line 347) and three ROTTRACOUPLING MPCs between the reference node’s DOFs 4-6 and that node’s translations:

          labmpc(nmpc)='ROTTRACOUPLING      '
          idof=8*(irefnode-1)+k+3
          call nident(ikmpc,idof,nmpc-1,id)
          do l=nmpc,id+2,-1
            ikmpc(l)=ikmpc(l-1)
            ilmpc(l)=ilmpc(l-1)
          enddo
          ikmpc(id+1)=idof
          ilmpc(id+1)=nmpc

The coupling nodes are then tied to the reference node and to irotnode_kin by rigidmpc (lines 476-484). A second card on the same reference node gets a second rotation node, and its ROTTRACOUPLING MPCs are inserted into ikmpc with the same dependent DOFs as the first card’s, without a check (lines 370-377). Nothing ties the two rotation nodes together, so each surface has its own rotation.

The ROTTRACOUPLING MPCs are what forcadd.f (lines 48-62) and bounadd.f (lines 55-77) use to send a *CLOAD or *BOUNDARY on DOFs 4-6 of the reference node to the rotation node: they look the DOF up with nident(ikmpc,idof,...) and take the rotation node of the entry they find. With two entries for the same DOF the lookup finds one of them, so a moment reaches only one rotation node and a *BOUNDARY on DOFs 4-6 holds only one, and which one depends on the order of the cards.

*RIGID BODY does not have the problem, because the user can give both cards the same ROT NODE (two_rb.inp).

Fix

Possible fix (not built or tested): in couplings.f, before creating a rotation node, look up 8*(irefnode-1)+4 in ikmpc. If it is already there with label ROTTRACOUPLING, take that MPC’s rotation node (nodempc(1,nodempc(3,ipompc(ilmpc(id)))), as forcadd.f does) as irotnode_kin and skip the new node and the three new MPCs. All couplings on one reference node then share one rotation, as two *RIGID BODY cards with the same ROT NODE do. If sharing is not meant to be supported, an *ERROR when a reference node gets a second *KINEMATIC coupling would at least avoid the silent result.

Workaround: put all surfaces that should move together with one reference node into one *SURFACE and one *COUPLING (one_kin.inp), or use *RIGID BODY cards with a common ROT NODE.

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

two_kin.dat:

100 3.718670E-18 1.757190E-17 5.004381E-02

two_kin_swap.dat:

100 3.718670E-18 1.757190E-17 5.004381E-02

one_kin.dat:

100 -1.423228E-18 7.538206E-18 3.444381E-02

1 Like

posted: Two *KINEMATIC couplings on one REF NODE: the surfaces turn independently, and a moment on the reference node reaches only one, chosen by card order (exit 0) · Issue #281 · Dhondtguido/CalculiX · GitHub

1 Like