Plugin How-To – Obtaining Face Counts

Using the Pointwise Grid Model from within a plugin can be a little confusing. This leads to customers calling our support team with a plugin question that starts with the phrase “How do I…?

While many of these inquiries can be answered by pointing the customer to the appropriate pages in the Pointwise CAE Plugin SDK documentation, some inquiries require a more in-depth explanation with code examples.

I plan to periodically post these interesting inquiries along with their answers. These posts should provide valuable insight into writing a plugin and using the Pointwise Grid Model.

Today’s Plugin How-To deals with obtaining grid model face counts.

Q: How do I determine the total number of internal faces in an unstructured grid?

You will need to do a little grid processing first to calculate the number of internal faces.

First, you must calculate the total number of face usages by all cells from all blocks in the grid. Let’s call this value usageCount. For example, a hex cell has 6 face usages and a pyramid cell has 5 face usages. The total face usage count represents the sum of all boundary faces and all interior faces. However, as you may have already noticed, interior faces are shared between adjacent cells. This results in the double counting of interior faces! That is okay. We will adjust for that later.

Next, you must calculate the total number of grid boundary faces. Let’s call this value bndryCount. This is done by stepping through the grid model domains and summing the total element counts. The individual tri or quad face counts are not needed here. Just the total face count is needed.

With usageCount and bndryCount, we can compute the number of internal faces as:

internalCount = (usageCount – bndryCount) / 2

Subtracting the number of boundary faces from the total face usage count leaves us with only the number of double counted internal faces. Dividing this by 2 gives us the number of distinct interior faces. It is important to note that for this calculation, connection faces are considered to be interior faces. A connection face is a face shared between two cells with different volume conditions.

Example Code Using the C API

static PWP_UINT32
calcInternalFaceCount(CAEP_RTITEM *pRti)
{
    PWGM_ELEMCOUNTS cnts;
    // Count block cell face usage counts
    PWP_UINT32 usageCount = 0;
    PWP_UINT32 ndx = 0;
    PWGM_HBLOCK hBlk = PwModEnumBlocks(pRti->model, ndx);
    while (PWGM_HBLOCK_ISVALID(hBlk)) {
        PwBlkElementCount(hBlk, &cnts);
        // process elements according to the export's
        // dimensionality.
        if (CAEPU_RT_DIM_2D(pRti)) {
            // Run through all 2D elems and sum their
            // respective edge counts
            usageCount += PWGM_ECNT_Quad(cnts) * 4 +
                        PWGM_ECNT_Tri(cnts) * 3;
        }
        else {
            // Run through all 3D elems and sum their
            // respective face counts
            usageCount += PWGM_ECNT_Tet(cnts) * 4 +
                        PWGM_ECNT_Pyramid(cnts) * 5 +
                        PWGM_ECNT_Wedge(cnts) * 5 +
                        PWGM_ECNT_Hex(cnts) * 6;
        }
        hBlk = PwModEnumBlocks(pRti->model, ++ndx);
    }

    // Count boundary faces
    PWP_UINT32 bndryCount = 0;
    ndx = 0;
    PWGM_HDOMAIN hDom = PwModEnumDomains(pRti->model, ndx);
    while (PWGM_HBLOCK_ISVALID(hDom)) {
        PwDomElementCount(hDom, &cnts);
        // process elements according to the export dimension
        if (CAEPU_RT_DIM_2D(pRti)) {
            // Sum the 2D boundary edge counts
            bndryCount += PWGM_ECNT_Bar(cnts);
        }
        else {
            // Sum the 3D boundary face counts
            bndryCount += PWGM_ECNT_Quad(cnts) +
                          PWGM_ECNT_Tri(cnts);
        }
        hDom = PwModEnumDomains(pRti->model, ++ndx);
    }

    // Compute number of internal faces
    PWP_UINT32 internalCount = (usageCount - bndryCount) / 2;

    char msg[128];
    caeuSendInfoMsg(pRti, "CALC FACE COUNTS:", 0);

    sprintf(msg, "boundary: %lu", (unsigned long)bndryCount);
    caeuSendInfoMsg(pRti, msg, 0);

    sprintf(msg, "interior: %lu",
        (unsigned long)internalCount);
    caeuSendInfoMsg(pRti, msg, 0);

    sprintf(msg, "total: %lu",
        (unsigned long)bndryCount + internalCount);
    caeuSendInfoMsg(pRti, msg, 0);

    return internalCount;
}

// Message window output:
// Info: (0) CALC FACE COUNTS:
// Info: (0) boundary: 13800
// Info: (0) interior: 830050
// Info: (0) total: 843850

Example Code Using the C++ API

void YourPlugin::calcFaceCounts()
{
    PWGM_ELEMCOUNTS cnts;
    // Count block cell face usage counts
    PWP_UINT32 usageCount = 0;
    CaeUnsBlock blk(model_);
    while (blk.isValid()) {
        blk.elementCount(&cnts);
        // process elements according to the export dimension.
        if (isDimension2D()) {
            // Run through all 2D elems and sum their
            // respective edge counts
            usageCount += PWGM_ECNT_Quad(cnts) * 4 +
                          PWGM_ECNT_Tri(cnts) * 3;
        }
        else {
            // Run through all 3D elems and sum their
            // respective face counts
            usageCount += PWGM_ECNT_Tet(cnts) * 4 +
                          PWGM_ECNT_Pyramid(cnts) * 5 +
                          PWGM_ECNT_Wedge(cnts) * 5 +
                          PWGM_ECNT_Hex(cnts) * 6;
        }
        ++blk;
    }
    // Count boundary faces
    PWP_UINT32 bndryCount = 0;
    CaeUnsPatch patch(model_);
    while (patch.isValid()) {
        patch.elementCount(&cnts);
        // process elements according to the export dimension.
        if (isDimension2D()) {
            // Sum the 2D boundary edge counts
            bndryCount += PWGM_ECNT_Bar(cnts);
        }
        else {
            // Sum the 3D boundary face counts
            bndryCount += PWGM_ECNT_Quad(cnts) +
                          PWGM_ECNT_Tri(cnts);
        }
        ++patch;
    }
    // Compute number of internal faces
    PWP_UINT32 internalCount = (usageCount - bndryCount) / 2;
    char msg[128];
    sendInfoMsg("CALC FACE COUNTS:");

    sprintf(msg, "boundary: %lu", (unsigned long)bndryCount);
    sendInfoMsg(msg);

    sprintf(msg, "interior: %lu",
        (unsigned long)internalCount);
    sendInfoMsg(msg);

    sprintf(msg, "total: %lu",
        (unsigned long)bndryCount + internalCount);
    sendInfoMsg(msg);
}

// Message window output:
// Info: (0) CALC FACE COUNTS:
// Info: (0) boundary: 13800
// Info: (0) interior: 830050
// Info: (0) total: 843850

Q: I see that face count information is available to the face streaming callbacks. Can I use face streaming to obtain the face counts?

The short answer is yes.

However, face streaming can be a time- and memory-intensive operation. Face streaming should only be used if needed. Fortunately, there is a little face streaming trick you can use to obtain the face counts without incurring the usual overhead.

Basically, you should invoke the face streamer as usual providing the appropriate callbacks. Then, you would capture the face count numbers in the beginStream() callback. However, the beginStream() callback should return 0 (zero) to halt streaming! This approach is acceptable because the processing performed by the face streamer prior to calling the beginStream() callback is relatively light. The vast majority of the face streaming overhead is avoided by returning 0.

Example Code Using the C API

struct FaceCounts {
    // Capture these values
    PWP_UINT32 totalNumFaces_;
    PWP_UINT32 numBoundaryFaces_;
    PWP_UINT32 numInteriorFaces_;
};

static PWP_UINT32 beginCB(PWGM_BEGINSTREAM_DATA *data)
{
    FaceCounts *cnts = (FaceCounts*)data->userData;
    // capture face counts
    cnts->totalNumFaces_ = data->totalNumFaces;
    cnts->numBoundaryFaces_ = data->numBoundaryFaces;
    cnts->numInteriorFaces_ = data->numInteriorFaces;
    return 0; // 0 forces streaming to end
}

static PWP_UINT32
streamInternalFaceCount(CAEP_RTITEM *pRti)
{
    // Instatiate the face count data.
    FaceCounts faceCounts;
    // Stream faces through beginCB. It is okay to set the
    // faceCB and endCB to null because they are never
    // called. Pass faceCounts to streamer as user data.
    PwModStreamFaces(pRti->model, PWGM_FACEORDER_DONTCARE,
        beginCB, 0, 0, &faceCounts);
    // After PwModStreamFaces() returns, faceCounts has been
    // updated with the counts!
    char msg[128];
    caeuSendInfoMsg(pRti, "STREAM FACE COUNTS:", 0);

    sprintf(msg, "boundary: %lu",
        (unsigned long)faceCounts.numBoundaryFaces_);
    caeuSendInfoMsg(pRti, msg, 0);

    sprintf(msg, "interior: %lu",
        (unsigned long)faceCounts.numInteriorFaces_);
    caeuSendInfoMsg(pRti, msg, 0);

    sprintf(msg, "total: %lu",
        (unsigned long)faceCounts.totalNumFaces_);
    caeuSendInfoMsg(pRti, msg, 0);

    return faceCounts.numInteriorFaces_;
}

// Message window output:
// Info: (0) STREAM FACE COUNTS:
// Info: (0) boundary: 13800
// Info: (0) interior: 830050
// Info: (0) total: 843850

Example Code Using the C++ API

// Define a stream handler to capture face counts
class FaceCounts : public CaeFaceStreamHandler {
public:
    FaceCounts() :
        totalNumFaces_(0),
        numBoundaryFaces_(0),
        numInteriorFaces_(0)
    {
    }

    ~FaceCounts()
    {
    }

    PWP_UINT32 streamBegin(const PWGM_BEGINSTREAM_DATA &data)
    {
        // capture face counts
        totalNumFaces_ = data.totalNumFaces;
        numBoundaryFaces_ = data.numBoundaryFaces;
        numInteriorFaces_ = data.numInteriorFaces;
        return 0; // 0 forces streaming to end
    }

    PWP_UINT32 streamFace(const PWGM_FACESTREAM_DATA &)
    {
        // This virtual override is required. However, it
        // will never get called because we return 0 in
        // streamBegin()
        return 0;
    }

    // Capture these values
    PWP_UINT32 totalNumFaces_;
    PWP_UINT32 numBoundaryFaces_;
    PWP_UINT32 numInteriorFaces_;
};

// Obtain the face counts in your plugin
void YourPlugin::streamFaceCounts()
{
    // Instantiate the face count stream handler
    FaceCounts faceCounts;
    // stream faces through the faceCounts handler
    model_.streamFaces(PWGM_FACEORDER_DONTCARE, faceCounts);
    // After streamFaces() returns, faceCounts has been
    // updated with the counts!
    char msg[128];
    sendInfoMsg("STREAM FACE COUNTS:");

    sprintf(msg, "boundary: %lu",
        (unsigned long)faceCounts.numBoundaryFaces_);
    sendInfoMsg(msg);

    sprintf(msg, "interior: %lu",
        (unsigned long)faceCounts.numInteriorFaces_);
    sendInfoMsg(msg);

    sprintf(msg, "total: %lu",
        (unsigned long)faceCounts.totalNumFaces_);
    sendInfoMsg(msg);
}

// Message window output:
// Info: (0) STREAM FACE COUNTS:
// Info: (0) boundary: 13800
// Info: (0) interior: 830050
// Info: (0) total: 843850

Your Suggestions Welcome

I hope this information is useful. If you have any other ideas for a future Plugin How-To post, please leave a comment below or contact our support staff.

Happy coding!

Find out more about the Pointwise Plugin SDK by clicking on the link below.

Learn more about the Pointwise CAE Plugin SDK

About David Garlisch

Illini by birth, Texan by choice.
This entry was posted in Applications, Software and tagged , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s