test_forcad_utils Program

Uses

  • program~~test_forcad_utils~~UsesGraph program~test_forcad_utils test_forcad_utils forunittest forunittest program~test_forcad_utils->forunittest module~forcad_kinds forcad_kinds program~test_forcad_utils->module~forcad_kinds module~forcad_utils forcad_utils program~test_forcad_utils->module~forcad_utils module~forcad_utils->module~forcad_kinds

Unit test program for forcad_utils. using ForUnitTest: https://github.com/gha3mi/forunittest


Calls

program~~test_forcad_utils~~CallsGraph program~test_forcad_utils test_forcad_utils check check program~test_forcad_utils->check initialize initialize program~test_forcad_utils->initialize interface~basis_bspline_2der basis_bspline_2der program~test_forcad_utils->interface~basis_bspline_2der interface~basis_bspline_der basis_bspline_der program~test_forcad_utils->interface~basis_bspline_der interface~compute_multiplicity compute_multiplicity program~test_forcad_utils->interface~compute_multiplicity interface~dyad dyad program~test_forcad_utils->interface~dyad interface~elemconn_c0 elemConn_C0 program~test_forcad_utils->interface~elemconn_c0 interface~elemconn_cn elemConn_Cn program~test_forcad_utils->interface~elemconn_cn interface~gauss_leg gauss_leg program~test_forcad_utils->interface~gauss_leg interface~kron kron program~test_forcad_utils->interface~kron interface~ndgrid ndgrid program~test_forcad_utils->interface~ndgrid interface~unique unique program~test_forcad_utils->interface~unique proc~basis_bernstein basis_bernstein program~test_forcad_utils->proc~basis_bernstein proc~basis_bspline basis_bspline program~test_forcad_utils->proc~basis_bspline proc~compute_knot_vector compute_knot_vector program~test_forcad_utils->proc~compute_knot_vector proc~det det program~test_forcad_utils->proc~det proc~elevate_degree_a_5_9 elevate_degree_A_5_9 program~test_forcad_utils->proc~elevate_degree_a_5_9 proc~export_vtk_legacy export_vtk_legacy program~test_forcad_utils->proc~export_vtk_legacy proc~eye eye program~test_forcad_utils->proc~eye proc~findspan findspan program~test_forcad_utils->proc~findspan proc~hexahedron_xc hexahedron_Xc program~test_forcad_utils->proc~hexahedron_xc proc~insert_knot_a_5_1 insert_knot_A_5_1 program~test_forcad_utils->proc~insert_knot_a_5_1 proc~inv inv program~test_forcad_utils->proc~inv proc~kron_eye kron_eye program~test_forcad_utils->proc~kron_eye proc~linspace linspace program~test_forcad_utils->proc~linspace proc~repelem repelem program~test_forcad_utils->proc~repelem proc~rotation rotation program~test_forcad_utils->proc~rotation proc~solve solve program~test_forcad_utils->proc~solve proc~tetragon_xc tetragon_Xc program~test_forcad_utils->proc~tetragon_xc summary summary program~test_forcad_utils->summary proc~basis_bspline_2der_a basis_bspline_2der_A interface~basis_bspline_2der->proc~basis_bspline_2der_a proc~basis_bspline_2der_b basis_bspline_2der_B interface~basis_bspline_2der->proc~basis_bspline_2der_b proc~basis_bspline_2der_c basis_bspline_2der_C interface~basis_bspline_2der->proc~basis_bspline_2der_c proc~basis_bspline_der_a basis_bspline_der_A interface~basis_bspline_der->proc~basis_bspline_der_a proc~basis_bspline_der_b basis_bspline_der_B interface~basis_bspline_der->proc~basis_bspline_der_b proc~compute_multiplicity1 compute_multiplicity1 interface~compute_multiplicity->proc~compute_multiplicity1 proc~compute_multiplicity2 compute_multiplicity2 interface~compute_multiplicity->proc~compute_multiplicity2 proc~dyad_t1_t1 dyad_t1_t1 interface~dyad->proc~dyad_t1_t1 proc~cmp_elemconn_c0_l cmp_elemConn_C0_L interface~elemconn_c0->proc~cmp_elemconn_c0_l proc~cmp_elemconn_c0_s cmp_elemConn_C0_S interface~elemconn_c0->proc~cmp_elemconn_c0_s proc~cmp_elemconn_c0_v cmp_elemConn_C0_V interface~elemconn_c0->proc~cmp_elemconn_c0_v proc~cmp_elemconn_cn_l cmp_elemConn_Cn_L interface~elemconn_cn->proc~cmp_elemconn_cn_l proc~cmp_elemconn_cn_s cmp_elemConn_Cn_S interface~elemconn_cn->proc~cmp_elemconn_cn_s proc~cmp_elemconn_cn_v cmp_elemConn_Cn_V interface~elemconn_cn->proc~cmp_elemconn_cn_v proc~gauss_legendre_1d gauss_legendre_1D interface~gauss_leg->proc~gauss_legendre_1d proc~gauss_legendre_2d gauss_legendre_2D interface~gauss_leg->proc~gauss_legendre_2d proc~gauss_legendre_3d gauss_legendre_3D interface~gauss_leg->proc~gauss_legendre_3d proc~kron3 kron3 interface~kron->proc~kron3 proc~kron_t1_t1 kron_t1_t1 interface~kron->proc~kron_t1_t1 proc~kron_t1_t2 kron_t1_t2 interface~kron->proc~kron_t1_t2 proc~ndgrid2 ndgrid2 interface~ndgrid->proc~ndgrid2 proc~ndgrid3 ndgrid3 interface~ndgrid->proc~ndgrid3 proc~unique_integer unique_integer interface~unique->proc~unique_integer proc~unique_real unique_real interface~unique->proc~unique_real proc~compute_knot_vector->proc~repelem proc~elevate_degree_a_5_9->interface~compute_multiplicity proc~bincoeff bincoeff proc~elevate_degree_a_5_9->proc~bincoeff proc~inv->proc~det proc~inv->proc~eye proc~inv->proc~inv proc~inv->proc~solve cosd cosd proc~rotation->cosd sind sind proc~rotation->sind proc~factln factln proc~bincoeff->proc~factln proc~gauss_legendre gauss_legendre proc~gauss_legendre_1d->proc~gauss_legendre proc~gauss_legendre_2d->interface~kron proc~gauss_legendre_2d->interface~ndgrid proc~gauss_legendre_2d->proc~gauss_legendre proc~gauss_legendre_3d->interface~kron proc~gauss_legendre_3d->interface~ndgrid proc~gauss_legendre_3d->proc~gauss_legendre

Variables

Type Attributes Name Initial
real(kind=rk), allocatable :: A(:)
real(kind=rk), allocatable :: A2(:,:)
real(kind=rk) :: A2x2(2,2)
real(kind=rk) :: A4(2,2)
real(kind=rk), allocatable :: A_inv(:,:)
real(kind=rk) :: B4(4)
real(kind=rk) :: B_ref(4)
real(kind=rk) :: Bk(4,2)
real(kind=rk), allocatable :: K1(:)
real(kind=rk), allocatable :: K2(:)
real(kind=rk), allocatable :: K3(:)
real(kind=rk), allocatable :: M(:,:)
real(kind=rk), allocatable :: Pw(:,:)
real(kind=rk), allocatable :: Qw(:,:)
real(kind=rk), allocatable :: R(:,:)
real(kind=rk), allocatable :: R_expected(:,:)
real(kind=rk), allocatable :: Wksi(:)
real(kind=rk), allocatable :: X1(:)
real(kind=rk), allocatable :: X2(:)
real(kind=rk), allocatable :: X3(:)
real(kind=rk), allocatable :: Xksi(:,:)
real(kind=rk) :: Xt
real(kind=rk), allocatable :: Xt2(:,:)
real(kind=rk), allocatable :: Xt3(:,:)
integer, allocatable :: conn1D(:,:)
integer, allocatable :: conn2D(:,:)
integer, allocatable :: conn3D(:,:)
real(kind=rk) :: d2B(4)
real(kind=rk) :: d2B_ref(4)
real(kind=rk) :: dB(4)
real(kind=rk) :: dB_ref(4)
integer :: degree
integer :: k
real(kind=rk) :: knot(7)
real(kind=rk), allocatable :: knot_in(:)
real(kind=rk), allocatable :: knot_out(:)
integer :: nc
integer :: nq
real(kind=rk), allocatable :: out(:)
integer :: p
integer :: rr
integer :: s
real(kind=rk) :: u(2)
real(kind=rk), allocatable :: u2(:)
type(unit_tests) :: ut
real(kind=rk) :: v(2)
real(kind=rk), allocatable :: v2(:)
real(kind=rk), allocatable :: vec(:)
character(len=*), parameter :: vtk_file = "vtk/test_output.vtk"
real(kind=rk) :: w(4)
real(kind=rk), allocatable :: w2(:)

Source Code

program test_forcad_utils

   use forcad_kinds, only: rk
   use forcad_utils, only: basis_bspline, basis_bspline_der, basis_bspline_2der, &
      basis_bernstein, compute_multiplicity, ndgrid, dyad, kron, unique, findspan, &
      compute_knot_vector, insert_knot_A_5_1, remove_knots_A_5_8, elevate_degree_A_5_9, &
      hexahedron_Xc, tetragon_Xc, elemConn_C0, elemConn_Cn, rotation, det, inv, gauss_leg, &
      export_vtk_legacy, solve, repelem, linspace, eye, kron_eye
   use forunittest, only: unit_tests

   implicit none

   type(unit_tests) :: ut

   real(rk) :: Xt
   integer  :: nc, degree
   real(rk) :: knot(7)
   real(rk) :: B4(4), dB(4), d2B(4), A4(2,2)
   real(rk) :: B_ref(4), dB_ref(4), d2B_ref(4)
   real(rk) :: u(2), v(2), w(4)
   real(rk), allocatable :: u2(:), v2(:), w2(:)
   real(rk) :: A2x2(2,2), Bk(4,2)
   real(rk), allocatable :: A(:), vec(:), M(:,:)
   real(rk), allocatable :: X1(:), X2(:), X3(:)
   real(rk), allocatable :: Xt2(:,:), Xt3(:,:)
   real(rk), allocatable :: R(:,:), R_expected(:,:)
   real(rk), allocatable :: knot_in(:), knot_out(:), Pw(:,:), Qw(:,:)
   real(rk), allocatable :: Xksi(:,:), Wksi(:)
   real(rk), allocatable :: K3(:), K2(:), K1(:), out(:)
   integer, allocatable :: conn1D(:,:), conn2D(:,:), conn3D(:,:)
   integer :: nq, p, rr, s, k
   character(len=*), parameter :: vtk_file = "vtk/test_output.vtk"
   real(rk), allocatable :: A2(:,:), A_inv(:,:)

   ! Initialize unit tests
   call ut%initialize(n=57)

   ! ----------------------------
   ! Test: basis_bspline
   ! ----------------------------
   degree = 2
   nc     = 4
   knot = [0.0_rk, 0.0_rk, 0.0_rk, 0.5_rk, 1.0_rk, 1.0_rk, 1.0_rk]
   Xt     = 0.5_rk

   B4 = basis_bspline(Xt, knot, nc, degree)
   B_ref = [0.0_rk, 0.5_rk, 0.5_rk, 0.0_rk]

   call ut%test(1)%check( &
      name     = "basis_bspline", &
      res      = B4, &
      expected = B_ref, &
      msg      = "Partition of unity and shape check", &
      group    = "basis")

   call ut%test(2)%check( &
      name     = "basis_sum", &
      res      = sum(B4), &
      expected = 1.0_rk, &
      msg      = "Partition of unity", &
      group    = "basis")

   call basis_bspline_der(Xt, knot, nc, degree, dB, B4)
   dB_ref  = [0.0_rk, -2.0_rk, 2.0_rk, 0.0_rk]

   call ut%test(3)%check( &
      name     = "basis_bspline_der", &
      res      = dB, &
      expected = dB_ref, &
      msg      = "1st derivative shape check", &
      group    = "basis_der")

   call basis_bspline_der(Xt, knot, nc, degree, dB)

   call ut%test(4)%check( &
      name     = "basis_bspline_der_B", &
      res      = dB, &
      expected = dB_ref, &
      msg      = "1st derivative alternate", &
      group    = "basis_der")

   call basis_bspline_2der(Xt, knot, nc, degree, d2B, dB, B4)
   d2B_ref = [0.0_rk, 4.0_rk, -12.0_rk, 8.0_rk]

   call ut%test(5)%check( &
      name     = "basis_bspline_2der_A", &
      res      = d2B, &
      expected = d2B_ref, &
      msg      = "2nd derivative shape check A", &
      group    = "basis_2der")

   call basis_bspline_2der(Xt, knot, nc, degree, d2B, dB)

   call ut%test(6)%check( &
      name     = "basis_bspline_2der_B", &
      res      = d2B, &
      expected = d2B_ref, &
      msg      = "2nd derivative shape check B", &
      group    = "basis_2der")

   call basis_bspline_2der(Xt, knot, nc, degree, d2B)

   call ut%test(7)%check( &
      name     = "basis_bspline_2der_C", &
      res      = d2B, &
      expected = d2B_ref, &
      msg      = "2nd derivative shape check C", &
      group    = "basis_2der")

   Xt = -0.1_rk
   B4 = basis_bspline(Xt, knot, nc, degree)

   call ut%test(8)%check( &
      name     = "basis_out_of_bounds_low", &
      res      = sum(B4), &
      expected = 0.0_rk, &
      msg      = "Out-of-bounds left", &
      group    = "bounds")

   Xt = 1.1_rk
   B4 = basis_bspline(Xt, knot, nc, degree)

   call ut%test(9)%check( &
      name     = "basis_out_of_bounds_high", &
      res      = sum(B4), &
      expected = 0.0_rk, &
      msg      = "Out-of-bounds right", &
      group    = "bounds")

   ! ----------------------------
   ! Test: basis_bernstein
   ! ----------------------------
   call ut%test(10)%check( &
      name     = "basis_bernstein_sum", &
      res      = sum(basis_bernstein(0.3_rk, 3)), &
      expected = 1.0_rk, &
      msg      = "Bernstein basis partition of unity", &
      group    = "bernstein")

   ! ----------------------------
   ! Test: compute_multiplicity
   ! ----------------------------
   call ut%test(11)%check( &
      name     = "compute_multiplicity1", &
      res      = compute_multiplicity([0.0_rk, 0.0_rk, 1.0_rk, 1.0_rk]), &
      expected = [2, 2], &
      msg      = "Multiplicity vector", &
      group    = "multiplicity")

   call ut%test(12)%check( &
      name     = "compute_multiplicity2", &
      res      = compute_multiplicity([0.0_rk, 0.0_rk, 0.0_rk, 1.0_rk, 1.0_rk, 1.0_rk], 1.0_rk), &
      expected = 3, &
      msg      = "Multiplicity at value", &
      group    = "multiplicity")

   ! ----------------------------
   ! Test: ndgrid
   ! ----------------------------
   X1 = [1.0_rk, 2.0_rk]
   X2 = [10.0_rk, 20.0_rk]
   X3 = [100.0_rk]

   call ndgrid(X1, X2, Xt2)
   call ndgrid(X1, X2, X3, Xt3)

   call ut%test(13)%check( &
      name     = "ndgrid2_shape", &
      res      = shape(Xt2), &
      expected = [4,2], &
      msg      = "ndgrid2 shape", &
      group    = "ndgrid")

   call ut%test(14)%check( &
      name     = "ndgrid3_value", &
      res      = Xt3(:,3), &
      expected = [100.0_rk, 100.0_rk, 100.0_rk, 100.0_rk], &
      msg      = "ndgrid3 constant Z", &
      group    = "ndgrid")

   ! ----------------------------
   ! Test: dyad
   ! ----------------------------
   A = [1.0_rk, 2.0_rk]
   vec = [3.0_rk, 4.0_rk, 5.0_rk]
   M = dyad(A, vec)

   call ut%test(15)%check( &
      name     = "dyad_check", &
      res      = M, &
      expected = reshape([3.0_rk, 6.0_rk, 4.0_rk, 8.0_rk, 5.0_rk, 10.0_rk], [2,3]), &
      msg      = "Outer product a .dyad. b", &
      group    = "dyad")

   ! ----------------------------
   ! Test: kron
   ! ----------------------------
   u = [1.0_rk, 2.0_rk]
   v = [3.0_rk, 4.0_rk]
   w = kron(u, v)

   call ut%test(16)%check( &
      name     = "kron_vector", &
      res      = w, &
      expected = [3.0_rk, 4.0_rk, 6.0_rk, 8.0_rk], &
      msg      = "u .kron. v", &
      group    = "kron")

   A2x2 = reshape([1.0_rk, 2.0_rk, 3.0_rk, 4.0_rk], [2,2])
   Bk   = kron(u, A2x2)

   call ut%test(17)%check( &
      name     = "kron_matrix", &
      res      = Bk, &
      expected = reshape([1.0_rk, 2.0_rk, 2.0_rk, 4.0_rk, 3.0_rk, 4.0_rk, 6.0_rk, 8.0_rk], [4,2]), &
      msg      = "u .kron. A", &
      group    = "kron")

   ! ----------------------------
   ! Test: unique
   ! ----------------------------
   call ut%test(18)%check( &
      name     = "unique_integer", &
      res      = unique([1,2,2,3,1,4]), &
      expected = [1,2,3,4], &
      msg      = "Unique integers", &
      group    = "unique")

   call ut%test(19)%check( &
      name     = "unique_real", &
      res      = unique([1.0_rk, 1.0_rk + 1e-16_rk, 2.0_rk, 1.0_rk, 3.0_rk]), &
      expected = [1.0_rk, 2.0_rk, 3.0_rk], &
      msg      = "Unique real with tolerance", &
      group    = "unique")

   ! ----------------------------
   ! Test: findspan
   ! ----------------------------
   call ut%test(20)%check( &
      name     = "findspan_middle", &
      res      = findspan(4, 2, 0.5_rk, knot), &
      expected = 3, &
      msg      = "Find span index at Xt=0.5", &
      group    = "findspan")

   ! ----------------------------
   ! Test: compute_knot_vector
   ! ----------------------------
   call ut%test(21)%check( &
      name     = "compute_knot_vector", &
      res      = compute_knot_vector([0.0_rk, 1.0_rk, 2.0_rk], 2, [-1, 1, -1]), &
      expected = [0.0_rk, 0.0_rk, 0.0_rk, 1.0_rk, 2.0_rk, 2.0_rk, 2.0_rk], &
      msg      = "Knot vector construction", &
      group    = "knot")

   ! ----------------------------
   ! Test: repelem
   ! ----------------------------
   call ut%test(22)%check( &
      name     = "repelem", &
      res      = repelem([1.0_rk, 2.0_rk, 3.0_rk], [2, 1, 3]), &
      expected = [1.0_rk, 1.0_rk, 2.0_rk, 3.0_rk, 3.0_rk, 3.0_rk], &
      msg      = "Repeat vector elements", &
      group    = "repelem")

   ! ----------------------------
   ! Test: hexahedron_Xc
   ! ----------------------------
   call ut%test(23)%check( &
      name     = "hexahedron_Xc_shape", &
      res      = shape(hexahedron_Xc([1.0_rk, 1.0_rk, 1.0_rk], [2,2,2])), &
      expected = [8,3], &
      msg      = "3D grid shape", &
      group    = "geometry")

   ! ----------------------------
   ! Test: tetragon_Xc
   ! ----------------------------
   call ut%test(24)%check( &
      name     = "tetragon_Xc_shape", &
      res      = shape(tetragon_Xc([1.0_rk, 1.0_rk], [2,2])), &
      expected = [4,3], &
      msg      = "2D grid shape", &
      group    = "geometry")

   ! ----------------------------
   ! Test: rotation
   ! ----------------------------
   call ut%test(25)%check( &
      name     = "rotation_identity", &
      res      = rotation(0.0_rk, 0.0_rk, 0.0_rk), &
      expected = reshape([1.0_rk,0.0_rk,0.0_rk, 0.0_rk,1.0_rk,0.0_rk, 0.0_rk,0.0_rk,1.0_rk], [3,3]), &
      msg      = "Rotation(0,0,0) = I", &
      group    = "rotation")

   ! ----------------------------
   ! Test: det
   ! ----------------------------
   call ut%test(26)%check( &
      name     = "det_2x2", &
      res      = det(reshape([1.0_rk, 2.0_rk, 3.0_rk, 4.0_rk], [2,2])), &
      expected = -2.0_rk, &
      msg      = "Determinant 2x2", &
      group    = "matrix")

   ! ----------------------------
   ! Test: inv
   ! ----------------------------
   call ut%test(27)%check( &
      name     = "inv_2x2", &
      res      = inv(reshape([4.0_rk, 7.0_rk, 2.0_rk, 6.0_rk], [2,2])), &
      expected = reshape([0.6_rk, -0.7_rk, -0.2_rk, 0.4_rk], [2,2]), &
      msg      = "Inverse of 2x2", &
      group    = "matrix")

   ! ----------------------------
   ! Test: solve
   ! ----------------------------
   A4 = reshape([4.0_rk, 1.0_rk, 1.0_rk, 3.0_rk], [2,2])
   R_expected = reshape([1.0_rk/11.0_rk, 7.0_rk/11.0_rk], [2,1])
   R = solve(A4, reshape([1.0_rk, 2.0_rk], [2,1]))

   call ut%test(28)%check( &
      name     = "solve_linear_system", &
      res      = R, &
      expected = R_expected, &
      msg      = "Solving A.X = B", &
      group    = "matrix")

   ! ----------------------------
   ! Test: insert_knot_A_5_1
   ! ----------------------------
   p = 2
   rr = 1
   s  = 1
   k  = 3
   allocate(knot_in(0:6))
   knot_in = [0.0_rk, 0.0_rk, 0.0_rk, 0.5_rk, 1.0_rk, 1.0_rk, 1.0_rk]
   allocate(Pw(0:2,1:2))
   Pw(0,:) = [0.0_rk, 0.0_rk]
   Pw(1,:) = [0.5_rk, 0.5_rk]
   Pw(2,:) = [1.0_rk, 1.0_rk]

   call insert_knot_A_5_1(p, knot_in, Pw, 0.5_rk, k, s, rr, nq, knot_out, Qw)

   call ut%test(29)%check( &
      name     = "insert_knot_A_5_1_nq", &
      res      = nq, &
      expected = 3, &
      msg      = "Inserted knot, new number of control points", &
      group    = "insert_knot")

   ! ----------------------------
   ! Test: elevate_degree_A_5_9
   ! ----------------------------
   deallocate(knot_in, knot_out, Pw, Qw)
   knot_in = [0.0_rk, 0.0_rk, 1.0_rk, 1.0_rk]
   allocate(Pw(1:2,1:2))
   Pw(1,:) = [0.0_rk, 0.0_rk]
   Pw(2,:) = [0.5_rk, 0.5_rk]
   call elevate_degree_A_5_9(t=1, knot=knot_in, degree=1, Xcw=Pw, nc_new=nc, knot_new=knot_out, Xcw_new=Qw)

   call ut%test(30)%check( &
      name     = "elevate_degree_nc", &
      res      = nc, &
      expected = 3, &
      msg      = "New number of control points after elevation", &
      group    = "elevate_degree")

   ! ----------------------------
   ! Test: gauss_legendre_1D
   ! ----------------------------
   call gauss_leg([0.0_rk, 1.0_rk], 2, Xksi=vec, Wksi=A)

   call ut%test(31)%check( &
      name     = "gauss_legendre_1D_pts", &
      res      = size(vec), &
      expected = 3, &
      msg      = "Number of Gauss points (1D)", &
      group    = "quadrature")

   ! ----------------------------
   ! Test: gauss_legendre_2D
   ! ----------------------------
   call gauss_leg([0.0_rk, 1.0_rk], [0.0_rk, 1.0_rk], [2, 2], Xksi=Xksi, Wksi=Wksi)

   call ut%test(32)%check( &
      name     = "gauss_legendre_2D_shape", &
      res      = shape(Xksi), &
      expected = [9,2], &
      msg      = "Gauss points shape (2D)", &
      group    = "quadrature")

   ! ----------------------------
   ! Test: gauss_legendre_3D
   ! ----------------------------
   call gauss_leg([0.0_rk, 1.0_rk], [0.0_rk, 1.0_rk], [0.0_rk, 1.0_rk], [1, 1, 1], Xksi=Xksi, Wksi=Wksi)

   call ut%test(33)%check( &
      name     = "gauss_legendre_3D_size", &
      res      = size(Xksi,1), &
      expected = 8, &
      msg      = "Number of Gauss points (3D)", &
      group    = "quadrature")

   ! ----------------------------
   ! Test: export_vtk_legacy
   ! ----------------------------
   call export_vtk_legacy(filename=vtk_file, points=reshape([0.0_rk, 0.0_rk, 0.0_rk, 1.0_rk, 0.0_rk, 0.0_rk], [2,3]), &
      elemConn=reshape([1,2], [1,2]), vtkCellType=3, encoding="binary")

   call ut%test(34)%check( &
      name     = "export_vtk_legacy", &
      res      = .true., &
      expected = .true., &
      msg      = "Export to VTK (not crashing)", &
      group    = "export")

   call export_vtk_legacy(filename=vtk_file, points=reshape([0.0_rk, 0.0_rk, 0.0_rk, 1.0_rk, 0.0_rk, 0.0_rk], [2,3]), &
      elemConn=reshape([1,2], [1,2]), vtkCellType=3, encoding="ascii")

   call ut%test(35)%check( &
      name     = "export_vtk_legacy", &
      res      = .true., &
      expected = .true., &
      msg      = "Export to VTK (not crashing)", &
      group    = "export")

   ! ----------------------------
   ! Test: linspace
   ! ----------------------------
   call ut%test(36)%check( &
      name     = "linspace_uniform", &
      res      = linspace(0.0_rk, 1.0_rk, 5), &
      expected = [0.0_rk, 0.25_rk, 0.5_rk, 0.75_rk, 1.0_rk], &
      msg      = "Uniform linspace from 0 to 1", &
      group    = "linspace")

   ! ----------------------------
   ! Test: kron3
   ! ----------------------------
   K1 = [1.0_rk, 2.0_rk]
   K2 = [3.0_rk]
   K3 = [4.0_rk, 5.0_rk]
   out = kron(K1, K2, K3)

   call ut%test(37)%check( &
      name     = "kron3_product", &
      res      = out, &
      expected = [12.0_rk, 15.0_rk, 24.0_rk, 30.0_rk], &
      msg      = "kron3 result values", &
      group    = "kron")

   ! ----------------------------
   ! Test: elemConn_C0
   ! ----------------------------
   conn1D = elemConn_C0(5, 2)

   call ut%test(38)%check( &
      name     = "elemConn_C0_L", &
      res      = conn1D, &
      expected = reshape([1,3,2,4,3,5], [2,3]), &
      msg      = "Linear C0 connectivity", &
      group    = "connectivity")

   ! ----------------------------
   ! Test: elemConn_Cn (L)
   ! ----------------------------
   call elemConn_Cn(5, 2, [0.0_rk, 0.5_rk, 1.0_rk], [3,1,3], conn1D)

   call ut%test(39)%check( &
      name     = "elemConn_Cn_L", &
      res      = conn1D, &
      expected = reshape([1,2,2,3,3,4], [2,3]), &
      msg      = "Linear Cn connectivity", &
      group    = "connectivity")

   ! ----------------------------
   ! Test: elemConn_C0 (2D)
   ! ----------------------------
   conn2D = elemConn_C0(5, 5, 2, 2)

   call ut%test(40)%check( &
      name     = "elemConn_C0_S", &
      res      = shape(conn2D), &
      expected = [4,9], &
      msg      = "Surface C0 connectivity shape", &
      group    = "connectivity")

   ! ----------------------------
   ! Test: elemConn_Cn (2D)
   ! ----------------------------
   call elemConn_Cn(5, 5, 2, 2, [0.0_rk, 0.5_rk, 1.0_rk], [0.0_rk, 0.5_rk, 1.0_rk], &
      [3,1,3], [3,1,3], conn2D)

   call ut%test(41)%check( &
      name     = "elemConn_Cn_S", &
      res      = shape(conn2D), &
      expected = [4,9], &
      msg      = "Surface Cn connectivity shape", &
      group    = "connectivity")

   ! ----------------------------
   ! Test: elemConn_C0 (3D)
   ! ----------------------------
   conn3D = elemConn_C0(5, 5, 5, 2, 2, 2)

   call ut%test(42)%check( &
      name     = "elemConn_C0_V", &
      res      = shape(conn3D), &
      expected = [8,27], &
      msg      = "Volume C0 connectivity shape", &
      group    = "connectivity")

   ! ----------------------------
   ! Test: elemConn_Cn (3D)
   ! ----------------------------
   call elemConn_Cn(5, 5, 5, 2, 2, 2, &
      [0.0_rk, 0.5_rk, 1.0_rk], &
      [0.0_rk, 0.5_rk, 1.0_rk], &
      [0.0_rk, 0.5_rk, 1.0_rk], &
      [3,1,3], [3,1,3], [3,1,3], conn3D)

   call ut%test(43)%check( &
      name     = "elemConn_Cn_V", &
      res      = shape(conn3D), &
      expected = [8,27], &
      msg      = "Volume Cn connectivity shape", &
      group    = "connectivity")

   ! ----------------------------
   ! Test: inv (3x3 matrix)
   ! ----------------------------
   allocate(A2(3,3))
   A2 = reshape([1.0_rk, 2.0_rk, 3.0_rk, &
      0.0_rk, 1.0_rk, 4.0_rk, &
      5.0_rk, 6.0_rk, 0.0_rk], [3,3])
   A_inv = inv(A2)

   call ut%test(44)%check( &
      name     = "inv_3x3", &
      res      = matmul(A2, A_inv), &
      expected = eye(3), &
      msg      = "A . inv(A) = I for 3x3", &
      group    = "matrix")

   ! ----------------------------
   ! Test: inv (rectangular 3x2 matrix)
   ! ----------------------------
   deallocate(A2, A_inv)
   allocate(A2(3,2))
   A2 = reshape([1.0_rk, 2.0_rk, 3.0_rk, 4.0_rk, 5.0_rk, 6.0_rk], [3,2])
   A_inv = inv(A2)

   call ut%test(45)%check( &
      name     = "inv_rectangular_3x2", &
      res      = matmul(A_inv, A2), &
      expected = eye(2), &
      msg      = "inv(A) . A = I for 3x2", &
      group    = "matrix")

   ! ----------------------------
   ! Test: inv (rectangular 2x3 matrix)
   ! ----------------------------
   deallocate(A2, A_inv)
   allocate(A2(2,3))
   A2 = reshape([1.0_rk, 4.0_rk, 2.0_rk, 5.0_rk, 3.0_rk, 6.0_rk], [2,3])
   A_inv = inv(A2)

   call ut%test(46)%check( &
      name     = "inv_rectangular_2x3", &
      res      = matmul(A2, A_inv), &
      expected = eye(2), &
      msg      = "A . inv(A) = I for 2x3", &
      group    = "matrix")

   ! ----------------------------
   ! Test: inv (identity matrix)
   ! ----------------------------
   deallocate(A2, A_inv)
   allocate(A2(4,4))
   A2 = eye(4)
   A_inv = inv(A2)

   call ut%test(47)%check( &
      name     = "inv_identity", &
      res      = A_inv, &
      expected = A2, &
      msg      = "inv(I) = I", &
      group    = "matrix")

   call ut%test(48)%check( &
      name     = "inv_rectangular_2x3_proj", &
      res      = matmul(A_inv, A2), &
      expected = transpose(matmul(A_inv, A2)), &
      msg      = "inv(A) . A is symmetric projection (2x3)", &
      group    = "matrix")

   ! ----------------------------
   ! Test: kron_eye
   ! ----------------------------
   if (allocated(A2)) deallocate(A2)
   allocate(A2(2,2))
   A2 = reshape([1.0_rk, 2.0_rk, 3.0_rk, 4.0_rk],[2,2])
   R = kron_eye(A2, 2)

   call ut%test(49)%check( &
      name     = "kron_eye_block_diag", &
      res      = R, &
      expected = reshape([ &
      1.0_rk,0.0_rk,2.0_rk,0.0_rk, &
      0.0_rk,1.0_rk,0.0_rk,2.0_rk, &
      3.0_rk,0.0_rk,4.0_rk,0.0_rk, &
      0.0_rk,3.0_rk,0.0_rk,4.0_rk], [4,4]), &
      msg      = "Kronecker with identity blocks", &
      group    = "kron")

   ! ----------------------------
   ! Test: kron_t1_t1 (vector .kron. vector)
   ! ----------------------------

   ! different sizes + negatives/zeros
   u2 = [-1.0_rk, 0.0_rk, 2.0_rk]
   v2 = [ 5.0_rk, -3.0_rk]
   w2 = kron(u2, v2)

   call ut%test(50)%check( &
      name     = "kron_t1_t1_values", &
      res      = w2, &
      expected = [-5.0_rk, 3.0_rk, 0.0_rk, 0.0_rk, 10.0_rk, -6.0_rk], &
      msg      = "kron(u,v) concatenates u(i)*v blocks", &
      group    = "kron")

   ! length matches size(u)*size(v)
   call ut%test(51)%check( &
      name     = "kron_t1_t1_size", &
      res      = size(w2), &
      expected = size(u2)*size(v2), &
      msg      = "Length of kron(u,v)", &
      group    = "kron")

   ! non-commutativity (order matters)
   call ut%test(52)%check( &
      name     = "kron_t1_t1_noncommutative", &
      res      = all(w2 == kron(v2, u2)), &
      expected = .false., &
      msg      = "kron(u,v) /= kron(v,u) for vectors", &
      group    = "kron")

   ! ----------------------------
   ! Test: kron3(u, v, w)
   ! ----------------------------

   ! values with zeros/negatives and |v|=2, |w|=1
   u2 = [-1.0_rk, 2.0_rk]
   v2 = [0.0_rk, 3.0_rk]
   K3 = [-2.0_rk]
   out = kron(u2, v2, K3)  ! kron3
   call ut%test(53)%check( &
      name     = "kron3_values_mixed", &
      res      = out, &
      expected = [0.0_rk, 6.0_rk, 0.0_rk, -12.0_rk], &
      msg      = "ordering: (u1*v1*w1, u1*v2*w1, u2*v1*w1, u2*v2*w1)", &
      group    = "kron")

   ! size = |u|*|v|*|w|
   call ut%test(54)%check( &
      name     = "kron3_size", &
      res      = size(out), &
      expected = size(u2) * size(v2) * size(K3), &
      msg      = "length of kron3 output", &
      group    = "kron")

   ! associativity check: kron3(u,v,w) == kron(u, kron(v,w))
   u2 = [1.0_rk, 2.0_rk]
   v2 = [3.0_rk, 4.0_rk]
   K3 = [5.0_rk]
   out = kron(u2, v2, K3)
   w2  = kron(v2, K3)
   A   = kron(u2, w2)
   call ut%test(55)%check( &
      name     = "kron3_associativity_vec", &
      res      = all(abs(out - A) <= epsilon(0.0_rk)), &
      expected = .true., &
      msg      = "kron3 equals kron(u, kron(v,w))", &
      group    = "kron")

   ! another ordering/values case (|u|=2, |v|=1, |w|=3)
   u2 = [2.0_rk, -1.0_rk]
   v2 = [7.0_rk]
   K3 = [1.0_rk, 0.0_rk, -2.0_rk]
   out = kron(u2, v2, K3)
   call ut%test(56)%check( &
      name     = "kron3_values_ordering", &
      res      = out, &
      expected = [14.0_rk, 0.0_rk, -28.0_rk, -7.0_rk, 0.0_rk, 14.0_rk], &
      msg      = "iterate i (u), then j (v), then k (w)", &
      group    = "kron")

   ! non-commutativity across arguments
   u2 = [1.0_rk, 2.0_rk]
   v2 = [3.0_rk, 4.0_rk]
   K3 = [5.0_rk]     ! length-1 w is fine; non-commutativity comes from u vs v
   call ut%test(57)%check( &
      name     = "kron3_noncommutative", &
      res      = all(kron(u2, v2, K3) == kron(v2, u2, K3)), &
      expected = .false., &
      msg      = "kron3(u,v,w) /= kron3(v,u,w)", &
      group    = "kron")


   ! summary of tests
   call ut%summary( &
      required_score = 100.0, &
      verbose        = 3, &
      stop_fail      = .false.)

end program test_forcad_utils