0

I am trying to fit a complex conductivity model (the drude-smith-anderson model) using lmfit.minimize. In that fitting, I want constraints on my parameters c and c1 such that 0<c<1, -1<c1<0 and 0<1+c1-c<1. So, I am using the following code:

#reference: Juluri B.K. "Fitting Complex Metal Dielectric Functions with Differential Evolution Method". http://juluribk.com/?p=1597.
#reference: https://lmfit.github.io/lmfit-py/fitting.html

#import libraries (numdifftools needs to be installed but doesn't need to be imported)
import matplotlib.pyplot as plt
import numpy as np
import lmfit as lmf
import math as mt

#define the complex conductivity model
def model(params,w):
    sigma0 = params["sigma0"].value
    tau = params["tau"].value
    c = params["c"].value
    d = params["d"].value
    c1 = params["c1"].value
    druidanderson = (sigma0/(1-1j*2*mt.pi*w*tau))*(1 + c1/(1-1j*2*mt.pi*w*tau)) - sigma0*c/(1-1j*2*mt.pi*w*d*tau)
    return druidanderson

#defining the complex residues (chi squared is sum of squares of residues)
def complex_residuals(params,w,exp_data):
    delta = model(params,w)
    residual = (abs((delta.real - exp_data.real) / exp_data.real) + abs(
        (delta.imag - exp_data.imag) / exp_data.imag))
    return residual

# importing data from CSV file
importpath = input("Path of CSV file: ") #Asking the location of where your data file is kept (give input in form of path\name.csv)
frequency = np.genfromtxt(rf"{importpath}",delimiter=",", usecols=(0)) #path to be changed to the file from which data is taken
conductivity = np.genfromtxt(rf"{importpath}",delimiter=",", usecols=(1)) + 1j*np.genfromtxt(rf"{importpath}",delimiter=",", usecols=(2)) #path to be changed to the file from which data is taken
frequency = frequency[np.logical_not(np.isnan(frequency))]
conductivity = conductivity[np.logical_not(np.isnan(conductivity))]
w_for_fit = frequency
eps_for_fit = conductivity

#defining the bounds and initial guesses for the fitting parameters
params = lmf.Parameters()
params.add("sigma0", value = float(input("Guess for \u03C3\u2080: ")), min =10 , max = 5000) #bounds have to be changed manually
params.add("tau", value = float(input("Guess for \u03C4: ")), min = 0.0001, max =10) #bounds have to be changed manually
params.add("c1", value = float(input("Guess for c1: ")), min = -1 , max = 0) #bounds have to be changed manually
params.add("constraint", value = float(input("Guess for constraint: ")), min = 0, max=1)
params.add("c", expr="1+c1-constraint", min = 0, max = 1) #bounds have to be changed manually
params.add("d", value = float(input("Guess for \u03C4_1/\u03C4: ")),min = 100, max = 100000) #bounds have to be changed manually


# minimizing the chi square
minimizer_results = lmf.minimize(complex_residuals, params, args=(w_for_fit, eps_for_fit), method = 'differential_evolution', strategy='best1bin',
                             popsize=50, tol=0.01, mutation=(0, 1), recombination=0.9, seed=None, callback=None, disp=True, polish=True, init='latinhypercube')
lmf.printfuncs.report_fit(minimizer_results, show_correl=False)

As a result for the fit, I get the following output:

sigma0:      3489.38961 (init = 1000)
    tau:         1.2456e-04 (init = 0.01)
    c1:         -0.99816132 (init = -1)
    constraint:  0.98138820 (init = 1)
    c:           0.00000000 == '1+c1-constraint'
    d:           7333.82306 (init = 1000)

These values don't make any sense as 1+c1-c = -0.97954952 which is not 0 and is thus invalid. How to fix this issue?

1 Answers1

0

Your code is not runnable. The use of input() is sort of stunning - please do not do that. Write code that is pleasant to read and separates i/o from logic.

To make a floating point residual from a complex array, use complex_array.view(float)

Guessing any parameter value to be at or very close to its limit (here, c) is a very bad idea, likely to make the fit harder.

More to your question, you defined c as "evaluate 1+c1-constant and then apply the bounds min=0, max=1". That is literally, precisely, and exactly what your

params.add("c", expr="1+c1-constraint", min = 0, max = 1)

means: calculate c as 1+c1-constraint, and then apply the bounds [0, 1]. The code is doing exactly what you told it to do.

Unless you know what you are doing (I suspect maybe not ;)), I would strongly advise doing a fit with the default leastsq method before trying to use differential_evolution. It turns out that differential_evolution is not a very good global fitting method (shgo is generally better, though no "global" solver should be considered as very reliable). But, unless you know that you need such a method, you probably do not.

I would also strongly advise you to plot your data and some models evaluated with what you think are reasonable parameters.

M Newville
  • 7,486
  • 2
  • 16
  • 29
  • Dear professor, I only used this method because the standard fitting methods didn't work for my data (the same problem was faced by prof. bala juluri, whose implementation I used after modification (reference given at the top). Also, as we had to work on a lot of datasets, it was much more convenient to just input the path of the file each time we ran the code. Could you just tell me how to apply the bounds first and then evaluate c instead of the opposite? – Garvit Bansal Dec 19 '22 at 07:22
  • Also, I plotted the fits and got very good fits when I was not trying to add 0<1+c1-c<1 constraint so there should not be any problem with my implementation – Garvit Bansal Dec 19 '22 at 07:33