\documentclass[../book.tex]{subfiles}
\graphicspath{{\subfix{.}}}
\begin{document}

\chapter{Fitting Data with Multiple Components}
\label{05CompFit}

\setcounter{section}{-1}  % Start subsections from 0

\input{prereqs/05CompFit_prereqs}

\newpage
\section{Problem}
\setcounter{subsection}{-1}  % Start subsections from 0

Looking at the spectra for your initial and final titration points, you notice that there is some structure in the MLCT band. Specifically, for the band around 600nm in the spectrum at the initial point, you wonder how many features are present and what their central positions, widths, and intensities are are.  The band clearly has two partially resolved features, but there also appears to be a shoulder on the high energy side that indicates that the band is really three underlying features. Seeing that you can now fit equations to data, you want to fit this band to try to identify the underlying components. Because this is a solution phase UV-vis spectrum, you reason that any features are heterogeneously broadened and should be treated as Gaussian lineshapes. Thus, you would really like to fit the band to three Gaussian bands, plus a background. If you treat the background as linear, you already know how to use the linear model in lmfit\footnote{See Chapter \ref{04EqFit}}. So, you wonder if there might be a similar model for a Gaussian and a way to combine multiple instances of it. 

\section{Solution}
\setcounter{subsection}{-1}  % Start subsections from 0

The solution in your mind is pretty simple: figure out how to use the lmfit \cite{library} to fit the overall shape of your MLCT band to the sum of three Gaussians and a linear background. Then you can make a plot showing the three individual Gaussian components and annotate it wiht their positions.

\subsection{Planning}

\First says that basically what you want to do is to:
\begin{enumerate}
    \item Load in the spectrum you want to fit. 

    \item Isolate the region of the spectrum you want to fit.

    \item Make a composite model that is a linear background + 3 Gaussians. 

    \item Fit the model to the desired region of the spectrum and extract parameters for the Gaussians.

    \item Plot the results. 
\end{enumerate}
They point out that you already know how to do \#1, \#4, and \#5. They say they can teach you how to do \#2 quickly, but that they is leaving on a backpacking trip with friends and so you might have to figure out \#4 by reading the online documentation.  They say you should have enough knowledge to read the documentation, and so you should give it a try. But first, they launch into a quick tutorial on how to isolate the spectral region of interest. 

"This relies on combining logical operations with array slicing, so let's see each in turn."

% Logical operations
\subsection{Getting yes/no answers to questions posed by comparison statements}
\label{Getting_yes/no_answers_to_questions_posed_by_comparison_statements}

When looking for a region of a spectrum, you really mean you are looking for wavelengths that are between two values.  In other words, you want to find values that are greater than a specific value \textit{and} also less than another value. ``We can use logical operations to test for this," \first says.  ``These operations evaluate to one of the two \cite{boolean} values: \lstinline{True} or \lstinline{False},\footnote{This is the same \cite{data_type} introduced in Section \ref{Representing_answers_to_yes/no_questions_using_booleans}.} so you can use them to decide which data points are in the range you want."

\begin{pyin}
1 > 0
\end{pyin}
\begin{pyout}
True
\end{pyout}

\begin{pyin}
0 > 0
\end{pyin}
\begin{pyout}
False
\end{pyout}
``You can also test that something is less than (<), equal to (==), and so on.\footnote{A complete list of such operators is found in Chapter \ref{DataTypes}.}  Note that the test for `equal to' uses two equal signs.  This done to distinguish it from the assignment operator (=)."


``We have encountered the \cite{keyword} \citett{in} when building \citett{for} \cite{loop}.  This was used to access the elements in a \cite{collection} (e.g., \cite{list}, \cite{array}, etc.).  We can also use this in a logical test, to check if something exists in a collection."

\begin{pyin}
mylist = [1, "one", 2, 5]
\end{pyin}
\begin{pyin}
3 in mylist
\end{pyin}
\begin{pyout}
False
\end{pyout}
``You can see that \lstinline{in} checks if the item on the left is in the collection on the right.  If it is, it will return \lstinline{True} and if it is not it will return \lstinline{False}. You could try to change the entries in \lstinline{mylist} to make the \lstinline{in} statement \lstinline{True}."


% slicing with logic
\subsection{Selecting a subset of a Numpy array using comparison expressions}
\label{Slicing_arrays_by_values_instead_of_indices_using_comparison_expressions}

``The logical tests we just saw can be applied to a Numpy array. When you do so, an array of the same length will be returned, but this array will only contain the values \lstinline{True} and \lstinline{False}. For instance, suppose you had a \textbf{Numpy array} of numbers from 1 to 20:"

\begin{pyin}
import numpy as np
\end{pyin}
\begin{pyin}
nums = np.linespace(1, 21, 1)
\end{pyin}

``You can see which elements are greater than 5, using:"
\begin{pyin}
nums > 5
\end{pyin}
\begin{pyout}
array([False, False, False, False, False,  True,  True,  True,  True, True,  True,  True,  True,  True,  True,  True,  True,  True, True,  True])
\end{pyout}

You see that this returns an array of the same length as \lstinline{nums}, but where each entry specifies if that number was greater than 5.

``Something else that is interesting is that one can use an array of booleans to specify which elements of a Numpy array to select. This is used similarly to specifying indices---that is you place this between two square brackets, after the array.  If we assign the array of booleans to a variable, then we can use this variable to select a subset of the array."

\begin{pyin}
mask = nums > 5
\end{pyin}
\begin{pyin}
nums[mask]
\end{pyin}
\begin{pyout}
array([ 6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
\end{pyout}
``Using an array of booleans to select a subset of an array is known as \cite{mask}ing and it creates a \textit{copy} (not a \cite{view}) of the array. 

You think that maybe you could skip the intermediate step of assigning the result of a logical operation to a mask, so you try another version of the code that perform the logical test directly within the square brackets. 
\begin{pyin}
nums[nums > 5]
\end{pyin}
\begin{pyout}
array([ 6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
\end{pyout}
It worked, and looks very clean! So, masking is a powerful way to select specific elements in an array.

Then, you think this could be useful for \cite{list}s, too.  So you try the same thing:

\begin{pyin}
num_list = list(range(1, 21))
\end{pyin}
\begin{pyin}
num_list > 5
\end{pyin}
\begin{pyout}
Traceback (most recent call last):

  Cell In[17], line 1
    num_list>5

TypeError: '>' not supported between instances of 'list' and 'int'
\end{pyout}

\First explains, ``Masking only works because of the \acite{broadcasting} behavior of Numpy arrays.  If you tried this on something like a \cite{list}, you will get an error."




% Let's say that you had a \textbf{Numpy array} of numbers from 1 to 20:

% \begin{pyin}
% import numpy as np
% \end{pyin}
% \begin{pyin}
% nums = np.array(range(1, 21))
% \end{pyin}
% "Now let's suppose that you only wanted the numbers that are greater than 5.  You could write a \citett{for} \cite{loop} to do this, \cite{iterating} through each item, testing if it is greater than the number of interest, adding it to a new list, and converting the final list to an array.  But there is a faster way:

% \begin{pyin}
% nums[nums > 5]
% \end{pyin}
% \begin{pyout}
% array([ 6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
% \end{pyout}
% So, you can see that, if you have a \cite{Numpy_array}, you can provide a logical test within square brackets, and this will return the result for numbers that satisfy this logical test. 

% It is important to note that this only works for Numpy arrays, not Python \cite{list}s.  

% \begin{pyin}
% numlist = list(range(1, 21))
% \end{pyin}
% \begin{pyin}
% numlist[numlist > 5]
% \end{pyin}
% \begin{pyout}
% TypeError: '>' not supported between instances of 'list' and 'int'
% \end{pyout}
% So, make sure you have a Numpy array when you try this. 

% This looks at first glance like another way to slice arrays, but there's an important distinction.  When Python evaluates a statement like \lstinline{nums[nums > 5]}, it first evaluates the expression between the square brackets.  When this is a comparison expression, the expression is broadcast (the comparison is applied to each element individually) and the result is returned as an array of equal length but containing \lstinline{True} or \lstinline{False}.  When such an array is used in the indexing position, it is called a ``mask," and the result of this operation is a new array that contains only the elements of the array at positions where the mask is \lstinline{True}.  Note that the new array is a copy, not a ``view" (see Section \ref{Access_subsets_of_lists,_arrays,_or_strings_by_slicing}), and so changes to the array that results from masking \textit{do not} change the original array.  YOu can mask an array with any array of \lstinline{True}\\\lstinline{False} values as long as they are the same length, so you can break this up into two steps if you'd like:
% \begin{pyin}
% mask = nums > 5
% \end{pyin}
% \begin{pyin}
% nums[mask]
% \end{pyin}
% \begin{pyout}
% array([ 6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
% \end{pyout}

\subsection{Selecting array subsets based on multiple conditions using \texttt{and} and \texttt{or}}
\label{Slicing_based_on_multiple_conditions_using_and_and_or}

You see that \cite{mask}ing is great, but it still doesn't do what you want.  You can test whether a point is greater than the minimum wavelength that you want to keep, or if it is less than the maximum, but not both.  \First says, ``Sometimes we want to apply multiple tests to an object.  For instance, we want to know if a point $x$ is greater than 7 \textit{and} less than 10.  Mathematically, we would just write $10 > x > 7$, but we would understand that this is really two distinct logical operations: $10 > x$ \textit{and} $x > 7$.  For the expression to evaluate to true, $x$ must satisfy the first \textit{and} the second."

``In Python, the same behavior is accomplished using the \lstinline{and} operator,\footnote{This is true of any boolean operator, like or or xor.} but Python allows the same shortcuts: \lstinline{10 > x > 7} is a shorthand for \lstinline{10 > x and x > 7}.  More complicated tests can be created using multiple operations (e.g., \lstinline{and} and \lstinline {or} statements), like \lstinline{10 > x and x > 7 or x == 0}.  For these statements, there is an order of operations that is followed, but it is usually better to use parentheses to make it very clear what order you intend the conditionals to be evaluated. For example: \lstinline{(10 > x and x > 7) or x == 0} may evaluate to a different result than \lstinline{10 > x and (x > 7 or x == 0)}."

``So, you \cite{mask}ed \cite{Numpy_array}s using a single \cite{conditional_statement}, but in this case, you want to take a subset of the arrays that corresponds to a range of values in the middle of the array.  A quick test reveals the problem:"

\begin{pyin}
wavenumber = np.arange(400, 4000, 1)
\end{pyin}
\begin{pyin}
sliced_wavenumber = wavenumber[1900 > wavenumber > 800]
\end{pyin}
\begin{multiout}
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    sliced_wavenumber = wavenumber[1900 > wavenumber > 800]
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
\end{multiout}
Trying 

\begin{pyin}
sliced_wavenumber = wavenumber[1900 > wavenumber and wavenumber > 800]
\end{pyin} yields the same result.

\First explains, ``You gave Python a mathematically reasonable statement, but since \texttt{wavenumber} is a Numpy array, Python evaluates one conditional and returns an array of equal size but containing only \texttt{True} and \texttt{False} values at each \cite{index}.  When you link two conditionals like this, Python interprets it as you looking for values that satisfy the first condition \textit{and} the second one, simultaneously, and implicitly inserts the \lstinline{and} \cite{operator} for you. Unfortunately, the \lstinline{and} operator is only designed to evaluate to a single \lstinline{True} or \lstinline{False} value, and cannot handle the \cite{vectorization} that operations between Numpy arrays typically rely on. Basically, it is asking ``are these arrays both true?'' rather than ``are the $i^{th}$ elements in each of these arrays both true?''  This obviously is a problem."

``What you really want to do is an ``element-wise'' logical operation, performing a logical operation on two corresponding elements in two different arrays to form a new array holding the result of that logical operation.  For this, use the `elementwise and' operator (`\lstinline{&}'):"

\begin{pyin}
sliced_wavenumber = wavenumber[(wavenumber > 800) & (wavenumber < 1900)]
\end{pyin}
``Basically, you use \lstinline{&} rather than the Python \lstinline{and} command and everything else works as expected.  Similarly, \lstinline{or} can be replaced by `\lstinline{|}'.\footnote{On a U.S. keyboard, you get this character by holding shift and pressing the button above the Enter key}  Note that \textit{the parentheses here are important to set the order of operations correctly}.  If you leave them out, the line of code will fail because it evaluates the operators in order from left to right and the expression \lstinline{wavenumber > 80 & wavenumber < 1900} will not work. This is because the statement is evaluated left to write and this is the equivalent of \lstinline{((wavenumber > 80) & wavenumber) < 1900}, which will reduce to comparing the \lstinline{True/False} values in an array produced by the first logical test to the numerical data contained in the \lstinline{wavenumber} array. This will either fail, or produce results that you do not expect."


% enumerate
\subsection{Accessing the number of times a loop has iterated using \texttt{enumerate()}}
\label{Accessing_the_number_of_times_a_loop_has_iterated_using_enumerate}

``We saw that we could \cite{iterate} through multiple iterable \cite{collection}s together using \lstinline{zip}.\footnote{See Chapter \ref{03DrawMol}.}  Sometimes you need to know what number iteration you are on (corresponding to an index of the collection).  Or, sometimes the arrays or lists that you want to iterate over are not the same length.  In either of these cases, there is a better solution: the \lstinline{enumerate()} function."

``The enumerate \cite{function} accepts an \cite{iterable} \cite{object} as a \cite{positional_argument} and \cite{return}s a \cite{tuple} of two values for each element in the object. The first value is the iteration number, and the second is the element at the index corresponding to the iteration number. These values can be \cite{unpacked} and assigned to two \cite{variable}s. In some sense then \lstinline{enumerate(mylist)} is equivalent to \lstinline{zip(range(0, len(mylist)), mylist)}. The behavior can be seen in the following:"

\begin{lstlisting}
for i, name in enumerate(["one", "two", "three"]):
    print(f"the index is {i} and the value at that index is {name}")
\end{lstlisting}
\begin{multiout}
the index is 0 and the value at that index is one
the index is 1 and the value at that index is two
the index is 2 and the value at that index is three
\end{multiout}

``This can be a very powerful way to keep track of where you are in a list."
% Also, you can use it to go through two lists of different lengths.

% \begin{lstlisting}
% count = [1, 2, 3, 4]
% name = ["one", "two", "three"]

% for i, n in enumerate(name):
%     print(f"The count is {count[i]} and the name is {n}.")
% \end{lstlisting}
% \begin{multiout}
% The count is 1 and the name is one.
% The count is 2 and the name is two.
% The count is 3 and the name is three.
% \end{multiout}
% Where we are using the index to ensure we are getting corresponding items between lists. If we had tried to use zip, we would have obtained an error, because the lists are different lengths.\footnote{There are other (and better) tools for doing this as well, which we will not explore in this book, such as \lstinline{zip_longest()}, from the itertools library.}



\subsection{Visualizing fit results using \texttt{codechembook.quickPlots.plotFit()}}
\label{Visualizing_fit_results_using_codechembook.quickPlots.plotFit}

You had previously created a figure with two subplots that plotted data and a fit to this data in the top plot, and a plot of the residuals (data - fit) in the bottom plot. This allowed you to easily visualize the fit and how it deviates from the data, but it was a fair amount of coding. 

This approach is generally useful enough that the \citett{codechembook} \cite{library} includes a \cite{function} to accomplish this quickly: \lstinline{fitPlot()}. To use this function, you simple provide a lmfit fit \cite{result_object} as the only \cite{positional_argument}, and this function will generate a plot with data and fit on top and residual on the bottom. There are also a few useful \cite{keyword_argument}s for this function:  
\begin{itemize}
    \item \lstinline{residual}.  The default value for this is \lstinline{False} and the default behavior is to plot only the top potion (data and fit). Passing a value of \lstinline{True} results in showing the residual below the data. If \lstinline{True} is used, then the data and fit subplot will be 4 times taller than the residual subplot---a behavior that makes it very easy to see the residual, even when it is small.  However, passing \lstinline{residual = 'scaled'} to the function will produce a plot in which the scale of the $y$-axis on the top and the bottom are the same, meaning that one can directly compare the residuals to the data. 
    \item \lstinline{components}. The default value for this is \lstinline{False} and the default behavior is to plot only the final fit line.  If this keyword is set to \lstinline{True}, then the plot will also include individual components of the model. For instance, if you had a model that was the sum of a line and a Gaussian profile, this will show the sum, as well as just the line and just the Gaussian.  This can be useful for understanding the behavior of your model. 
\end{itemize}
There are a few other keywords, and you can consult the codechembook library to find them, but for now, this is sufficient to solve your current problem. 



% reading documentation
\subsection{Learning to use libraries by reading documentation}
\label{Learning_to_use_libraries_by_reading_documentation}

"Alright, I have to run!'' \First says, grabbing their messenger bag. ``Remember, you know what you need to in order to read the documentation for \cite{lmfit}. Here is a hint, start by Googling what you already know: `lmfit linear model'."

Following \first's advice, you find a page titled `Built-in fitting models of the models module.' Just a short time ago, this would have sounded like nonsense to you, but now you know that a \cite{module} is just part of a \cite{library}, and so the page is describing how that part of the lmfit library works. Scrolling through this page, you see the linear model you have been using. And you also see a Gaussian model! The documentation as of the writing of this book is reproduced in Figure \ref{fig:CompFit:GaussianDoc}.

\begin{figure}[h]
    \centering
    \includegraphics[width = 4.1in]{ChapNarrative/05CompFit/figures/GaussianModel.png}
    \caption{Screenshot of the online documentation for the GaussianModel \cite{class} within the lmfit \cite{library}.}
    \label{fig:CompFit:GaussianDoc}
\end{figure}

Reading this documentation, you find that the model is actually a \cite{class}. You also find a discussion of the \cite{parameter}s used by the Gaussian model function. You find that they are all optional, but decide that you will want to supply the \lstinline{independent_vars} (the x-values), just as you did when using the linear model.  You also see other optional parameters, but aren't totally sure what they are used for now. What you don't see is anything about how to pass the parameters that you want to fit (which the model says are amplitude, center, and sigma).  
%The page uses the word '\cite{parameter}s,' for what we have been calling 'arguments.' A quick Google search reveals that there is a subtle difference: `parameters' are the name of things in the definition of a function, and `arguments' are what we call them when we pass them to functions. You file this information away so that you can better understand documentation when you are reading it, and so that you can win a pointless argument at your next cocktail party. 
So, next you need to find this, as well as how to combine models. 


\subsection{Combining \texttt{lmfit} models}
\label{Combining_lmfit_models}

Scrolling more on the same page with the documentation for the GaussianModel, you find a section called "Example 3: Fitting Multiple Peaks---and using Prefixes" (Figure \ref{fig:CompFit:ExampleDocs}). The first part sounds exactly like what you are wanting to do, and the second part references "prefixes," which you recall is one of the optional \cite{parameter}s of the Gaussian Model. Looking at the other built-in models described on the page, you see that "prefix" is an optional parameter for all of them. Looking over the text of the example, you find that you can take existing models and add them together to make new models!  Specifically, on line 24, you see: \lstinline{mod = gauss1 + gauss2 + exp_mod}, and you note that \lstinline{gauss1}, \lstinline{gauss2}, and \lstinline{exp_mod} are all variables that were given to calls of the built-in models. So they appear to be adding together models to make a combined model that has 2 Gaussians and an exponential background. 

\begin{figure}
    \centering
    \includegraphics[width = 5in]{ChapNarrative/05CompFit/figures/ExampleModel.png}
    \caption{Screenshot of the online documentation for the lmfit \cite{library}, showing an example of how different model \cite{object}s can be combined. }
    \label{fig:CompFit:ExampleDocs}
\end{figure}

You also notice that, while the \lstinline{guess()} \cite{method} of the \lstinline{ExponentialModel} was used to get initial \cite{parameter} values for that model (and store them in a variable), this was not done for the Gaussian models. Instead, it seems these were added directly to the same variable using what looks to be a \cite{method}: \lstinline{update()}. You think this is a method, because it follows the \cite{syntax} for using methods \lstinline{<object>.method()}. Given that this is holding parameters to be fit, you suspect this must be something like a `parameter object.'

You notice on the top of the web page that there is a link to a page called "Parameters" and so you click on that and find out that there is, indeed, a Parameters \cite{class} (Figure \ref{fig:CompFit:ParameterDocs} shows part of this documentation), which means there are objects associated with it.  Thus, the \lstinline{guess()} method created a parameter object, that \lstinline{update()} added to.

\begin{figure}
    \centering
    \includegraphics[width = 5in]{ChapNarrative/05CompFit/figures/Parameters.png}
    \caption{Screenshot of the online documentation of the Parameter \cite{class} for the lmfit \cite{library}.}
    \label{fig:CompFit:ParameterDocs}
\end{figure}

Looking at Example 3 more (Figure \ref{fig:CompFit:ExampleDocs}), you find that parameter values were supplied using the \lstinline{make_params()} method of the Gaussian model object. That is, first a Gaussian model object is created (and assigned to either the variable \lstinline{gauss1} or \lstinline{gauss2}) and then \lstinline{make_params()} was used on this to set values of the parameters to be fit (\lstinline{center}, \lstinline{sigma}, and \lstinline{amplitude}). 

Comparing this to the description of the \lstinline{GaussianModel} (Figure \ref{fig:CompFit:GaussianDoc}), you see that Gaussian model has three parameters: amplitude, center, and sigma, and that these are all used as the name of keyword arguments in the \lstinline{make_params()} method in the example (Figure \ref{fig:CompFit:ExampleDocs}). Moreover, each of these keyword arguments accepted a dictionary, where \lstinline{value} was always specified, and sometimes \lstinline{min} and \lstinline{max}. 

Going back to the parameter page (Figure \ref{fig:CompFit:ParameterDocs}) you decide the \cite{dictionary} was specifying the starting value (\lstinline{value}), the lower bound for the fit range (\lstinline{min}), and the maximum value for the fit range (\lstinline{max}).  You can see there are other arguments that could be supplied, such as if you want the parameter to vary at all (\lstinline{vary}), but for now, you think this is probably all you need. 

Reading this, you also realize that what \lstinline{make_params()} must be doing is taking the dictionary for each parameter, and then passing this to the \lstinline{pararmeter()} function to define each parameter. 

The last thing from the example (Figure \ref{fig:CompFit:ExampleDocs}) that you notice is that you can directly use the \lstinline{fit()} method on the composite model (assigned to the variable \lstinline{mod}). So, you now think you now understand how to set up and carry out the fit. 

There remains one last problem: you want to get the values of the parameters of the final fit---specifically the center positions and their uncertainties. You could go through the same process as above, but in the interest of space, we present the following information, assuming a usage like: \lstinline{result = <model>.fit()}, so that the final result is stored in a variable called "result":
\begin{itemize}
    \item The final parameter values are an \cite{attribute} of the fit result object. So we can get all parameter information using \lstinline{result.params}

    \item You can treat the above attribute like a dictionary, so if you want information on a single parameter, you can use \lstinline{result.params["<parameter name>"]}

    \item The above contains all the accessible information about that parameter, and you can access the value of the parameter and its uncertainties using the attributes \lstinline{value} and \lstinline{stderr}, respectively
\end{itemize}

With that information, you can sit down and both fit and extract the information from that fit. 





% CODE
\section{Code}
\setcounter{subsection}{-1}  % Start subsections from 0

You start by drawing up a flowchart for the process you want to code.

\begin{figure}[H]
    \centering
    \includegraphics[]{ChapNarrative/05CompFit/figures/FlowChartCh6.pdf}
    \caption{Flowchart for the code that extracts spectral data from a csv file, builds up a model of this spectrum using multiple Gaussian line shapes and a linear background, and then fits this model to the data, displaying the results. }
    \label{fig:CompFit:ActivityDiagram}
\end{figure}


\lstinputlisting[language=Python]{../../Distribution/Code by Chapter/Ch06 - Fitting Data with Multiple Components/DecomposeSpectrum.py}

You run this code, select the file 0.001.csv from the folder Titration/UVvis in your data directory, and wait for about a second.  It produces the output shown in Figure \ref{fig:CompFit:finalPlot}.

\begin{figure}[H]
    \centering
    \includegraphics[width = 3in]{ChapNarrative/05CompFit/figures/comFitPlot.png}
    \caption{A plot of the area of the spectrum saved as 0.001.csv, including indications of the background and the three Gaussian components of the fit and their centers.  The key new concepts demonstrated here are the construction of a composite fit model and the use of methods and attributes of the \lstinline{lmfit} \lstinline{ModelResult} object.}
    \label{fig:CompFit:finalPlot}
\end{figure}


\subsection{Line-by-line explanation}

\begin{footnotesize}
\begin{itemize}
    \item [8--11:] We import the libraries (or parts of libraries) we will be using. New to this chapter are: \lstinline{GaussianModel()} (used to fit Gaussian lineshapes to data), \lstinline{plotFit()} (which we will use to plot the final result of our fitting), and \lstinline{quickPopupMessage()} (used to generate messages to remind the user of what they need to do next). 

    \item[14:] We use the function \lstinline{quickPopupMessage()} to tell the user what we want them to do. This will bring up a native operating system message box that shows the message. Including such helpful hints can be nice for users who might wait a long time between using the code, and so might forget what they are supposed to be doing. In this case, we want them to select the 0.001.csv file, since that is the data that this fit is going to be set up to handle. If you want, you can try a different file, and see what happens.  By the time you get to the final titration point, this code will fail, since the initial guesses and bounds provided for the parameters below will not work for that data. 

    \item [15--16:] We identify the Path to the data we wish to fit, using the \lstinline{quickOpenFilename()} function to specify that we can only open up csv files.  As a reminder, this will return a pathlib Path \cite{object}. 

    \item [18:] Using Numpy's \lstinline{genfromtxt()} function, we read the data file, and return to \cite{Numpy_array}s that have our $x$ (wavelength) and $y$ (absorbance) values of interest. 

    \item [21:] We define the lower and upper limits for our fitting.  This is the range of wavelengths we want to consider in our fitting.

    \item [24:] We mask the wavelength array such that we only get values that are greater than or equal to our lower limit and less than our upper limit, and assign the resulting sliced Numpy array to a new variable. 

    \item [25:] We can use the same mask expression and apply it to the absorbance values. Thus, we use the logic test on the wavelength arrays (which is the only place it makes sense, given the limits we specify).  This logic test tells us which indices to include.  But, since we want the same indices kept in the absorbance array, we can just apply the same mask to it. The end result is that we have a trimmed absorbance that matches the trimmed wavelength Numpy array. 

    \item [28:] Our final model is going to be the sum of three Gaussians and a linear background.  We will see below that we can simply add together model objects.  So, to start with, we create a linear model object, just as we did in the last chapter. There is one difference: we now have used the \lstinline{prefix} \ul{keyword argument}. This will append the string that is provided to the front of all parameters. Though not truly needed quite yet, we will find this behavior useful as we build up the multiple Gaussian contributions below. 

    \item [29:] Also, just as we did in the last chapter, we have Python provide guesses for the parameters of the linear model. 

    \item [30:] We are going to have three Gaussian components. It is possible to ask Python to guess the starting parameters, just as we did for the linear model. However, when we have three closely overlapping Gaussians the guesses may not be good, and it is useful to learn how to provide guesses for the parameters manually.  Here we create a list that contains the guesses we will use for the center positions of the three Guassians. This will allow us to iterate through them. 

    \item [31:] We will use a single guess for all three widths, which we assign to a variable.

    \item [32:] This variable holds a number we will use as a guess for the amplitude for all three Gaussians.  Though we don't anticipate all three Gaussians to be the same amplitude, starting with the same amplitude will be a good enough guess to start the fitting. 

    \item [33--38] This is the \citett{for} \cite{loop} that will allow us to iterate though our guesses for the center of the Gaussians.  We also use \lstinline{enumerate()} function in order keep track of the interation number, which also happens to correspond to the index for each element we have retrieved within the loop. Basically, for each Gaussian, we'll make a temporary model object with only one Gaussian model, set its parameters, and then add it to the final composite model.
    \begin{itemize}
        \item [34:] We produce a Gaussian model object. We again use the \lstinline{prefix} keyword argument to supply a string that is prepended on all parameter names. We use an f-string to provide a prefix that starts with a g (for Gausian) and then has a number associated with it, based on the iteration we are on within the loop.  We add 1 to this, so that we are going to number our Gaussians g1, g2, and g3.

        \item [35--37:] We explicitly add parameters to our Gaussian model. We do this by nesting the \lstinline{make_params()} method of the model object inside the \lstinline{update()} method of the parameter object we created in line 29. Within the \lstinline{make_params()} method, we specify the name of the parameter, and then several other items within a dictionary. We have to at least provide a value for the starting guess, which we do using the \lstinline{value} key. 
        \begin{itemize}
            \item [35:] We assign the values for the center of the Gaussian distribution, using the value we took from the \lstinline{c_guesses} list.
            
            \item [36:] We set up the \lstinline{amplitude} parameter, providing a starting value, but also another keyword: \lstinline{min}. This allows us to specify a value below which the parameter is not allowed to go, when fitting. The starting guess must, of course, be higher than this value. Sometimes it is quite useful to set such limits, because the fitting procedure will vary the parameter value until it fits the data best, without regard to if it makes sense. Setting the lower limit of the amplitude allows us to avoid negative amplitudes, which we do not expect in our UV-vis experiment. 
            
            \item [37:] We assign the value for the \lstinline{sigma} parameter (the width of the distribution) using the same approach.  However, now we provide \lstinline{min} and \lstinline{max}, which specify the lower and upper limits for this parameter, respectively. We are telling the fitting program that it can vary the value of \lstinline{sigma}, but it cannot ever be smaller than 0 or greater than 25. While there might be a good physical reason to not allow the width of the feature to be less than 0, there is not really an excellent physical justification for this upper limit.  Instead, we are setting this, because our chemical intuition tells us that we might expect all three bands to have similar widths, and this prevents one band from having very different widths from the other two. When restricting parameters to ranges, it is important to understand this may result in a non-optimal fit, so use such constraints with caution. Good practice would be to remove the upper limit after the first fit, re-run the fit with the best-fitting values as the initial values to see if you get different results, and assess how different they are.
            
        \end{itemize}
        It is important to realize that each of the parameters we create above will be prepended with the prefix specified in line 34, as it is added to the parameter object. This will be important later, as we extract information from the fit result object.

        \item [38:] We now add the Gaussian model object we created to the final model object and then assign this to the final model object.  In this way, we are ending each \lstinline{for} loop by adding the newly created Gaussian model to our existing model. By the time all iterations are completed, our final model will consist of 4 components: 1 linear and 3 Gaussian. 
    \end{itemize}
    \item [41:] We use the \lstinline{fit()} method of the final model object to fit data.  As we did last chapter, we supply the $y$-values, parameters, and $x$-values to this function, and pass the result of the fit to a variable. 

    \item [44:] We use the \lstinline{plotFit()} function to plot the results of our fit. We supply the result as a \cite{positional argument} and then specify several keyword arguments that (in turn) tell the function that we want the residuals to be plotted, that we want to show all components of the model (in this case, liner and 3 Gaussians), the title of the $x$-axis, the title of the $y$-axis, and we also tell the function that it doesn't need to show the results.  The default behavior would have output the results, but we are going to annotate the figure a bit before we show it.  The \lstinline{plotFit()} function also returns the Plotly Figure object it creates, so we can assign this to a variable so that we can continue to modify it. 

    \item [47--51:] We are going to include annotations on the plot, specifically the final center positions of the Gaussians and their uncertainties. To do so, we need access to the values of the individual components of the model, which are instances of Parameter objects that are entries in the dictionary stored in \lstinline{params} object.  Since the \cite{keys} in this dictionary are numbered according to which Gaussian we are working with, it is convenient to get numbers for them.  We can generate them using the \lstinline{range()} function.  The Gaussian components start at 1, we start at 1. Since the range function is \ul{exclusive} for the final number, so we add 1 to the length of the \lstinline{c_guesses}, which corresponds to the number of Gaussian components we have. Iterating through the prefixes defined with the \lstinline{range()} function will provide the numbers we need to access the correct dictionary entries. Once we have the corresponding Parameter object, we can use its attributes to get its value (\lstinline{value}) and uncertainty (\lstinline{stderr}).
    

    \begin{itemize}
        \item [48:] We access the values and standard errors (uncertainties) of our parameters in turn, using the numbers that we generated in the \lstinline{range()} function above.  We include these in an f-string that is passed to the text keyword argument for use in the annotation. 

        \item [49 \& 50:] Add the annotation to the Plotly figure to specify the $x$ and $y$ positions of the annotation.
        
        \item [51:] Finally, we state that we don't want an arrow drawn for these annotations.

    \end{itemize}
    
    \item [53:] We show the final assembled plot.

\end{itemize}
\end{footnotesize}

\hrule
\input{Exercises/Chapter 06 Fitting complex analytical models to data}
\hrule

\end{document}
