public static double[] fem3d_evaluate(int fem_node_num, double[] fem_node_xyz,
                                          int fem_element_order, int fem_element_num, int[] fem_element_node,
                                          int[] fem_element_neighbor, int fem_value_dim, double[] fem_value,
                                          int sample_node_num, double[] sample_node_xyz)

    //****************************************************************************80
    //
    //  Purpose:
    //
    //    FEM3D_EVALUATE samples an FEM function on a T4 tet mesh.
    //
    //  Discussion:
    //
    //    Note that the sample values returned are true values of the underlying
    //    finite element function.  They are NOT produced by constructing some
    //    other function that interpolates the data at the finite element nodes
    //    (something which MATLAB's griddata function can easily do.)  Instead,
    //    each sampling node is located within one of the associated finite
    //    element tetrahedrons, and the finite element function is developed and
    //    evaluated there.
    //
    //  Licensing:
    //
    //    This code is distributed under the GNU LGPL license.
    //
    //  Modified:
    //
    //    08 August 2009
    //
    //  Author:
    //
    //    John Burkardt
    //
    //  Parameters:
    //
    //    Input, int FEM_NODE_NUM, the number of nodes.
    //
    //    Input, double FEM_NODE_XYZ[3*FEM_NODE_NUM], the coordinates
    //    of the nodes.
    //
    //    Input, int FEM_ELEMENT_ORDER, the order of the elements,
    //    which should be 4.
    //
    //    Input, int FEM_ELEMENT_NUM, the number of elements.
    //
    //    Input, int FEM_ELEMENT_NODE[FEM_ELEMENT_ORDER*FEM_ELEMENT_NUM], the
    //    nodes that make up each element.
    //
    //    Input, int FEM_ELEMENT_NEIGHBOR[4*FEM_ELEMENT_NUM], the
    //    index of the neighboring element on each side, or -1 if no neighbor there.
    //
    //    Input, int FEM_VALUE_DIM, the "dimension" of the values.
    //
    //    Input, double FEM_VALUE[FEM_VALUE_DIM*FEM_NODE_NUM], the
    //    finite element coefficient values at each node.
    //
    //    Input, int SAMPLE_NODE_NUM, the number of sample nodes.
    //
    //    Input, double SAMPLE_NODE_XYZ[3*SAMPLE_NODE_NUM], the sample nodes.
    //
    //    Output, double) FEM3D_EVALUATE[FEM_VALUE_DIM*SAMPLE_NODE_NUM],
    //    the sampled values.
    //
    {
        int j;

        double[] p_xyz = new double[3];

        double[] b            = new double[fem_element_order];
        double[] sample_value = new double[fem_value_dim * sample_node_num];
        int[]    t_node       = new int[fem_element_order];
        double[] t_xyz        = new double[3 * fem_element_order];
        //
        //  For each sample point: find the element T that contains it,
        //  and evaluate the finite element function there.
        //
        for (j = 0; j < sample_node_num; j++)
        {
            p_xyz[0] = sample_node_xyz[0 + j * 3];
            p_xyz[1] = sample_node_xyz[1 + j * 3];
            p_xyz[2] = sample_node_xyz[2 + j * 3];
            //
            //  Find the element T that contains the point.
            //
            int step_num = 0;
            int t        = TetMesh.tet_mesh_search_naive(fem_node_num, fem_node_xyz,
                                                         fem_element_order, fem_element_num, fem_element_node,
                                                         p_xyz, ref step_num);
            //
            //  Evaluate the finite element basis functions at the point in T.
            //
            int i;
            for (i = 0; i < fem_element_order; i++)
            {
                t_node[i] = fem_element_node[i + t * fem_element_order];
            }

            for (i = 0; i < fem_element_order; i++)
            {
                t_xyz[0 + i * 3] = fem_node_xyz[0 + t_node[i] * 3];
                t_xyz[1 + i * 3] = fem_node_xyz[1 + t_node[i] * 3];
                t_xyz[2 + i * 3] = fem_node_xyz[2 + t_node[i] * 3];
            }

            Basis_mn.basis_mn_tet4(t_xyz, 1, p_xyz, ref b);
            //
            //  Multiply by the finite element values to get the sample values.
            //
            for (i = 0; i < fem_value_dim; i++)
            {
                sample_value[i + j * fem_value_dim] = 0.0;
                int k;
                for (k = 0; k < fem_element_order; k++)
                {
                    sample_value[i + j * fem_value_dim] += fem_value[i + t_node[k] * fem_value_dim] * b[k];
                }
            }
        }

        return(sample_value);
    }
    public static double[] fem3d_transfer(int sample_node_num, int sample_element_order,
                                          int sample_element_num, int sample_value_dim, int sample_value_num,
                                          double[] sample_node_xyz, int[] sample_element_node,
                                          int[] sample_element_neighbor, double[] sample_value, int fem_node_num,
                                          int fem_element_order, int fem_element_num, int fem_value_dim,
                                          int fem_value_num, double[] fem_node_xyz, int[] fem_element_node)

    //****************************************************************************80
    //
    //  Purpose:
    //
    //    FEM3D_TRANSFER "transfers" from one finite element mesh to another.
    //
    //  BAD THINGS:
    //
    //    1) the linear system A*X=B is defined with A being a full storage matrix.
    //    2) the quadrature rule used is low order.
    //    3) the elements are assumed to be linear.
    //
    //  Discussion:
    //
    //    We are also given a set of "sample" finite element function defined
    //    by SAMPLE_NODE_XYZ, SAMPLE_ELEMENT, and SAMPLE_VALUE.
    //
    //    We are given a second finite element mesh, FEM_NODE_XYZ and
    //    FEM_ELEMENT_NODE.
    //
    //    Our aim is to "project" the sample data values into the finite element
    //    space, that is, to come up with a finite element function FEM_VALUE which
    //    well approximates the sample data.
    //
    //    Now let W(x,y,z) represent a function interpolating the sample data, and
    //    let Vijk(x,y,z) represent the finite element basis function associated with
    //    node IJK.
    //
    //    Then we seek the coefficient vector U corresponding to a finite element
    //    function U(x,y,z) of the form:
    //
    //      U(x,y,z) = sum ( 1 <= IJK <= N ) Uijk * Vijk(x,y,z)
    //
    //    To determine the coefficent vector entries U, we form a set of
    //    projection equations.  For node IJK at grid point (I,J,K), the associated
    //    basis function Vk(x,y,z) is used to pose the equation:
    //
    //      Integral U(x,y,z) Vijk(x,y,z) dx dy dz
    //        = Integral W(x,y,z) Vijk(x,y,z) dx dy dz
    //
    //    The left hand side is the usual stiffness matrix times the desired
    //    coefficient vector U.  To complete the system, we simply need to
    //    determine the right hand side, that is, the integral of the data function
    //    W against the basis function Vk.
    //
    //  Licensing:
    //
    //    This code is distributed under the GNU LGPL license.
    //
    //  Modified:
    //
    //    27 August 2009
    //
    //  Author:
    //
    //    John Burkardt
    //
    //  Parameters:
    //
    //    Input, int SAMPLE_NODE_NUM, the number of nodes.
    //
    //    Input, int SAMPLE_ELEMENT_ORDER, the element order.
    //
    //    Input, int SAMPLE_ELEMENT_NUM, the number of elements.
    //
    //    Input, int SAMPLE_VALUE_DIM, the value dimension.
    //
    //    Input, int SAMPLE_VALUE_NUM, the number of values.
    //
    //    Input, double SAMPLE_NODE_XYZ[3*SAMPLE_NODE_NUM], the nodes.
    //
    //    Input, int SAMPLE_ELEMENT_NODE[SAMPLE_ELEMENT_ORDER*SAMPLE_ELEMENT_NUM],
    //    the nodes that make up each element.
    //
    //    Input, int SAMPLE_ELEMENT_NEIGHBOR[3*SAMPLE_ELEMENT_NUM],
    //    the neighbor triangles.
    //
    //    Input, double SAMPLE_VALUE[SAMPLE_VALUE_DIM*SAMPLE_NODE_NUM],
    //    the values.
    //
    //    Input, int FEM_NODE_NUM, the number of nodes.
    //
    //    Input, int FEM_ELEMENT_ORDER, the element order.
    //
    //    Input, int FEM_ELEMENT_NUM, the number of elements.
    //
    //    Input, int FEM_VALUE_DIM, the value dimension.
    //
    //    Input, int FEM_VALUE_NUM, the number of values.
    //
    //    Input, double FEM_NODE_XYZ[3*FEM_NODE_NUM], the nodes.
    //
    //    Input, int FEM_ELEMENT_NODE[FEM_ELEMENT_ORDER*FEM_ELEMENT_NUM],
    //    the nodes that make up each element.
    //
    //    Output, double FEM3D_TRANSFER[FEM_VALUE_DIM*FEM_VALUE_NUM],
    //    the values.
    //
    {
        int       element;
        int       i;
        int       j;
        const int project_node_num = 1;

        double[]  project_node_xyz = new double[3 * 1];
        const int quad_num         = 4;

        //
        //  Assemble the coefficient matrix A and the right-hand side B.
        //
        double[] b = typeMethods.r8mat_zero_new(fem_node_num, fem_value_dim);
        double[] a = typeMethods.r8mat_zero_new(fem_node_num, fem_node_num);

        double[] phi        = new double[4];
        double[] ref_weight = new double[quad_num];
        double[] ref_quad   = new double[4 * quad_num];
        double[] tet_quad   = new double[3 * quad_num];
        double[] tet_xyz    = new double[3 * 4];

        // Upstream doesn't initialize the arrays and seems to rely on weird default values.
        // Mimic here.
        for (int shim = 0; shim < phi.Length; shim++)
        {
            phi[shim] = -6.2774385622041925e+66;
        }
        for (int shim = 0; shim < ref_weight.Length; shim++)
        {
            ref_weight[shim] = -6.2774385622041925e+66;
        }
        for (int shim = 0; shim < ref_quad.Length; shim++)
        {
            ref_quad[shim] = -6.2774385622041925e+66;
        }
        for (int shim = 0; shim < tet_quad.Length; shim++)
        {
            tet_quad[shim] = -6.2774385622041925e+66;
        }
        for (int shim = 0; shim < tet_xyz.Length; shim++)
        {
            tet_xyz[shim] = -6.2774385622041925e+66;
        }

        for (element = 0; element < fem_element_num; element++)
        {
            for (j = 0; j < 4; j++)
            {
                for (i = 0; i < 3; i++)
                {
                    int j2 = fem_element_node[j + element * 4];
                    tet_xyz[i + j * 3] = fem_node_xyz[i + j2 * 3];
                }
            }

            double volume = Tetrahedron.tetrahedron_volume(tet_xyz);

            for (j = 0; j < quad_num; j++)
            {
                for (i = 0; i < 3; i++)
                {
                    tet_quad[i + j * 3] = 0.0;
                    int k;
                    for (k = 0; k < 4; k++)
                    {
                        double tq = tet_quad[i + j * 3];
                        double tx = tet_xyz[i + k * 3];
                        double rq = ref_quad[k + j * 4];

                        tet_quad[i + j * 3] = tq + tx * rq;
                    }
                }
            }

            //
            //  Consider each quadrature point.
            //  Here, we use the midside nodes as quadrature points.
            //
            int quad;
            for (quad = 0; quad < quad_num; quad++)
            {
                for (i = 0; i < 3; i++)
                {
                    project_node_xyz[i + 0 * 3] = tet_quad[i + quad * 3];
                }

                Basis_mn.basis_mn_tet4(tet_xyz, 1, project_node_xyz, ref phi);

                for (i = 0; i < 4; i++)
                {
                    int ni = fem_element_node[i + element * fem_element_order];
                    //
                    //  The projection takes place here.  The finite element code needs the value
                    //  of the sample function at the point (XQ,YQ).  The call to PROJECTION
                    //  locates (XQ,YQ) in the triangulated mesh of sample data, and returns a
                    //  value produced by piecewise linear interpolation.
                    //
                    double[] project_value = FEM_3D_Projection.projection(sample_node_num, sample_node_xyz,
                                                                          sample_element_order, sample_element_num, sample_element_node,
                                                                          sample_element_neighbor, sample_value_dim, sample_value,
                                                                          project_node_num, project_node_xyz);

                    for (j = 0; j < fem_value_dim; j++)
                    {
                        b[ni + j * fem_node_num] += volume * ref_weight[quad] *
                                                    (project_value[j + 0 * fem_value_dim] * phi[i]);
                    }

                    //
                    //  Consider each basis function in the element.
                    //
                    for (j = 0; j < 4; j++)
                    {
                        int nj = fem_element_node[j + element * fem_element_order];

                        a[ni + nj * fem_node_num] += volume * ref_weight[quad] * (phi[i] * phi[j]);
                    }
                }
            }
        }

        //
        //  SOLVE the linear system A * X = B.
        //
        double[] x = Solve.r8ge_fss_new(fem_node_num, a, fem_value_dim, b);
        //
        //  Copy solution.
        //
        double[] fem_value = new double[fem_value_dim * fem_value_num];

        for (j = 0; j < fem_value_num; j++)
        {
            for (i = 0; i < fem_value_dim; i++)
            {
                fem_value[(i + j * fem_value_dim + fem_value.Length) % fem_value.Length] = x[(j + i * fem_value_num + x.Length) % x.Length];
            }
        }

        return(fem_value);
    }
    public static double[] projection(int fem_node_num, double[] fem_node_xyz,
                                      int fem_element_order, int fem_element_num, int[] fem_element_node,
                                      int[] fem_element_neighbor, int fem_value_dim, double[] fem_value,
                                      int sample_node_num, double[] sample_node_xyz)

    //****************************************************************************80
    //
    //  Purpose:
    //
    //    PROJECTION evaluates an FEM function on a T3 or T6 triangulation.
    //
    //  Licensing:
    //
    //    This code is distributed under the GNU LGPL license.
    //
    //  Modified:
    //
    //    27 August 2009
    //
    //  Author:
    //
    //    John Burkardt
    //
    //  Parameters:
    //
    //    Input, int FEM_NODE_NUM, the number of nodes.
    //
    //    Input, double FEM_NODE_XYZ[3*FEM_NODE_NUM], the coordinates
    //    of the nodes.
    //
    //    Input, int FEM_ELEMENT_ORDER, the order of the elements.
    //
    //    Input, int FEM_ELEMENT_NUM, the number of elements.
    //
    //    Input, int FEM_ELEMENT_NODE(FEM_ELEMENT_ORDER,FEM_ELEMENT_NUM), the
    //    nodes that make up each element.
    //
    //    Input, int FEM_ELEMENT_NEIGHBOR[4*FEM_ELEMENT_NUM], the
    //    index of the neighboring element on each side, or -1 if no neighbor there.
    //
    //    Input, int FEM_VALUE_DIM, the "dimension" of the values.
    //
    //    Input, double FEM_VALUE[FEM_VALUE_DIM*FEM_NODE_NUM], the
    //    finite element coefficient values at each node.
    //
    //    Input, int SAMPLE_NODE_NUM, the number of sample nodes.
    //
    //    Input, double SAMPLE_NODE_XYZ[3*SAMPLE_NODE_NUM], the sample nodes.
    //
    //    Output, double PROJECTION[FEM_VALUE_DIM*SAMPLE_NODE_NUM],
    //    the sampled values.
    //
    {
        int face = 0;
        int j;

        double[] p_xyz    = new double[3];
        int      step_num = 0;

        double[] b            = new double[fem_element_order];
        double[] sample_value = new double[fem_value_dim * sample_node_num];
        double[] t_xyz        = new double[3 * fem_element_order];
        //
        //  For each sample point: find the element T that contains it,
        //  and evaluate the finite element function there.
        //
        for (j = 0; j < sample_node_num; j++)
        {
            p_xyz[0] = sample_node_xyz[0 + 3 * j];
            p_xyz[1] = sample_node_xyz[1 + 3 * j];
            p_xyz[2] = sample_node_xyz[2 + 3 * j];
            //
            //  Find the triangle T that contains the point.
            //
            int t = TetMesh.tet_mesh_search_delaunay(fem_node_num, fem_node_xyz,
                                                     fem_element_order, fem_element_num, fem_element_node,
                                                     fem_element_neighbor, p_xyz, ref face, ref step_num);

            switch (t)
            {
            case -1:
                Console.WriteLine("");
                Console.WriteLine("PROJECTION - Fatal error!");
                Console.WriteLine("  Search failed.");
                return(null);
            }

            //
            //  Evaluate the finite element basis functions at the point in T.
            //
            int t_node;
            int i;
            for (i = 0; i < fem_element_order; i++)
            {
                t_node           = fem_element_node[i + t * fem_element_order];
                t_xyz[0 + i * 3] = fem_node_xyz[0 + t_node * 3];
                t_xyz[1 + i * 3] = fem_node_xyz[1 + t_node * 3];
                t_xyz[2 + i * 3] = fem_node_xyz[2 + t_node * 3];
            }

            Basis_mn.basis_mn_tet4(t_xyz, 1, p_xyz, ref b);
            //
            //  Multiply by the finite element values to get the sample values.
            //
            for (i = 0; i < fem_value_dim; i++)
            {
                double dot = 0.0;
                int    k;
                for (k = 0; k < fem_element_order; k++)
                {
                    t_node = fem_element_node[k + t * fem_element_order];
                    dot   += fem_value[i + t_node * fem_value_dim] * b[k];
                }

                sample_value[i + j * fem_value_dim] = dot;
            }
        }

        return(sample_value);
    }