Writing a FOM function

The figure of merit (FOM) function is one of the most important things when fitting a model to measured data. The FOM defines what a good fit is and distinguishes it from a bad fit. Every fitting problem has its own quirks and twists and may therefore benefit from having a custom FOM function written for that specific problem. Here, we will briefly go through the steps you need to take to extend GenX with your very own FOM function.

Making a custom FOM available in GenX

The file that defines the different built-in FOM functions is called fom_funcs.py. As of svn release 145, there is a simple and transparent way to add your own FOM functions in a separate file, which must be called fom_funcs_custom.py and reside in the same directory as fom_funcs.py. This file is read by fom_funcs.py if it exists, otherwise it is ignored. The fom_funcs_custom.py file is not part of the GenX distribution, but must be created by the user. This ensures that custom-build FOM functions are not overwritten when updating the GenX distribution to the latest version. All custom FOM function definitions can be included in this one file, or you may choose to read in several other files from fom_funcs_custom.py, just as fom_funcs_custom.py``is read in ``fom_funcs.py (Look at the code of fom_funcs.py to see how that can be achieved).

Once you have defined a new FOM function in fom_funcs_custom.py, you just need to restart GenX and it should become available under Settings‣Optimizer as usual. What you might want to do is to add some documentation to the html string which is used as a help function within GenX. And finally, when it works, why not send it to me for inclusion in the main distribution.

Example of a FOM function

It’s always easiest to start with an example to get ones head around things. Let us start with an absolute logarithmic difference figure of merit. The code below is a slow but easy to understand variant:

def log(simulations, data):
    ''' The absolute logarithmic difference'''
    N = 0 # Total number of data points
    fom = 0 # The total fom to calculate
    for dataset, sim in zip(data, simulation):
        if dataset.use:
            fom = fom + np.sum(np.abs(np.log10(dataset.y) - np.log10(sim)))
            N = N + len(dataset.x)
    fom = fom/N
    return fom

For each data set which is active, i.e. which has its use attribute set to True, we add the summed logarithmic difference to fom and the number of data points to N. The current status of active is displayed in the data list seen in the left panel in GenX.

The most tricky statement for a non-python programmer is probably the zip function in the first loop. This will, just like a zipper, create a long list of tuple pairs for each element in data and simulation. See. The statement dataset, sim will “unbundle” them again, just as if two items where returned from a function. Next up is the calculation of each data point in the current data set and adding it to the total fom. We also keep track on the number of data points for later normalization. Note that the fom is only calculated and added if the flag dataset.use is True.

An example how this can be programmed in a more compact and computationally efficient way is seen below, where a faster type of for loop is used.

def log(simulations, data):
    ''' The absolute logarithmic difference'''
    N = np.sum([len(dataset.y)*dataset.use for dataset in data])
    return 1.0/(N - 1)*np.sum([np.sum(np.abs(np.log10(dataset.y) - np.log10(sim)))
        for (dataset, sim) in zip(data, simulations) if dataset.use])

It looks a bit more complicated since it uses list comprehension, which is faster and more compact than ordinary for loops. Also note the if statement last inside the brackets: if dataset.use this will only append an item to the list if the condition is true. That is if the use flag is set. To learn more about list comprehensions go to the python docs. This is the syntax that you will find most in the built-in FOM functions provided.