*ORIENTATION: a *DISTRIBUTION name that reads as a number (D1, E1, E5, 12, ...) is taken as zero coordinates, and the run stops with only "matrix found to be singular"

Summary

The data line of an *ORIENTATION holds either the coordinates of a and b, or the name of a *DISTRIBUTION. orientations.f tells the two apart by trying to read the fields as reals, and takes the line as a name only when that read fails.

gfortran’s (f20.0) read accepts many ordinary names as numbers without an error:

  • D1, E1, E5 and Q1 read as 0.0. The letter is taken as an exponent with no mantissa.
  • 1 and 12 read as numbers.

The orientation then becomes a plain one with a = b = 0 (or a = (12,0,0), b = 0), with no message.

transformatrix.f normalises a and b by their lengths, so the axes become 0/0 = NaN. For an orthotropic or anisotropic material the run then stops with exit code 255. The only output is “matrix found to be singular” in spooles.out, and nothing points at the orientation. The same deck works with the distribution named DIST.

Version

ccx 2.23 (conda-forge build pl5321h484372f_2, macOS arm64). orientations.f is the same on master.

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 17 Oct 2026). Not fixed: the routine involved is unchanged on master, and conda-forge’s latest 2.23 build (pl5321hd17ca8b_8, which carries about 40 extra patches) gives the same output.

Minimal input

distname.inp:

** Unit cube, orthotropic: E1 = 1000, E2 = E3 = 100, nu = 0, G = 50.
** Local axes = global axes, given by a distribution named D1.
** Uniaxial stress 1 in x: exact u_x(x = 1) = 1/E1 = 0.001.
*NODE, NSET=NALL
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
3, 1.0, 1.0, 0.0
4, 0.0, 1.0, 0.0
5, 0.0, 0.0, 1.0
6, 1.0, 0.0, 1.0
7, 1.0, 1.0, 1.0
8, 0.0, 1.0, 1.0
*ELEMENT, TYPE=C3D8, ELSET=EALL
1, 1, 2, 3, 4, 5, 6, 7, 8
*NSET, NSET=XMAX
2, 3, 6, 7
*MATERIAL, NAME=MAT
*ELASTIC, TYPE=ENGINEERING CONSTANTS
1000.0, 100.0, 100.0, 0.0, 0.0, 0.0, 50.0, 50.0
50.0
*DISTRIBUTION, NAME=D1
, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0
*ORIENTATION, NAME=OR
D1
*SOLID SECTION, ELSET=EALL, MATERIAL=MAT, ORIENTATION=OR
*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

How to run

ccx distname

Expected

The distribution’s default line gives local axes equal to the global axes, so u_x = 1/E1 = 0.001 at nodes 2, 3, 6 and 7. That is what ccx gives when the distribution and the orientation’s data line are both renamed to DIST.

The manual (7.43) puts no restriction on the name beyond being required and at most 80 characters long. Where a name cannot be told apart from coordinates, ccx should at least report that the orientation is degenerate.

Actual

Exit code 255. stdout ends with:

 STEP            1

 Static analysis was selected

The .dat holds only the step header. spooles.out contains:

 matrix found to be singular

The names E1, E5, Q1, 1 and 12 give the same result. A plain *ORIENTATION with the line 0., 0., 0., 0., 0., 0. gives exactly the same output.

Where it goes wrong

orientations.f, master lines 88–95:

      do i=1,6
         read(textpart(i)(1:20),'(f20.0)',iostat=istat) ab(i)
         if(istat.gt.0) then
            distribution=.true.
            read(textpart(1)(1:80),'(a80)',iostat=istat) distname
            exit
         endif
      enddo

For D1, all six reads succeed: D1 gives 0.0 and the empty fields 2 to 6 give 0.0. So distribution stays .false. and the name is never looked up.

Distributions are read before any *ORIENTATION (keystart.f), so textpart(1) could be checked against the existing distribution names first. Alternatively, a line with fewer than six non-empty fields could be taken as a name. A check that |a| > 0 and that b is not parallel to a would also catch degenerate plain orientations.

Workaround

Give the distribution a name that does not read as a number, such as DIST or DIST1.

A related issue:

Summary

An *ORIENTATION can refer to a *DISTRIBUTION and add a rotation about a local axis on its second line. That rotation is applied correctly only to the distribution’s first line, the default system. orientations.f converts the angle from degrees to radians inside its loop over the distribution’s lines, so line k is turned by angle·(π/180)^k:

  • With “3, 90.”, an element listed on the second line is turned by 1.57 degrees instead of 90.
  • Lines further down are turned even less.

Version

ccx 2.23 (conda-forge build pl5321h484372f_2, macOS arm64). orientations.f is the same on master.

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 17 Oct 2026). Not fixed: the routine involved is unchanged on master, and conda-forge’s latest 2.23 build (pl5321hd17ca8b_8, which carries about 40 extra patches) gives the same output.

Minimal input

distrot.inp:

** Unit cube, orthotropic: E1 = 1000, E2 = E3 = 100, nu = 0, G = 50.
** Local axes from a distribution, then turned 90 degrees about local z,
** so local 1 = global y and the stiffness along global x is E2 = 100.
** Uniaxial stress 1 in x: exact u_x(x = 1) = 1/E2 = 0.01.
*NODE, NSET=NALL
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
3, 1.0, 1.0, 0.0
4, 0.0, 1.0, 0.0
5, 0.0, 0.0, 1.0
6, 1.0, 0.0, 1.0
7, 1.0, 1.0, 1.0
8, 0.0, 1.0, 1.0
*ELEMENT, TYPE=C3D8, ELSET=EALL
1, 1, 2, 3, 4, 5, 6, 7, 8
*NSET, NSET=XMAX
2, 3, 6, 7
*MATERIAL, NAME=MAT
*ELASTIC, TYPE=ENGINEERING CONSTANTS
1000.0, 100.0, 100.0, 0.0, 0.0, 0.0, 50.0, 50.0
50.0
*DISTRIBUTION, NAME=DIST
, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0
1, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0
*ORIENTATION, NAME=OR
DIST
3, 90.0
*SOLID SECTION, ELSET=EALL, MATERIAL=MAT, ORIENTATION=OR
*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

How to run

ccx distrot

Expected

The 90 degree rotation about local z makes local 1 the global y-axis, so the stiffness along global x is E2 = 100. Under the uniaxial stress σ_x = 1, u_x = 1/E2 = 0.01 at nodes 2, 3, 6 and 7, and u_y = 0.

ccx gives exactly that when the element takes the default line. To see it, delete the line 1, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, which lists the same axes for element 1. It also gives 0.01 with a plain *ORIENTATION of 1.0, 0.0, 0.0, 0.0, 1.0, 0.0 and 3, 90.0.

The manual, section 10.5, describes the same thing: every orientation created from the distribution’s lines gets the additional rotation, i.e. “orab(1..6,i) is modified appropriately”.

Actual

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

         2  1.013521E-03 -4.928624E-04 -3.774999E-19
         3  1.013521E-03 -4.996201E-04 -1.117740E-20
         6  1.013521E-03 -4.928624E-04 -3.252248E-19
         7  1.013521E-03 -4.996201E-04 -3.519933E-20

This is the compliance of the material turned by 1.5708 degrees, which is 90·π/180 read as degrees:

  • cos⁴θ/E1 + sin⁴θ/E2 + sin²θ·cos²θ/G12 = 1.013521e-3.
  • The u_y values come from the shear coupling of that small off-axis angle.

ccx prints no warning.

Where it goes wrong

orientations.f, in the branch for distributions (master lines 195–226):

         do iorien=1,norien
            if(orname(iorien).eq.distname) then
               ...
               if(iaxis.ne.0) then
                  ...
                  pi=4.d0*datan(1.d0)
                  angle=angle*pi/180.d0

angle is overwritten on every pass, so the first distribution line gets 90·π/180 rad and the second gets (90·π/180)·π/180 rad. The fix is to do the conversion once, before the loop, or into a separate variable.

Workaround

Put the rotation into the distribution’s axes themselves and leave out the rotation line of the *ORIENTATION.

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

                                INCREMENT     1


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

         2  1.013521E-03 -4.928624E-04 -1.770255E-17
         3  1.013521E-03 -4.996201E-04 -4.546456E-18
         6  1.013521E-03 -4.928624E-04 -1.051490E-17
         7  1.013521E-03 -4.996201E-04  6.576218E-19
1 Like

Posted: *ORIENTATION with a *DISTRIBUTION: the additional rotation reaches only the first distribution line, because the angle is converted to radians again for every line · Issue #218 · Dhondtguido/CalculiX · GitHub

1 Like

Here are more cases of the same missing *ORIENTATION input check as in the first post, this time with plain data lines instead of a distribution name. The input is invalid: section 7.103 (*ORIENTATION) of the 2.23 manual defines a rectangular system by “a point a on the local X’ axis and a point b belonging to the X’-Y’ plane but not on the X’ axis”, which rules out a at the origin and b on the X’ axis.

So I am not asking ccx to accept such a line. I am asking for a clear *ERROR that names the orientation, instead of NaN output (isotropic material, exit 0) or an exit with no message (orthotropic material, exit 255).

Users do write such lines. The 2021 topic TYPE ORTHO not known hit the same input: post 1 has a = (0, 0, 0) (0.,0.,0.,1.,0.,0. and 0.,0.,0.,0.,1.,0.), and Calc_em traced that run’s failure to the orientation by reading the input (post 5). Post 14 there (2023) has 1.,1.,0.,0.,0.,0., i.e. b = (0, 0, 0), which lies on the X’ axis through a; that case was not resolved in the topic.

Checked on ccx 2.23 (macOS arm64); orientations.f, transforms.f, transformatrix.f and umat_def_plas.f are unchanged on current master (537aa81, 2 Nov 2026), and the *DEFORMATION PLASTICITY and *TRANSFORM cases at the end were also run on that master.

The cases

The first post already showed the orthotropic stop for 0., 0., 0., 0., 0., 0.. Three more plain data lines do the same:

  • only point a (1., 0., 0.): the missing b is read as (0, 0, 0), which lies on the X’ axis;
  • b on the axis (1., 0., 0., 2., 0., 0.);
  • a at the origin (0., 0., 0., 1., 0., 0.).

transformatrix.f then divides by a zero length, and the local axes are NaN:

  • Isotropic material: the run finishes with exit 0 and correct displacements. But the element stresses, which ccx prints in the local system, have NaN in five of their six components (all six for a = 0). There is no warning.
  • Orthotropic material (TYPE=ENGINEERING CONSTANTS): exit status 255. stdout shows nothing after “Static analysis was selected”, and spooles.out contains “matrix found to be singular”, as in the first post.

Deck

ori_degenerate.inp:

** Unit cube, uniaxial stress 1 in x. The *ORIENTATION line gives point a
** only: point b is (0,0,0), which lies on the local x axis.
*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
*NSET, NSET=XMAX
2, 3, 6, 7
*MATERIAL, NAME=MAT
*ELASTIC
1000., 0.
*ORIENTATION, NAME=OR
1., 0., 0.
*SOLID SECTION, ELSET=EALL, MATERIAL=MAT, ORIENTATION=OR
*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
*EL PRINT, ELSET=EALL
S
*END STEP

Run it with ccx ori_degenerate.

For the other two cases replace the line under *ORIENTATION by 1., 0., 0., 2., 0., 0. or 0., 0., 0., 1., 0., 0.; for the reference, by 1., 0., 0., 0., 1., 0.. For the orthotropic case replace the *ELASTIC lines by

*ELASTIC, TYPE=ENGINEERING CONSTANTS
1000., 100., 100., 0., 0., 0., 50., 50.
50.

Expected

An *ERROR that orientation OR is degenerate (a at the origin, or b missing or on the local X’ axis), since section 7.103 rules these points out. With the valid line 1., 0., 0., 0., 1., 0. ccx gives u_x = 1.000000E-03 at nodes 2, 3, 6, 7 and the stresses 1, 0, 0, 0, 0, 0 (to 1e-16), for both materials.

Actual

Isotropic, as written: exit 0. The displacements are right (u_x = 1.000000E-03 at node 7). The stresses:

 stresses (elem, integ.pnt.,sxx,syy,szz,sxy,sxz,syz) for set EALL and time  0.1000000E+01

         1   1  1.000000E+00           NaN           NaN           NaN           NaN           NaN OR                  

and the same for integration points 2-8. 1., 0., 0., 2., 0., 0. gives the same output. 0., 0., 0., 1., 0., 0. gives NaN in all six components, also with exit 0.

Orthotropic: exit status 255 with any of the three lines. stdout ends with “Static analysis was selected”, and spooles.out contains “matrix found to be singular”.

Where it goes wrong

src/orientations.f (master lines 88-95, quoted in the first post) reads six reals from the data line. Missing fields read as 0 without an error, and nothing checks the two points. src/transformatrix.f (lines 46-59) then normalises a, removes its component from b, and normalises the rest:

         dd=dsqrt(e2(1)*e2(1)+e2(2)*e2(2)+e2(3)*e2(3))
         do j=1,3
            e2(j)=e2(j)/dd
         enddo

When b is on the X’ axis, dd is 0 and e2 and e3 become NaN. When a is the origin, the first normalisation (lines 46-49) already divides by 0. The cylindrical branch of the same routine already treats its degenerate case (a point on the axis, lines 91-111). The rectangular branch has no such guard.

Possible fix

In orientations.f, after reading a plain orientation, stop with an *ERROR naming the orientation when |a| = 0 or when b has no component perpendicular to a (relative to |a| |b|). That is the check suggested at the end of the first post. It should not change any valid input: for a valid orientation both quantities are clearly nonzero.

*DEFORMATION PLASTICITY: a hang instead of NaN

With *DEFORMATION PLASTICITY, the same input never finishes. In ori_degenerate.inp, replace the *ELASTIC lines by

*DEFORMATION PLASTICITY
1000., 0., 10., 5., 0.5

With any of the three degenerate lines (1., 0., 0.; 1., 0., 0., 2., 0., 0.; 0., 0., 0., 1., 0., 0.), ccx 2.23 and master 537aa81 hang: the first increment never ends. The .sta file stays empty, stdout stops after “Nonlinear geometric effects are taken into account”, and the run uses full CPU until it is killed (I stopped it after 60 s). With the valid line 1., 0., 0., 0., 1., 0., or without the orientation, the run finishes at once with u_x = 1.000500E-03 at node 7.

A stack sample of the hanging 2.23 process shows every sample in umat_def_plas, called from umat_abaqusnl_total ← mechmodel ← resultsmech ← results ← nonlingeo. The NaN local axes reach the Newton loop for the Mises stress in src/umat_def_plas.f (lines 88-93, the same in 2.23 and on master):

      do
        c2=al*(q/s0)**(xn-1.d0)
        dq=(ee*eq-(c1+c2)*q)/(c1+xn*c2)
        if((dabs(dq).lt.q*1.d-4).or.(dabs(dq).lt.1.d-10)) exit
        q=q+dq
      enddo

The loop has no iteration limit, and with q = NaN the exit test is never true. The orientation check above would prevent this. An iteration limit with an *ERROR in that loop would also turn any other NaN strain into a message instead of a hang.

The same gap in *TRANSFORM

Section 7.135 (*TRANSFORM) of the 2.23 manual defines a rectangular system with the same two points, “a point a on the local X’ axis and a point b belonging to the X’-Y’ plane but not on the X’ axis”, and transforms.f also stores them without a check. They go through the same transformatrix.f (lines 46-49 and 56-59), so the division by zero is the same. But *TRANSFORM never passes through orientations.f, so the check has to be made in transforms.f as well, after the data line is read (line 97).

tr_a0.inp, a unit cube whose four nodes at x = 1 carry a rectangular *TRANSFORM with a at the origin:

** Unit cube (E = 1000, nu = 0); the four nodes at x = 1 carry a *TRANSFORM
** and a load of 0.25 in local direction 1.
*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
*NSET, NSET=XMAX
2, 3, 6, 7
*MATERIAL, NAME=MAT
*ELASTIC
1000., 0.
*SOLID SECTION, ELSET=EALL, MATERIAL=MAT
*TRANSFORM, NSET=XMAX, TYPE=R
0., 0., 0., 1., 0., 0.
*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

ccx 2.23 and master 537aa81, all with “Job finished”, exit 0 and no warning:

  • this deck (a at the origin): every free displacement in the model is NaN, also at the nodes without a transform;
  • 1., 0., 0., 2., 0., 0. (b on the X’ axis): the transformed nodes move 1.000000E-03 NaN NaN (local system);
  • 1., 0., 0., 0., 1., 0. (valid): 1.000000E-03 -7.255989E-20 -1.402057E-19;
  • *TRANSFORM, NSET=XMAX, TYPE=C with 0., 0., 0., 0., 0., 0. (a = b, no axis): again every free displacement is NaN.