2.1. How to use the tutorial

Consider the data:

#exponential data
0.0  1.0
1.0  3.0
1.0  6.0
2.0  13.0
4.0  15.0
5.0  30.0
6.0  61.0
7.0  67.0
8.0  130.0

The first column represents x values, the second represents y values. If you add more columns, they will represent the y values of another data set, like here:

#description of data
0.700  -108.10063  -108.10428  -108.10300
0.800  -108.84659  -108.85078  -108.85017
0.900  -109.19790  -109.20283  -109.20336
1.000  -109.33838  -109.34432  -109.34622
1.100  -109.36798  -109.37535  -109.37872
1.200  -109.34164  -109.35103  -109.35611
1.300  -109.28984  -109.30207  -109.30888
1.400  -109.22937  -109.24540  -109.25394
1.500  -109.16919  -109.18994  -109.20050
1.600  -109.11371  -109.13998  -109.15273
1.800  -109.02197  -109.05764  -109.07838
2.000  -108.95762  -108.99855  -109.02822
2.200  -108.91890  -108.96283  -108.99615

Coming back to the first data set, it resembles an exponential function, so we can use the ExponentialFit class to make a fit. Copy the above data to a text file and name it exp.dat. Inside the same repository create a Python script, here it is called example_fit.py. Copy the lines and run the script.

import minifit

exp_fit = minifit.ExponentialFit("exp.dat") #reads the data from exp.dat and sets properties of the fit with keyword arguments, if they are not passed, then default values are used.
exp_fit() #tries to fit with chosen data and configuration

then in terminal:

python3 example_fit.py

The numerical results (optimal parameters of the model function) are going to be displayed in the terminal. Inside the folder that contains example_fit.py a new one called minifit-results will be created, where a graph with a new fit (pdf) will be saved.

Note

When searching for a file, MiniFit follows a specific sequence of locations.

First, it looks for the file in the current working directory (CWD) where the fitting_script.py resides. If the file exists in the CWD, it is considered for further processing.

If the file is not found in the CWD, MiniFit looks for the file in the data folder in the ./data directory. If the file is found in the data folder, it is used for subsequent operations.

If the file is not found in either the CWD or the data folder, MiniFit treats the filename as an absolute path. It attempts to locate the file using the provided absolute path. If the file is found at the specified location, it is passed to the read_data function.

some_folder/
  • fitting_script.py

  • data1.dat

  • random_file

  • data/

  • data1.dat

  • path/to/file/data1.dat

MiniFit searches for the file in the following order: first in the current working directory (CWD), then in the ./data folder, and finally, as an absolute path. If the file is found in any of these locations, it is passed to the read_data function for further processing.

Create a folder named data. Inside, add a data file example2.dat with the following columns

#description of data
0.700  -108.10131  -108.10501  -108.10391
0.800  -108.84738  -108.85164  -108.85094
0.900  -109.19889  -109.20390  -109.20426
1.000  -109.33960  -109.34565  -109.34732
1.100  -109.36948  -109.37700  -109.38005
1.200  -109.34343  -109.35304  -109.35765
1.300  -109.29190  -109.30443  -109.31058
1.400  -109.23163  -109.24802  -109.25565
1.500  -109.17136  -109.19254  -109.20188
1.600  -109.11553  -109.14200  -109.15366
1.800  -109.02295  -109.05906  -109.07775
2.000  -108.95805  -108.99939  -109.02627
2.200  -108.91896  -108.96331  -108.99293
2.400  -108.89924  -108.94816  -108.97254
2.800  -108.88591  -108.94857  -108.95394
3.200  -108.88277  -108.95726  -108.94935
import minifit

POLY = minifit.PolyFit("example2.dat", order = 7)
POLY()

then in terminal:

python3 example_fit.py

Line with “POLY()” will run the optimization using polynomials of the 7th order for data from example2.dat that is stored inside the data repository. If the “order” keyword argument is missing, it defaults to 1 (linear regression, ax + b). The numerical results (found optimal parameters of model function and corresponding errors) will be displayed inside the terminal. The graph of the results compared to the original data will be saved inside the minifit-results directory.

You can pass a guess of the parameters of the chosen model function that you think will best resemble the data when used. A good starting guess can make fit more likely to be accurate (or possible). If the guess is not passed, then the default one is chosen. The number of guess elements should match the model function arguments. For example, if you use a quadratic function ax^2 + bx + c, you can pass a guess:

import minifit

POLY = minifit.PolyFit("poly.dat", guess=(-3., 5., 45.), order = 2)
POLY()

You can use the following model classes:

import minifit

POLY = minifit.PolyFit("your_data.dat", order = 7)
EXP = minifit.ExponentialFit("your_data.dat")
GAUSS = minifit.GaussFit("some_data.txt")
L_J = minifit.LennardJonesFit("data.txt")
MORSE = minifit.MorseFit("data.txt")

#to run call created object
MORSE()

2.2. auto_fit and auto_range

If the fit that uses default guess is not satisfactory and the derivation of a good guess is impractical, then auto_guess with auto_range can be used. Instead of passing:

guess=(5., 40., 45.5)

You can pass a range, and a guess that matches the left and right limit will be randomly generated for each parameter, and then curve_fit() will use that guess for fitting.

auto_range=(
(-30, 30),
(10, 80),
(10, 90)
)

If the fit is successful, then the operation stops, and found ``popt``is displayed; if not, another guess that matches the range is generated. and curve_fit() tries to fit again, now, with a different starting guess. The fit is considered successful if the total square root error is smaller than the chosen precision (default 0.4)

self.sq_root_error = np.sqrt(
                    np.dot(diff, diff)
                )

To use auto_guess, it has to be set to True using a keyword argument. If the auto_guess is set to True, auto_range can also be passed. If it is not passed, then a default range is used.

auto_guess:

(bool) If True, fit until chosen precision is reached. Default False.

auto_range:

(tuple) Sets boundaries for each parameter of the guess. If not set, the default range is used. Only used if auto_guess is True. Instead of guess = (5., 10., -3.) pass auto_range = ((0.,10.), (5., 15.), (-40., 30.)) If fitting takes a lot of time, giving a better range might be helpful.

precision:

(float) Used by auto_guess. Default 0.4. Only used if auto_guess is True. If auto_guess is True and fitting takes a lot of time, lowering the precision may be necessary for convergence. Lowering the precision will make the fitting process faster, but the quality of popt may be worse.

Example of usage:

Consider the data:

#foo_data.txt
0.700  -108.10131  -108.10501  -108.10391
0.800  -108.84738  -108.85164  -108.85094
0.900  -109.19889  -109.20390  -109.20426
1.000  -109.33960  -109.34565  -109.34732
1.100  -109.36948  -109.37700  -109.38005
1.200  -109.34343  -109.35304  -109.35765
1.300  -109.29190  -109.30443  -109.31058
1.400  -109.23163  -109.24802  -109.25565
1.500  -109.17136  -109.19254  -109.20188
1.600  -109.11553  -109.14200  -109.15366
1.800  -109.02295  -109.05906  -109.07775
2.000  -108.95805  -108.99939  -109.02627
2.200  -108.91896  -108.96331  -108.99293
2.400  -108.89924  -108.94816  -108.97254
2.800  -108.88591  -108.94857  -108.95394
3.200  -108.88277  -108.95726  -108.94935
import minifit

morse2 = minifit.MorseFit(
"foo_data.txt",
auto_guess=False
)

Without a good guess, MiniFit cannot find optimal parameters. With auto_guess set to True, popt will be found with set precision. The smaller the range, the faster the fit will converge. If precision is too tight, the fit may not be possible. If auto_range is not passed, then the default auto_range is used.

import minifit

morse = minifit.MorseFit(
"foo_data.txt",
auto_guess=True,
precision=0.01
)

morse2 = minifit.MorseFit(
"foo_data.txt",
auto_guess=True,
auto_range=[
    (-5.0, 5.0),  # re
    (-5.0, 5.0),  # de
    (-10.0, 10.0),  # b0
    (-0.1, 0.1),  # b1
    (-0.1, 0.1),  # b2
    (-0.1, 0.1),  # b3
    (-0.1, 0.1),  # b4
    (-0.1, 0.1),  # b5
    (-0.1, 0.1),  # b6
],
precision=0.01,
)

In general, for fitting “potential curves” PolyFit, MorseFit, and UserFit give the most accurate results.

For more examples, go to https://gitlab.com/mike3.14/minifit/-/tree/main/src/examples and copy the data folder into your machine. You can copy example_fit.py, example_fit2.py, and example_fit3.py as well.

2.3. x_label and y_label

It’s possible to set custom labels and units for the x and y-axis. Consider examples:

import minifit

morse2 = minifit.MorseFit(
    "foo_data.txt",
    auto_guess=True,
    auto_range=[
        (-5.0, 5.0),  # re
        (-5.0, 5.0),  # de
        (-10.0, 10.0),  # b0
        (-0.1, 0.1),  # b1
        (-0.1, 0.1),  # b2
        (-0.1, 0.1),  # b3
        (-0.1, 0.1),  # b4
        (-0.1, 0.1),  # b5
        (-0.1, 0.1),  # b6
    ],
    precision=0.01,
    x_label="r[A]", # sets x-axis label
    y_label="E[eV]", # sets y-axis label
)
morse2() # Performs the optimization

exp_1 = minifit.ExponentialFit(
    "exp.dat", guess=(1, 1, 1), x_label="time[s]", y_label="temperature[°C]"
)  # reads data, sets guess for ``a * np.exp(b * x) + c``, where a,b,c = 1
# sets label descriptions and units used
exp_1()  # Performs the optimization

2.4. User Defined Functions

It is also possible to pass a user-defined function to MiniFit. Consider the code:

import minifit


def user_poly(x, *args, **kwargs):
    a, b, c, d, e, f, g, h = args
    return (
        a * x**7
        + b * x**6
        + c * x**5
        + d * x**4
        + e * x**3
        + f * x**2
        + g * x
        + h
    )


def quadratic_poly(x, *args, **kwargs):
    a, b, c = args
    return a * x**2 + b * x + c


a_range = [
    (-100, 100),
    (-300, 300),
    (-300, -300),
    (-300, 300),
    (-300, 300),
    (-300, 300),
    (-300, 300),
    (-300, 300),
]


poly_user_1 = minifit.UserFit(
    "example.dat",
    model=user_poly,
    num_param=8,
    auto_guess=True,
    precision=0.01,
    auto_range=a_range,
)
poly_user_1()

poly_user_2 = minifit.UserFit(
    "example.dat",
    model=quadratic_poly,
    num_param=3,
    type_of_fit="Quadratic Polynomial",
    label_type="Quadratic_Poly",
)
poly_user_2()