Recursivity and permutations
@ Lorenzo Fiore | Friday, May 17, 2024 | 4 minutes read

Recursivity and permutations

Motivation and context

  • The amazing computing platform FEniCS comes packed with a lot of useful features for the automation of solution of partial differential equations. The fact that people develop such powerful software and give it to the public as open source still honestly astonishes me after years of passion about open-source-software.
  • Social considerations aside, I recently stumbled upon an interesting feature that was not covered in the tutorials I came across, because, let’s be honest, open-source is great, but bright people definitely spend more time writing software then explaining others what they created and why.
  • The feature I stumbled upon is re-meshing: in FEniCS it’s possible to define a function over the mesh and drive a mesh-refinement algorithm with this input, great! I immediately took occasion to experiment with this feature and try out different approaches for mesh refinement in fracture propagation simulations.
  • The fact is that I ended up having a couple of options I wanted to try with quite a slow model for the testing… that’s the perfect excuse to spend a bit of time on automation! And when I say a bit I mean probably I spent as much time automating the procedure as the time I would have needed to perform the task manually. Well… at least the script is ready for the next time!
  • The core of the automation script ended up being the task of combining a bunch of parameters in permutations to analyze the effect of the variation of each one separately on the result of the Finite Element computation.

Challenges I faced

  • Long story short: I have a list of lists of parameters, the length of the list (and of the sub-lists) is undefined, and I want to get all the possible permutations of those, seems like a doable task but I was forces to leverage a programming functionality which I seldom see around: recursivity.
  • Why use recursivity? Let’s imagine the workflow to the solution of the task: for each element in the first list of parameters take each element in the second list of parameters and each element in the third and so on till the last list of parameters. Nested for cycles will do the job right?
  • Well only if the number of parameters to vary is predefined, which is not the case here: I’d like to run multiple parametric studies changing only some parameter of interest in each study, so I would need to manually change the for cycles nesting for each parametric study: not a geek option!
  • The solution to this issue is in 2 hours of stackoverflow, coding, testing and asking my-self why I’m still stubborn enough not to use ChatGPT. The outcome is a better understanding of how to use recursivity and an “AH-AH” moment of realization when I got it working, here is a sample code for understanding purposes:
parametric_studies = [
    [1, 2],
    [3, 4, 5],
    [6, 7, 8, 9]
]

def permutations(list_of_lists):
    if len(list_of_lists) == 1:
        return list_of_lists[0]
    else:   
        permutations_list = []
        for element_upper_level in list_of_lists[0]:
            for element_lower_level in permutations(list_of_lists[1:]):
                permutations_list.append( f"{element_upper_level}_{element_lower_level}")
        return permutations_list

studies = permutations(parametric_studies)

for study in studies:
    print(study)
  • Using recursivity, the permutations function is calling itself every time it needs to open a new list to iterate over its elements thus grabbing one element of each list independently from the number of lists it is fed with, brilliant!

Useful lessons I learned

  • I am perfectly aware that the time spent automating this task will be repaid only if I get a change to use it quite often, but I value the learning more then the outcome and now that it’s ready I can use it forever, unless I forget about it… that’s why I put all the code over here!
"""
- This script is meant to execute a batch of simulations in series
  with the purpose of parametric study
"""

#%% ---------------------------------------------------------------70
# Import necessary packages
# -----------------------------------------------------------------70
import os
import sys
import time

#%% ---------------------------------------------------------------70
# Control parameters
# -----------------------------------------------------------------70

FEM_file = "Slide_Gate_Plate_adaptative_refinement_without_materials_batch.py"

parameters_labels = [
    "damageRefinementThreshold", 
    "beta",
    "lowSpecificationsMaterialsFlag", 
    "eta",
    "plasticityFlag"
]

parametric_studies = [
    [ # Study influence of eta parameter
        [0.3],
        [0.5],
        [1],
        [1e6, 2e6, 3e6, 4e6],
        [0, 1]
    ]   
]

outfile = "./Parametric_Study/Parametric_study.md"

#%% ---------------------------------------------------------------70
# Auxiliary functions
# -----------------------------------------------------------------70 

def permutations(list_of_lists):
    """
    Compute list_of_lists permutations recursively
    """
    
    if len(list_of_lists) == 1:
        return list_of_lists[0]
    else:   
        permutations_list = []
        for element_upper_level in list_of_lists[0]:
            for element_lower_level in permutations(list_of_lists[1:]):
                permutations_list.append( f"{element_upper_level}_{element_lower_level}")
        return permutations_list

def fileprint(string):
    print(string)
    with open(outfile, "a") as file:
        file.writelines( "\n" + string)

#%% ---------------------------------------------------------------70
# Run Batch
# -----------------------------------------------------------------70

# measure elapsed time for inverse analysis procedure
tic = time.time()


for study_values in parametric_studies:

    # append parameter names to parameter values 
    named_study_values = []
    for ii in range(len(study_values)):
        named_list = []
        for element in study_values[ii]:
            named_list.append(f"{parameters_labels[ii]}_{element}")
        named_study_values.append(named_list)

    # run the batch
    for sim_ID in permutations(named_study_values):

        # launch the FEM with the input parameters
        try:
            os.system(f"python {FEM_file} {sim_ID}")
            fileprint(f"Sim: {sim_ID} completed.")
        except:
            fileprint(f"Got some trouble with {sim_ID}")
        
        fileprint(f"Elapsed CPU time: {int((time.time() - tic)/60):5d} [min]")

© 2021 - 2024 MechEngrLorenzoFiore

Powered by Hugo with theme Dream.

avatar

Lorenzo Fiore's blog
Leave the world a better place than you find it

Once a Scout, always a Scout!

I’m a proud member of the World Wide Scout Movement.

Try and leave this world a little better than you found it, and when your turn comes to die, you can die happy in feeling that at any rate, you have not wasted your time but have done your best.

Robert Baden-Powell, fouder of the Scout Movement

About me

If you came up here you want to know a little bit more about myself. That’s awesome!

I’m a Mechanical Engineer, a curious person and a maker! I don’t waste any time trying to learn everything I can about the topic I love the most, be it mechanical design, programming, old school hand abilities, music, languages, you name it!

This websites purpose is to share my knowledge and experience hoping this would be of any help for anyone facing the challenges I face. Enjoy and let’s stay in contact!

Social Links