TEUCHOS_UNIT_TEST(integration_values, volume)
  {
    PHX::KokkosDeviceSession session;
    
    Teuchos::RCP<shards::CellTopology> topo = 
       Teuchos::rcp(new shards::CellTopology(shards::getCellTopologyData< shards::Quadrilateral<4> >()));

    const int num_cells = 20;
    const int base_cell_dimension = 2;
    const panzer::CellData cell_data(num_cells,topo);

    const int cubature_degree = 2;    
    RCP<IntegrationRule> int_rule = 
      rcp(new IntegrationRule(cubature_degree, cell_data));
    
    panzer::IntegrationValues2<double> int_values("prefix_",true);
    panzer::MDFieldArrayFactory af("prefix_",true);

    int_values.setupArrays(int_rule);

    const int num_vertices = int_rule->topology->getNodeCount();
    PHX::MDField<double,Cell,NODE,Dim> node_coordinates 
        = af.buildStaticArray<double,Cell,NODE,Dim>("nc",num_cells, num_vertices, base_cell_dimension);

    // Set up node coordinates.  Here we assume the following
    // ordering.  This needs to be consistent with shards topology,
    // otherwise we will get negative determinates

    // 3(0,1)---2(1,1)
    //   |    0  |
    //   |       |
    // 0(0,0)---1(1,0)

    typedef panzer::ArrayTraits<double,PHX::MDField<double> >::size_type size_type;
    const size_type x = 0;
    const size_type y = 1;
    for (size_type cell = 0; cell < node_coordinates.dimension(0); ++cell) {
      node_coordinates(cell,0,x) = 0.0;
      node_coordinates(cell,0,y) = 0.0;
      node_coordinates(cell,1,x) = 1.0;
      node_coordinates(cell,1,y) = 0.0;
      node_coordinates(cell,2,x) = 1.0;
      node_coordinates(cell,2,y) = 1.0;
      node_coordinates(cell,3,x) = 0.0;
      node_coordinates(cell,3,y) = 1.0;
    }

    int_values.evaluateValues(node_coordinates);
    
    TEST_EQUALITY(int_values.ip_coordinates.dimension(1), 4);
    double realspace_x_coord = (1.0/std::sqrt(3.0) + 1.0) / 2.0;
    double realspace_y_coord = (1.0/std::sqrt(3.0) + 1.0) / 2.0;
    TEST_FLOATING_EQUALITY(int_values.ip_coordinates(0,0,0), 
                           realspace_x_coord, 1.0e-8);
    TEST_FLOATING_EQUALITY(int_values.ip_coordinates(0,0,1), 
                           realspace_y_coord, 1.0e-8);

  }
int
SOP_PrimGroupCentroid::buildCentroids(fpreal t, int mode, int method)
{
    bool                        store;
    exint                       int_value;

    const GA_AIFStringTuple     *ident_t;
    GA_Attribute                *ident_attrib;
    GA_Offset                   ptOff;
    GA_RWAttributeRef           ident_gah;
    GA_RWHandleI                class_h;

    const GU_Detail             *input_geo;

    UT_BoundingBox              bbox;
    UT_String                   attr_name, pattern, str_value;
    UT_Vector3                  pos;

    UT_Array<GA_Range>          range_array;
    UT_Array<GA_Range>::const_iterator  array_it;
    UT_StringArray              string_values;
    UT_IntArray                 int_values;

    // Get the input geometry as read only.
    GU_DetailHandleAutoReadLock gdl(inputGeoHandle(0));
    input_geo = gdl.getGdp();

    // Check to see if we should store the source group/attribute name as an
    // attribute the generated points.
    store = STORE(t);

    // If we want to we need to create the attributes.
    if (store)
    {
        // A 'class' operation, so create a new integer attribute.
        if (mode == 2)
        {
            // Add the int tuple.
            ident_gah = gdp->addIntTuple(GA_ATTRIB_POINT, "class", 1);
            // Bind the handle.
            class_h.bind(ident_gah.getAttribute());
        }
        // Using the 'name' attribute or groups, so create a new string
        // attribute.
        else
        {
            attr_name = (mode == 0) ? "group" : "name";

            // Create a new string attribute.
            ident_gah = gdp->addStringTuple(GA_ATTRIB_POINT, attr_name, 1);
            ident_attrib = ident_gah.getAttribute();

            // Get the string tuple so we can set values.
            ident_t = ident_gah.getAIFStringTuple();
        }
    }

    // Create a new attribute reference map.
    GA_AttributeRefMap          hmap(*gdp, input_geo);

    // Get the attribute selection string.
    ATTRIBUTES(pattern, t);

    // If we have a pattern, try to build the ref map.
    if (pattern.length() > 0)
        buildRefMap(hmap, pattern, gdp, input_geo, mode, GA_ATTRIB_PRIMITIVE);

    // The list of GA_Primitives in the input geometry.
    const GA_PrimitiveList &prim_list = input_geo->getPrimitiveList();

    // Creating by groups.
    if (mode == 0)
    {
        // Get the group pattern.
        GROUP(pattern, t);

        // If the group string is empty, get out of here.
        if (pattern.length() == 0)
            return 1;

        buildGroupData(pattern, input_geo, range_array, string_values);
    }
    // 'name' or 'class'.
    else
    {
        // Build the data.  If something failed, return that we had an issue.
        if (buildAttribData(mode, input_geo, range_array, string_values, int_values))
            return 1;
    }

    // Iterate over each of the primitive ranges we found.
    for (array_it=range_array.begin(); !array_it.atEnd(); ++array_it)
    {
        // Create a new point.
        ptOff = gdp->appendPointOffset();

        // Bounding Box
        if (method == 1)
        {
            // Calculate the bouding box center for this range.
            boundingBox(input_geo, *array_it, prim_list, pos);
            // Set the point's position to the center of the box.
            gdp->setPos3(ptOff, pos);
        }
        // Center of Mass
        else if (method == 2)
        {
            // Calculate the center of mass for this range.
            centerOfMass(*array_it, prim_list, pos);
            // Set the point's position to the center of mass.
            gdp->setPos3(ptOff, pos);
        }
        // Barycenter
        else
        {
            // Calculate the barycenter for this range.
            baryCenter(input_geo, *array_it, prim_list, pos);
            // Set the point's position to the barycenter.
            gdp->setPos3(ptOff, pos);
        }

        // Store the source value if required.
        if (store)
        {
            // 'class', so get the integer value at this iterator index.
            if (mode == 2)
            {
                int_value = int_values(array_it.index());
                class_h.set(ptOff, int_value);
            }
            // 'name' or by group, so get the string value at this iterator
            // index.
            else
            {
                str_value = string_values(array_it.index());
                ident_t->setString(ident_attrib, ptOff, str_value, 0);
            }
        }

        // If there are no entries in the map then we don't need to copy
        // anything.
        if (hmap.entries() > 0)
        {
            GA_WeightedSum              sum;

            // Start a weighted sum for the range.
            hmap.startSum(sum, GA_ATTRIB_POINT, ptOff);

            // Add the values for each primitive to the sum.
            for (GA_Iterator it(*array_it); !it.atEnd(); ++it)
            {
                hmap.addSumValue(sum,
                                 GA_ATTRIB_POINT,
                                 ptOff,
                                 GA_ATTRIB_PRIMITIVE,
                                 *it,
                                 1);
            }

            // Finish the sum, normalizing the values.
            hmap.finishSum(sum,
                           GA_ATTRIB_POINT,
                           ptOff,
                           1.0/(*array_it).getEntries());
        }
    }

    return 0;
}