How to "unit test" Fortran UDFs?

In essence, I’m wondering if it’s possible to do something similar to the unit test module in Python, but for user defined functions (UDFs) in MFIX. I’ve provided some more information about why I want to do this and what I’m hoping to achieve below.


When I implement MFIX UDFs that perform arithmetic, it’s hard to know whether my code is actually working as intended. You can always embed a bunch of print statements to try and catch errors, but when we talk about operations on large arrays, it can be hard to think of all the edge cases (i.e. situations that result in division by zero or give NaNs) or detect “bad” individual array elements. It also seems excessive to create a bunch of user defined scalars to save variables during code development for this purpose.

What I really want is to, for example, set up a breakpoint in the UDF with visual debugging inside Visual Studio Code so I can monitor the code for a few time steps as it executes line by line, but I haven’t figured out how to do this. If the whole code was just one Fortran file, it would be fairly easy to test each UDF standalone, but as far as I can tell all the module imports make this approach nearly impossible.

Let me know if anyone has any input or alternative methods to suggest. Thanks!

My suggestion is to always build the solver with debug flags while implementing a UDF and not hesitate to test it before the UDF is complete.

Jeff,

What do you mean by “test” in this context? As an example, let’s say I had a UDF implementing a custom cohesive force calculation that goes into calc_force_dem - how would you go about testing this UDF?

This is where I get a bit stuck; no errors or warnings from the debugger could still be a “false positive”. I feel like there needs to be another layer of testing the actual validity of the output, which I don’t know how to do without excessive print statements.

By “test”, I meant compile and run with a simplified setup, i.e., say a coarser mesh, just a few particles if it is a DEM simulation etc. Something that will be easier to debug compared with the production run. By “test before the UDF is complete”, I meant you don’t need to have the full UDF implemented, you could first make sure some intermediate variables are computed the way you want.

Now how or what to test depends on the inputs/outputs of the UDF. Say you have a few scalar inputs and one scalar output, you can test the routine outside of MFiX. If it needs to pass an array of cell values or particles, then you need to test within MFiX.

Regarding your example, I would set up a case with 2 particles to test particle-particle cohesion and a case with one particle and a wall to test particle-wall cohesion, and use the particle_input.dat file to control the initial condition. Then I would do a hand calculation to know the expected values. Here a print statement would be fine to check you get values you expect. I would also visualize the data (vtp file) at a high frequency to make sure it behaves correctly. Repeat by increasing the Hamaker constant, or whatever parameter is in the model, including extreme cases, say zero value, extremely large values, or unphysical values (say negative values: is there a data check, does the code handle this or crashes?).

When you write code, pay attention to any operation that could be problematic: If there is a division, is there any chance you will ever divide by zero? If you evaluate a square root or non integer power, is there any chance the argument will ever become negative etc.

I hope this help. There is of course no fool proof method to test a code and guarantee there are no bugs or limitations, but you can hopefully catch them as early as possible.

Jeff,

This is helpful, thank you! I have yet to write a UDF that can be tested outside of MFIX, guess I’ve unintentionally made things harder for myself :upside_down_face:

I wasn’t thinking about the 2-particles option, that would be quite an easy way to test what I want with just print statements. I will give this a try.

Julia

Never underestimate the power of a humble write (*,*) !!!

Haha! I used to feel that way, but even print statements are susceptible to some obscuration - the programmer who wrote them has to make some assumptions about the data (via print string formatting, etc.) that could cause certain problems to remain hidden.

The more I learn about debugging and validating code, the more I find myself doubting everything :rofl:

Using (*,*) means you don’t have to worry so much about the print string formatting. But I know what you mean about doubting everything. Sometimes I just use something like write (*,*) "XXXXXXX" just to see if a particular block of code is even executing. Or if you have a complex calculation, it’s often helpful to break it up and print out intermediate parts (numerators and denominators), as in this example: https://mfix.netl.doe.gov/forum/t/segmentation-fault-invalid-memory-reference-in-tfm-simualtion/2674/34 - if you’re just printing numbers, you don’t really have to worry about format strings.

Ah okay, that’s good to know! I did read that example back when you posted it, very helpful in understanding the error tracking process. I appreciate the attention to detail in your description.

1 Like