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

\chapter{Automating Repetitive Calculations}
\label{Chap:N:00bArrays}
% reaction optimization


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

\input{prereqs/00bArrays_prereqs} %prereqs go here

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

You are still working to plan out the experiment in the 24 well plate, as shown below.

\begin{figure}[H]
    \centering
    \includegraphics{ChapNarrative/00aExpDes/Figures/EmptyWellPlate.png}
    \caption{Schematic representation of a 24 well plate, with the desired changes in chemical composition noted. This is the same diagram as shown in Figure \ref{fig:ExpDes:WellPlate}. In the last chapter, we learned how to calculate the conditions for any single well.  In this chapter, we will learn how to calculate for all wells at once. }
    \label{fig:Arrays:WellPlate}
\end{figure}


At this point, you have written a function so that you can calculate the volumes of each stock solution needed for a single well using single command in Python.  However, you still need to input the concentrations of HCl and ionic strengths for each well, and you have to run the function once for every well.  This might not be a problem for a single 24-well plate, but you think that doing this multiple times (or extending to a 384-well plate) might still be a big problem.  What you really want is to calculate all of the wells at once, regardless of the number of wells or the shape of the plate, which \first promised you would accomplish over lunch today!  


%You recently developed a new aqueous catalyst that relies upon a proton transfer step.  Obviously, the efficacy of the catalyst will depend on the $pH$ of the solution, relative to the $pK_a$ of the catalyst.  However, you are also concerned that the $pK_a$ of the catalyst will, in turn, depend on the ionic strength of the solution. This suggests you need to run multiple titration experiments, each  conducted under a different (but constant) ionic strength. 

%While you are excited to run these experiments and lay the groundwork for your first paper, it will take some work. Fortunately, your lab has a 24 plate reader that can check $pH$ of the solutions.  This means that you can envision the 4 rows as representing different constant ionic strengths, and the 6 wells in each row as comprising a titration experiment. Deciding you will titrate with HCl, you produce the following sketch of the experiment:

% \begin{center}
% \includegraphics[width = 2in]{ChapNarrative/00bArrays/Figures/24 well plate_labeled.png}
% \end{center}
   
% Though the plate reader will save you time, it also  means that you need to calculate out all 24 conditions and keep track of them. You mention to \first that you are excited to do the experiment, but are not looking forward to doing these tedious calculations and double checking them. This is particularly true since your advisor has a habit of changing their mind regarding the exact limits of reaction---and each time the limits change you will need to recalculate the conditions. 
 
% You know that you could set up a spreadsheet to do this in Microsoft Excel, but the fact that you wanted to maintain a constant volume across the experiments means that the formulas in Excel become cumbersome. 

% \First mentions that this really hasn't been a problem for them, since they learned to code.  Intrigued, you ask what they means. They say that coding is a perfect tool for performing such repetitive tasks and can both reduce mistakes \textit{and} improve your life in lab. They say that you could write a simple program that would calculate everything you need and could adapt to whatever limits your advisor decides they like that day. They also mention that they are free over lunch, so if you wanted to take an hour, they could give you a quick introduction to coding and get you to the point that you can generate these experimental conditions.  They points out that the very \textit{first} time you implement this solution might take longer than calculating by hand, but that you will have generated a general tool that can be used again and again.  So, the more frequently your advisor changes their mind, the more time you will save in the long run. 

% Knowing the fickleness of your advisor, you comment this could get \textit{years} of your life back. You agree to meet over lunch, and \first tells you to bring your laptop with you. 

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

\begin{wrapfigure}{r}{.5\textwidth}
    \begin{mdframed}
        \textbf{Installing new libraries in Python} \\
        \noindent At the time of this writing, there are two dominant means by which to install new libraries in Python: \textit{pip} and \textit{conda}. The most important thing to know is that you should \textbf{not} mix these two.  So, if you started using pip, please continue using it.  If you started using conda or Anaconda, then continue to use it. One thing to note is that conda installations can be \textit{slow}, sometimes taking hours. If you wish to understand more, please visit the website to read about the reasons we selected conda.
    \end{mdframed}
\end{wrapfigure}

When you arrive at the break room, \first is waiting and, as you sit down, says, ``One nice thing about Python is that the \cite{function} that we wrote yesterday to calculate what to do for a single well in the plate can be just as easily used to calculate any number of wells.  Just like we stored single values in variables, we can store many values as a collection in a single variable, and Python will perform the same operation on each value without you having to tell it to do it.  Today, we will see how this works, and then write a simple program that can handle any number of wells and any range of concentrations.



``In order to accomplish what we want to do, we need to use capabilities beyond standard Python.  In fact, what you can do easily with the core Python programming language is relatively limited.  Fortunately, there is a huge range of other code written in Python that you can access.  This significantly expands the things that we can do without having to reinvent the wheel.  I'll show you how we can use these additional capabilities to take the function you already made and simply adapt it to work with any size well plate."

%extending
\subsection{Importing libraries to add capabilities to Python}
\label{Importing_libraries_to_add_capabilities_to_Python}
%
% No need for scipi or other constants. 
% operate on an single array for gradients in that direction
%

``To understand why we might want to expand the capabilities Python consider this: the base Python knows how to calculate exponents, but it doesn't know how to do logarithms. So, if we want to calculate logarithms, we need to either write a function to do this, or add that capability from some extermal source.''  

\First says the latter can be done by importing a \cite{library} and then using the capabilities of this library. You will use \cite{Numpy}\footnote{If you would like more information beyond what is in this book, check out 
\href{https://numpy.org/devdocs/user/absolute_beginners.html}{https://numpy.org/devdocs/user/absolute\_beginners.html}
} which is the dominant library for doing the sorts of mathematics that are common in science. They show you how to quickly install Numpy to work with your Python installation.\footnote{See the companion website for this book (\website) for detailed instructions to install Numpy and all the other libraries you will use in this book.}  Also, since we have just seen that a feature of coding is making it so that we don't have to write out long lines of code over and over, we will also import the Numpy library and assign it a shorter name, called an \cite{alias}.  In this case, we will assign it the alias \lstinline{np}. 

\begin{pyin}
import numpy as np
\end{pyin}

We refer to the capabilities of this library by using the alias, adding a `.', and then naming the capability we wish to use. 

\begin{pyin}
np.log(10)
\end{pyin}
\begin{pyout}
2.302585092994046
\end{pyout}

From this value, you can see that \lstinline{log()} is a function in the Numpy library and calling \lstinline{np.log(<number>)} performs a natural logarithm. \First says that the base can be changed, but we will not need to do so right now. 

\begin{mdframed}
        \textbf{Numpy 1 vs. Numpy 2}
        
        \noindent At the time this book was written, Numpy 2 had just been released.  This book was written using Numpy 1, and Anaconda currently still ships Numpy 1, but users of \texttt{pip} probably have Numpy 2, and Anaconda will eventually update as well.  For our use in this book, there is little difference between the two versions.  One annoyance is that Numpy2 will print the results of expression evaluations in a way that explicitly denotes the numerical \cite{data_type} along with the value: \lstinline{np.float64(3.14)} rather than simply \lstinline{3.14}.  Here, \lstinline{float64} indicates a 64-bit floating point number.  To disable this behavior, you can run the command \lstinline{np.set_printoptions(legacy='1.25')} at the beginning of your Python session or script (but after importing Numpy).
\end{mdframed}

\subsection{Operating on collections of data efficiently with Numpy arrays}
\label{Operating_on_collections_of_data_efficiently_with_Numpy_arrays}

Another advantage of \cite{Numpy} is that this library includes a \cite{data_type} known as \cite{Numpy_array}. The Numpy array holds a collection of values that can all be acted on using a single \cite{operator}. For instance, imagine you had three different samples of water, with masses of 1 g, 4.3121 g, and 3 g. If you wanted to calculate the number of molecules for each, you \textit{could} do this three separate times, or you can place the masses in a Numpy array, and then do math on the array.
\begin{pyin}
MW_water = 18.01528
\end{pyin}
\begin{pyin}
N_A = 6.022e23
\end{pyin}
\begin{pyin}
masses = np.array([3, 4.3121, 1]) # units of g
\end{pyin}

\begin{pyin}
molecules = masses / MW_water * N_A
\end{pyin}

\begin{pyin}
molecules
\end{pyin}

\begin{pyout}
array([1.00281539e+23, 1.44141341e+23, 3.34271796e+22])
\end{pyout}
``In the 4$^{th}$, we solved three different math problems at once! In this example, we have first put the values in a Numpy array and \cite{assign}ed it to the \cite{variable} \lstinline{masses}.  This is called array \cite{creation} or array \cite{instantiation}. The latter is term is used because we have created an \cite{instance} of a Numpy array. You will notice that you didn't need to import Numpy each time. Once a library is imported (as was done in Section \ref{Importing_libraries_to_add_capabilities_to_Python}, you can keep using it."\footnote{This was a bit more explicitly addressed in the final box at the end of Chapter \ref{Chap:N:00ExpDes}.}

``Next, we do some math on the array, using its variable name.  We call this `operating on the array.' We assign the result of these operations to a new variable. Then, we ask for the value of the new variable, by typing its name. In the output, we can see that the new variable is also an array, and that it contains the results associated with running the calculations on each of the values in the array. Rather than needing to specify that the operations should be done on each component, a behavior of arrays is that the operations are naturally applied to all elements. In this case, we have one array variable (\lstinline{masses}) and two variables that have only single values (\lstinline{MW_water} and \lstinline{N_A}).  When Python evaluates this \cite{expression}, it breaks the problem up into three parts, each corresponding with one element of the array, and yields a new array that is the same size.  Variables that only hold single values are just applied the same in each part.  This behavior, where single-value variables are applied the same to each element of the array, is an example of the concept of \acite{broadcasting}."

``Of course, one can \cite{pass} the array as an \cite{argument} to the  \lstinline{print()} \cite{function}:"
\begin{pyin}
print(molecules)

\end{pyin}
\begin{pyout}
[1.00281539e+23 1.44141341e+23 3.34271796e+22]
\end{pyout}
``The difference in the output is that we no longer have the explicit indication this is an array, and the numbers are no longer separated by commas.  Using the \lstinline{print()} function simply makes the output more readable."

\begin{mdframed}
    \textbf{Why not Pandas?}
    
    \noindent If you spend anytime searching the internet regarding working with data in Python, you will encounter the Pandas library. This is an extremely popular library for data science. However, we do not use it here for two reasons. First, it is not needed, and so we did not think that the added complexity of working with Pandas was worth introducing. Second, Pandas is written for working with data that has a particular structure---a structure that is not always met by experimental chemistry data. Thus, we think the flexibility and relative simplicity of using only Numpy is worth missing out on the few conveniences of Pandas. Of course, as you learn Python, you can also elect to use it in your own workflows---but neither of the authors of this book currently use Pandas regularly.
\end{mdframed}

``When expressions contain multiple arrays that are the same size, operations involving them are performed in an element by element manner. In other words, if we multiply two arrays that are the same size, the result will be a new array in which the first element is the product of the first two elements of the existing arrays, the second is the product of the second two, and so on.  This process is called \cite{vectorization}.\footnote{Even though this term suggests a relationship to mathematical vectors, arrays are not vectors!} For instance, imagine we wanted to know the difference in the number of molecules between a sample that is 100\% \ce{D2O} and the `normal' sample.  We can do the following:"
\begin{pyin}
MW_d2o = 20.0276 # g/mol
\end{pyin}
\begin{pyin}
molecules_d2o = masses / MW_d2o * N_A
\end{pyin}
\begin{pyin}
molecules_d2o - molecules
\end{pyin}
\begin{pyout}
array([-1.00760224e+22, -1.44829387e+22, -3.35867413e+21])
\end{pyout}
The second input created a new array that had the molecules of \ce{D2O} that was the same size as the \lstinline{molecules} array and the third subtracted the numbers from the \lstinline{molecules} array from this one, on an array-element by array-element basis.

``The last thing that you need know is that, while you have just seen that you can use operators like \lstinline{+, -, *, /, **} on arrays, not everything works this way.  For instance, if we try to round our array, we need to use a rounding function in Numpy instead of the normal Python one that we used before:
\begin{pyin}
np.round(123.456, decimals = 1)
\end{pyin}
\begin{pyout}
123.5   
\end{pyout}
``For \lstinline{np.round()}, you see that the keyword for digits to round is \lstinline{decimals} rather than \lstinline{ndigits}.  If we try to use the `normal' \lstinline{round()} function, we get this:"
\begin{pyin}
round(123.456, decimals = 1)
\end{pyin}
\begin{multiout}
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    round(123.456, decimals=1)
TypeError: 'decimals' is an invalid keyword argument for round()
\end{multiout}
``Let's talk about this error."



\subsection{Interpreting Python error messages to fix mistakes}
\label{Interpreting_Python_error_messages_to_fix_mistakes}

``Error messages in any programming language are irritating, but they usually contain helpful information that you can use to figure out what went wrong and how to fix it.  A few things to know:
\begin{itemize}
    \item When Python encounters an error, the line of code that it was running when the error happened does not finish and, if it was supposed to assign a value to a \cite{variable}, that doesn't happen.
    
    \item If the error happens inside of a \cite{function}, it just stops.  The rest of the function does not execute, it does not return a value, no other code executes, and nothing else happens.
    
    \item Just because you don't get an error message doesn't mean that your code ran as you expected it, or that your answers are correct.  It just means that nothing literally stopped Python from being able to process the code.
\end{itemize}
There are three basic pieces of information in an error message:
\begin{enumerate}
    \item The file in which you can find the code that was running when the error happened.  In our case, the ``file" was actually the IDLE Shell (\lstinline{<pyshell#8>}).
     \item The actual line of code that was running when the error happened.  Here, the line of code was the one that we tried to run containing the \lstinline{round()} function.
     \item A description of the error including a standard error type (here, a ``TypeError," which denotes that an operation was being done on a \cite{data_type} that was not supported by the operation.
\end{enumerate}
The first two parts are just there to help you figure out what was happening when the error occurred.  Sometimes, the problem isn't specific to the code that was executing, but rather is caused by some previous code that executed fine but set up a situation in which the current line could not run correctly.  The last part will help you understand what the problem was.  Python has standard types of errors that you can look up when you come across them.  In fact, just copying the last two lines of the error message and searching (e.g., using Google or AI tools like ChatGPT) will often get you the solution to your problem quickly!"





``Let's look at another example of an error. We know that, since Numpy arrays are vectorized, we can subtract one array from another, and it will do this on an element-by-element basis. However, you have to ensure that the lengths of the arrays are the same, or you will get an error. For instance:
\begin{pyin}
molecules - np.array([1, 1, 1, 1]) # trying to subtract 1 from each element
\end{pyin}
\begin{multiout}
Traceback (most recent call last):

  Cell In[4], line 6
    molecules - np.array([1, 1, 1, 1])

ValueError: operands could not be broadcast together with shapes (3,) (4,) 
\end{multiout}
In this case, Python is telling us that we cannot \cite{broadcast} or vectorize these arrays, because they do not have the correct \cite{shape}s that would allow this. The error is a ValueError, which usually signifies that some variable is the right data type but has some other property that makes the operation unable to finish.  In this case, the arrays are different lengths, so we could neither broadcast nor vectorize them and Python didn't know what else to do."

``In both of these cases, the problem was with the line of code that we wrote.  Most of the time, as you write more complex code, the error will actually occur in some function that is called by some other function that is called by some other function.  When that occurs, the \cite{traceback} will be very helpful because it will let you track back through the nested series of functions that got from the main code to the line that actually caused the issue."

\subsection{Structuring data using arrays of arrays}
\label{Structuring_data_using_arrays_of_arrays}

``One convenient thing about  \cite{Numpy_array}s is that one array can hold other arrays. This is called \cite{nesting}.  For instance, we can have an array that holds 4 arrays, each of which hold 6 values. We call the arrays holding the other 4 arrays the \cite{outer_array} and the 4 arrays the 
\cite{inner_array}s.''

\begin{pyin}
array1 = np.array([ 1,  2,  3,  4,  5,  6])
\end{pyin}

\begin{pyin}
array2 = np.array([ 2,  4,  6,  8, 10, 12])
\end{pyin}

\begin{pyin}
array3 = np.array([ 3,  6,  9, 12, 15, 18])
\end{pyin}

\begin{pyin}
array4 = np.array([ 4,  8, 12, 16, 20, 24])
\end{pyin}

\begin{pyin}
outer_array = np.array([array1, array2, array3, array4])
\end{pyin}

\begin{pyin}
outer_array
\end{pyin}

\begin{multiout}
array([[ 1,  2,  3,  4,  5,  6],
       [ 2,  4,  6,  8, 10, 12],
       [ 3,  6,  9, 12, 15, 18],
       [ 4,  8, 12, 16, 20, 24]])
\end{multiout}

\begin{pyin}
outer_array3 = outer_array * 3
\end{pyin}

\begin{pyin}
outer_array3
\end{pyin}

\begin{multiout}
array([[ 3,  6,  9, 12, 15, 18],
       [ 6, 12, 18, 24, 30, 36],
       [ 9, 18, 27, 36, 45, 54],
       [12, 24, 36, 48, 60, 72]])
\end{multiout}


From this example, though we operated on the outer array you can see that the \cite{broadcast}ing applies to all inner arrays, which is convenient for doing math on all the arrays. 

This sort of behavior happened because we were operating on an array using a single value. However, as we saw in Section \ref{Operating_on_collections_of_data_efficiently_with_Numpy_arrays}, there is another option: we can apply an operation using another array of exactly the same \cite{shape}---where shape refers to the size of the inner and outer arrays. In our case, we say the array \lstinline{outer_array3} is of [4, 6], meaning that the outer array has 4 elements, each of which has 6 elements. If we choose another array that has this same dimensions, we can perform operations with it, and it will match up the corresponding elements of the array.  

For instance, what if we subtracted our array from itself?

\begin{pyin}
outer_array3 - outer_array3
\end{pyin}
\begin{multiout}
array([[0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0]])
\end{multiout}

You can see that each element has been subtracted from itself, which is why the result is all zeros. 

At this point \first smiles at you. "This [4, 6] array matches the format of 24 well plate we have been considering" They point out the sketch you made earlier (Figure \ref{fig:Arrays:WellPlate}). ``So, if we can figure out a way to map our calculations onto these arrays, we will solve our problem!  We are almost there, but it is going to be convenient to know a few other things about using Numpy arrays."




\subsection{Generating arrays of all ones or zeros Numpy}
\label{Generating_arrays_of_all_ones_or_zeros_Numpy}

``It can be irritating to have to make array \cite{literal}s all the time.  Fortunately, Numpy includes a bunch of functions that create \cite{Numpy_array}s for us.  You will often want to make arrays in which each element is 1, or arrays in which each element is 0.  There are several functions that do this automatically for us.  Let's take making 1s as example.  If we know the size of the array we want, we can use \lstinline{numpy.ones()} to make it.  You just have to tell the function what size to make the array.  If you want a 1D array of 6 elements, just pass it the argument 6."
\begin{pyin}
np.ones(6)
\end{pyin}
\begin{pyout}
array([1., 1., 1., 1., 1., 1.])
\end{pyout}
``If you want a 2D array like your well plate, 4 $\times$ 6, then you pass the dimensions of the array in parentheses:"
\begin{pyin}
np.ones((4,6))
\end{pyin}
\begin{pyout}
array([[1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1.]])
\end{pyout}
``The parentheses are important because the \lstinline{ones()} function is only expecting one total argument, and it will interpret an argument having two dimensions in parentheses as a single argument specifying (rows, columns).

``Sometimes, we either don't know or are too lazy\footnote{This is not necessarily a pejorative. In fact, one of the main motivations for learning to code is that it automates doing stuff for you... so a major motivation is trying to be `lazy.'} to define the dimensions of the array.  In that case, there's another function, \lstinline{numpy.ones_like()}, that will take as an argument another array, and produce an array with the same dimensions but ones for all elements.  It doesn't actually change the array that you use as the argument, it just uses it as a template and makes a new array from it.  So, we could also make an array of all ones that is the same size as our \lstinline{outer_array} from before like this:"
\begin{pyin}
np.ones_like(outer_array)
\end{pyin}
\begin{pyout}
array([[1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1.]])
\end{pyout}
``Either way gets us the same array, but sometimes one will be more convenient than the other.  For arrays of all zeros, things work the same way, but the functions are \lstinline{numpy.zeros()} and \lstinline{numpy.zeros_like()}."





\subsection{Generating an array of equally spaced numbers using Numpy}
\label{Generating_an_array_of_equally_spaced_numbers_using_Numpy}

``Sometimes you will want to make \cite{Numpy_array}s that have equally spaced numbers in them, and Numpy has shortcuts for this, too.  The two main ones you will use are \lstinline{linspace()} and \lstinline{arange()}.  They are pretty similar, but you use \lstinline{linspace()} when you know how many elements need to be in the array, and \lstinline{arange()} when you know what the spacing of the elements should be.  In our case, we'll be using \lstinline{linspace()}.  The arguments you need to pass to it are, in order, the value of the first element, the value of the last element, and the number of elements.  For instance:"
\begin{pyin}
np.linspace(3.14, 100, 11)
\end{pyin}
\begin{pyout}
array([ 3.14 , 12.826, 22.512, 32.198, 41.884, 51.57, 61.256, 70.942,  80.628, 90.314, 100.])
\end{pyout}
``Here you can see we have 11 equally spaced values, starting at the first (3.14) and ending at the last (100).  So, both the upper and lower limit are \cite{inclusive}. Finally, while these \cite{argument}s can involve either whole numbers or numbers with decimal places, \lstinline{linespace()} always returns a \lstinline{numpy array} of numbers with decimal places.\footnote{This is true even if only integers are used and the requested values could be represented at integers.  If you want to see, try \lstinline{np.linspace(1, 11, 11).}}" This is important, because numbers with decimal places are the ones most likely to encounter issues with \cite{numerical_precision}, as discussed in the last chapter.''\footnote{One other thing to note here is that the output had a fancy curly arrow at the end of the first line, and one at the start of the second.  These arrows indicate that the line was too long to fit on the page, so the output was `wrapped' onto a line. This arrow is not part of the Python output, but is part of the formatting of this specific book, so you will not have these arrows in your outputs.}

``The function \lstinline{arange()} is pretty similar to \lstinline{linspace()}, but there is one main difference: the stop value is not inclusive, meaning that your array's last element will always be less than the stop value.  So, if we wanted to make an array that goes from 0 to 1 with 0.1 spacing, we would have to do something like:"
\begin{pyin}
np.arange(0, 1.1, 0.1)
\end{pyin}
\begin{pyout}
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
\end{pyout}




\subsection{Planning a multi-step solution to a complex problem}
\label{Planning_a_multi-step_solution_to_a_complex_problem}

% 1mL per well
% use numpy.meshgrid, print three out... volumes and amounts for all components 

``Okay, we're ready to solve our original motivating problem!  What we want to do is to define the first and last values for HCl concentration and ionic strength, then use Numpy to make \cite{Numpy_array}s based round this for us.  Then, we can reuse your \cite{function} from earlier (the end of the last chapter) to operate on each element in the arrays simultaneously, and display the results as text.  This way, you will only have to do one calculation, and you'll get the volumes for all of the wells in the blink of an eye."

``Start by determining the range of parameters you want to explore, the final conditions you wish to have, and starting materials you are working with."

%You say you decided to use 1 $\mu$M catalyst and 0.1 M substrate. You decide to explore salt concentrations that run from 0 M to 0.1 M.  You also decide that you want to explore solvent mixtures that run from 100\% \ce{CH2Cl2} to a 1:1 mixture of THF and \ce{CH2Cl2}. Because you will \textit{always} have some CH2Cl2 around, you can prepare stock solutions of catalyst and salt in this solvent. You can prepare a stock solution of the catalyst such that you will need to add only a very small amount of solvent (e.g., 10 $\mu$L)---enough that you are going to ignore that volume in your calculations. The smallest amount of \ce{CH2Cl2} you will have is 0.5mL, and the largest salt concentration you will have is 0.1mM, so you need a stock solution of at least 0.2mM. This means that, in total, you will have three liquids to add to a well: pure TFH, pure \ce{CH2Cl2}, and 0.2mM TBAH in \ce{CH2Cl2}.  The problem you have is to determine the amounts of each to add. 

Like before, you want each well to have 1 mL final volume.  You want to use a constant catalyst concentration of 0.01 M for the titrations, and you have a 0.1 M stock catalyst solution, a 6 M stock HCl solution, and a 3 M stock NaCl solution. For ionic strengths, the catalyst has a single protonation site and you plan your titration to run to 1 equivalent of acid added, so the lowest possible ionic strength is going to be 0.02 M. You want to run up to a strength of 0.2 M. Finally, you say you want the 4 different titrations to be at equally spaced ionic strengths. 

``This, hopefully, makes sense.  However, it can be a bit cumbersome to say or read through.  There are other tools that can help us plan out a solution. For instance, many people find flowcharts to be a helpful graphically depiction of what they want to do in code, so they can outline the solution in a flowchart, and then write out the code to accomplish it. Let's put together a flowchart to guide us on this solution."

\begin{figure}[H]
    \centering
    \includegraphics[]{ChapNarrative/00bArrays/Figures/FlowChartCh1.pdf}
    \caption{Flowchart of the approach to calculating the concentrations for the 24 well plate. It can be useful to try to sketch out the steps associated with a problem in this manner, to help envision the way the code needs to work. The ability to take the solution for a single well (as seen in the last chapter) and apply it to the entire plate relies on the use of \cite{Numpy_array}s.}
    \label{fig:Arrays:ActionDiagram}
\end{figure}


``First, let's create arrays holding [HCl] and ionic strengths that we will want to use for each well.  For [HCl], we want to span 0 to 1 equivalents of catalyst, so we can define"
\begin{pyin}
innerMHCl = np.linspace(0, 1, 6) * 0.01 # scale the array by [catalyst]
\end{pyin}
``Then, we can make the 2D array like this:"
\begin{pyin}
MHCl = np.array([innerMHCl, innerMHCl, innerMHCl, innerMHCl])
\end{pyin}

``We can do the same thing for ionic strength.  Each of the titrations are under a constant ionic strength. Running from 0.02M  to 0.2 M.  We want them to be equally spaced, and so there should be three steps from 0.02 M to 0.2 M.

\begin{pyin}
starting_ionic = 0.02
\end{pyin}

\begin{pyin}
ending_ionic = 0.2
\end{pyin}

\begin{pyin}
ionic_step = (ending_ionic - starting_ionic) / 3
\end{pyin}

Here, we are dividing by 3, even though we have 4 rows, because there are three steps \textit{between} the rows. Using these \cite{variable}s, we can create an \texttt{array}.

\begin{pyin}
inner_ionic = np.array([0.02, 0.02, 0.02, 0.02, 0.02, 0.02])
\end{pyin}

\begin{pyin}
ionic = np.array([inner_ionic, inner_ionic + ionic_step, inner_ionic + 2*ionic_step, inner_ionic + 3*ionic_step])
\end{pyin}

A fair amount happened in this last step, so let's have a closer look at it.  We created an array, and put arrays inside of it.  But for each subsequent array we added, we also added a value to that array, using our step size we determined earlier.  The end result is that we ended up with an array that has the desired ionic strengths, which we can verify by asking for the value. 

\begin{pyin}
ionic
\end{pyin}

\begin{multiout}
array([[0.02, 0.02, 0.02, 0.02, 0.02, 0.02],
       [0.08, 0.08, 0.08, 0.08, 0.08, 0.08],
       [0.14, 0.14, 0.14, 0.14, 0.14, 0.14],
       [0.2 , 0.2 , 0.2 , 0.2 , 0.2 , 0.2 ]])
\end{multiout}

``Lastly, we need to make an array to hold the concentration of the catalyst in each well. We have a couple of options: we could define a 4 by 6 array with all ones, or we  can use \lstinline{np.ones_like()} to create an array of ones that is the same shape as another array. If we base this array on, say, the HCl concentrations, then it will always automatically be the right size in case our well plate changes.  So, we can just type:"
\lstinline{vol_final} with"
\begin{pyin}
Mcat = .01 * np.ones_like(MHCl) # M
\end{pyin}

``Now, you can use these three \cite{variable}s that hold arrays as arguments to our \lstinline{calcConc()} function the same way you used single values before, and it will automatically use \acite{broadcasting} and \cite{vectorization} to calculate all the wells at once!"
\begin{pyin}
calcConc(Mcat, MHCl, ionic)
\end{pyin}
\begin{multiout}
[ ] catalyst solution
 [[0.1 0.1 0.1 0.1 0.1 0.1]
 [0.1 0.1 0.1 0.1 0.1 0.1]
 [0.1 0.1 0.1 0.1 0.1 0.1]
 [0.1 0.1 0.1 0.1 0.1 0.1]]
[ ] HCl
 [[0.         0.00033333 0.00066667 0.001      0.00133333 0.00166667]
 [0.         0.00033333 0.00066667 0.001      0.00133333 0.00166667]
 [0.         0.00033333 0.00066667 0.001      0.00133333 0.00166667]
 [0.         0.00033333 0.00066667 0.001      0.00133333 0.00166667]]
[ ] NaCl
 [[0.00666667 0.006      0.00533333 0.00466667 0.004      0.00333333]
 [0.02666667 0.026      0.02533333 0.02466667 0.024      0.02333333]
 [0.04666667 0.046      0.04533333 0.04466667 0.044      0.04333333]
 [0.06666667 0.066      0.06533333 0.06466667 0.064      0.06333333]]
[ ] water
 [[0.89333333 0.89366667 0.894      0.89433333 0.89466667 0.895     ]
 [0.87333333 0.87366667 0.874      0.87433333 0.87466667 0.875     ]
 [0.85333333 0.85366667 0.854      0.85433333 0.85466667 0.855     ]
 [0.83333333 0.83366667 0.834      0.83433333 0.83466667 0.835     ]]
\end{multiout}

You can see now how this could save a ton of time.  All you have had to do is define two variables, rerun your function, and you have your volumes for all the wells.  

\subsection{Writing scripts to execute complete solutions in one click}
\label{Writing_scripts_to_execute_complete_solutions_in_one_click}

% Save a .py file

``The solution we just made is good, but we can do even better! It would be time consuming to type this out every time you wanted to change the experimental conditions. One of the benefits of programming is saving the lines of code that you write and running them automatically instead of forcing you to type them in every time. So far, we've used Python interactively, where we give it one instruction at a time.  But, we can put all the lines into a text document, called a \cite{script}, that we save and run from the IDLE.  When we run it, IDLE will go through the file one line at a time and automatically transfer that line to the \cite{Python_interpreter} and run it.  Then, it will go to the next line and do the same. That way, you don't have to do all the typing, which saves you a ton of time and prevents a lot of mistakes.  To do this, we just add more code to the end of the file where you defined your function (see Section \ref{Avoiding_repetitive_typing_by_making_your_own_functions}.  You didn't know it, but that was a script that you wrote, and you can modify it as you need to.  Python will just keep reading the file and will do the instructions that you put in there."

You go back to the IDLE text editor window and make sure the file was saved as \lstinline{WellPlate.py} so that you don't lose your previous work. ``Your Python script file names should always end with ``.py" so that your computer knows that they should be run with Python.  Other than that, this is just a text file, there is nothing special about it." 

You copy and paste the lines of code that you put into the IDLE Shell into the Python script and click Run-->Run Module, and you get the exact same result as you did previously without any typing!

``Now that we have this, you could just change the numbers every time our advisor changes their mind, and just run it again by clicking Run-->Run Module."  




\subsection{Keeping track of changes to your code using versioning practices}
\label{Keeping_track_of_changes_to_your_code_using_versioning_practices}


\First looks over at you, but sees you frowning slightly. They ask what is wrong, and you say that you are bit concerned, because you started with a file that was just a single function that you called in the IDLE, and then you added lots of new capabilities to it.  While that was nice, you are thinking about how you can ever hope to keep track of what is going on with code as you build it out. 
 Worse, what happens if you make a change it results in the code not working at all? How could you recover from this? 

%``Wait a second," you say, ``we just took something that was a function that calculated what I need for a single well, but  made something that works, shouldn't we make sure we don't break it?  I can see how it would be easy to change this code over and over and lose track of its original state, even ending up with something that doesn't work."

\First compliments you for your foresight and says if they can just show you how to do this, so you push the laptop over to them.

``What you have brought up is a need for \cite{version_control}. The idea behind this is to help you keep track of the changes you have made to your code as you develop it.  This is good practice for a lot of reasons: for instance, if you make a change that you decide is bad, you can easily go back to a time you liked the code.''

``There are multiple means to handle versioning, and most professional programmers use something like git.\footnote{If you want to know more, look at Chapter \ref{16Beyond}. But we really don't think this knowledge is needed yet} We aren't going to worry about this yet, since it is a bit much for where we are right now. So, let's talk about a simpler means to do this."

"What I like to do is as follows: each time I am going to change a script, I save the current version of it, with the Script name, and a series of three numbers after it.  For a generic Python file, this would look something like: \lstinline{Name_x.y.z.py}, where `x, y, and z' are the numbers.''

``You may have seen files with version numbers like this before; but what do they mean?'' \first asks. You just shake your head. Like, you know that these numbers seem to increase over time, but why does one or another change?  That has always been a mystery.

\First explains that each number has a meaning, and you should increment them as follows:
\begin{itemize}
    \item [\texttt{z}] indicates changes associated with `patches.' These are changes to the code that do not alter intended behavior or introduce new features. For instance, perhaps you have a code with a typo in it.  Fixing that typo does not change how the code behaves from the user's standpoint. 

    \item [\texttt{y}] indicates minor version changes. This should be used when you change the behavior or introduce new features to your program, but does so in a way that is backward compatible. This means that someone upgrading your program will not need to change how they have been using it---the things they have been doing in the past will continue to work with the new version.

    \item [\texttt{x}] indicates a major version change.  These are changes to the program that break backwards compatibility.  Changing this number indicates that someone upgrading your program can expect some things they have been doing will no longer work, and they will have to change how they use your program. 
\end{itemize}

``Right now, I am going to save a version of your code we just wrote. But this code is already different from just the function.  Really, it is trying to solve a related, but different, problem. For this reason, I will save this as a different script. Since this is going to be a script to calculate the volumes in well plates, we can call it \lstinline{WellPlate.py}.''

`` But, of course, we already added text to the function, so let us go back to how we started.'' \First deletes the code that you added, so that it is just the same function that you had at the end of Chapter \ref{Chap:N:00ExpDes}. 

``Before we save this, there is one more thing to do: we should \cite{document} these changes in your script using a \cite{comment} at the top.  I typically include this for each version, so I can understand what I was doing.  So, \first goes to the top of the script and adds this comment: \lstinline{# Version 0.1.0 - 240605 - A function that calculates volumes of stock solutions needed for a well in a well plate. }.  They then save a \textit{copy} of this script as \lstinline{WellPlate_0.1.0.py}.

``And now we are ready to keep altering the existing script: \lstinline{WellPlate.py}. When we do this, we can restructure the solution slightly to be better organized and easier to read. We will start by documenting the update, and  if we make further changes to this program, we will save this version we are producing now as a different file: \lstinline{WellPlate_0.2.0.py}. 


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

\First then starts typing for a few minutes and shows you the following code:

\lstinputlisting{../../Distribution/Code by Chapter/Ch01 - Automating Repetitive Calculations/WellPlate.py}

``Let's walk through this again, line by line."
\subsection{Line-by-line explanation}
\begin{footnotesize}
\textbf{Lines}: \textbf{Explanation}
\begin{itemize}
    \item [1 \& 2:] These lines are \cite{comment}s that explain what the \cite{script} does, which is helpful to anyone else reading the code or to you if you forget in a few years. They also include a version number and date, so that you can look back at what you changed over the years. You have a version number for the current version (0.2.0) and one for the original (0.1.0). We increment the second version number, since this change is introducing a new functionality, but not breaking the old functionality.

    \item [3:] We do not need to add a blank line here, however, from a readability standpoint, it can be a good idea.  These blank lines help human being  separate out the different parts of the code. We will sometimes use them and sometimes not, depending on exactly how the code is structured. 

    \item [4:] We need to use the \cite{Numpy} \cite{library}, and we can't assume that we have already imported it, so we will import it here, too, to be sure.

    \item [6-28:] This is just the same code you developed in the last chapter, with no modifications.  See the previous chapter for an explanation.

    \item [30-32:] We want to be able to use a well plate with any number of rows and columns, so we can define \cite{variable}s to hold those numbers here.  This means we only have to change them one place in the code, which will make our lives easier and make the code less prone to errors from forgetting to change the number of rows or columns every time we use them.

    \item [34-39:] Here we are setting up variables for the concentrations and ionic strengths that we will be using in the experiment.  Just like the last lines, setting them all here makes it so that we don't have to go through the code and change every value, which would take time and be prone to mistakes.

    \item [41-42:] We are going to make a 2D \cite{Numpy_array} of catalyst concentrations here.  We can make an array of all ones and multiply it by the catalyst concentration, which is a single value.  Numpy will then take care of \acite{broadcasting} the single value across the entire array, multiplying each element by the catalyst concentration and giving us an array where each element is the catalyst concentration that we want.

    \item [44-51:] This is where we are going to calculate the 2D array representing [HCl].  The basic plan is to make two 1D arrays, one that says how the concentration should change across a row, and one that says how the concentration should change down a column.  Then, we can take the ``outer product" of the two arrays, which will produce an array where each element is the product of the corresponding elements of the 1D arrays.  Here are the details:
    \begin{itemize}
        \item [46:] We will use \lstinline{numpy.linspace()} to create a 6-element Numpy array that goes from the initial to final concentration of HCl.  This is how our concentrations are changing.
        \item [48:] Each row of the plate has the same range of concentrations in it, so we can just make a 4-element array that is all ones and that will cause each row to be the same as the row array we just made.
        \item [50:] Now we have a `trick' to make the 2D array.  \lstinline{numpy.outer()} performs an outer product between two vectors.  The outer product between a vector of length four and a vector of length six is a 4 $\times$ 6 array.  Let's say we have the 1D arrays \lstinline{a = [a1, a2]} and \lstinline{b = [b1, b2, b3]}.  The outer product of these two vectors is the 2D array \lstinline{[[a1*b1, a1*b2, a1*b3],[a2*b1, a2*b2, a2*b3]]}.  The \lstinline{numpy.outer()} function accepts two \cite{arguments}: the first is the array whose length is the number of rows we want, and the second is the array whose length is the number of columns we want in our final array of arrays.  So, if we pass it the individual column and row arrays that we just made, we get the 2D array that we want in one line.
    \end{itemize}

    \item [53-60:] Here, we are doing the same thing as for \ce{[HCl]}, and the only difference is that each row is different and each column is the same, rather than the other way around as above.

    \item [63:] Now, all we have to do is call our function, and we get the printout of the volumes in the IDLE Shell.  \textit{Voila}!
\end{itemize}
\end{footnotesize}

``It was convenient to put the script into a file that we could edit, and IDLE had a capability for this, but there are many other programs that you can use that have even more helpful features.  These are generally called IDEs (Integrated Development Environments) and I really like using Spyder.  Like most things in Python, it is free to install and use, and you can install it the same way you installed Numpy."%it comes built in to Anaconda."
\websitefootnote

\begin{mdframed}
    \textbf{Why don't we use Jupyter notebooks?}
    
    \noindent An alternative to using an IDE like Spyder is to use Jupyter notebooks.  For sure, you will encounter people using this for solving data analysis workflows. However, there are a few reasons we are not going to use them in this book. First, it is possible to create very bad workflows in Jupyter where your analysis is not valid, but you don't realize it.  Second, it is challenging to access and code from Jupyter notebooks in external code, which limits the reusability of your code. Thus, while you may eventually decide to use Jupyter as you become more experienced, we think learning to code with an IDE gives you more flexibility and ensures better practices. 
\end{mdframed}

\begin{mdframed}
    \textbf{Why use Spyder as the IDE?}
    
    \noindent When reading about IDEs on the web, you will find that VSCode dominates most discussions and recommendations. The reason is that VSCode is free, easy to customize, and can be used with a variety of languages. However, we do not recommend using it for two reasons. First, you are learning Python, and so you don't need a more general tool. Second, Spyder provides some functionalities that are useful for data analysis and for people learning Python that are not present in VSCode.  These include a variable explorer, that will give you information on the \cite{object}s you are working with and their current states and a window that keeps a history of the plots and images you make.
\end{mdframed}

\begin{mdframed}
    \textbf{Python interpreter vs. iPython}
    
    \noindent While IDLE uses the standard Python interpreter, Spyder and many other IDEs use an interpreter called IPython. IPython features expanded capability over the standard interpreter: it can run operating system command line commands natively, it can display images, it has helpful input/output line numbering, and autocompletion.  If you get used to using IPython in an IDE, you may be surprised that some of the functionality that you have learned is not available when you have to run the standard interpreter (python.exe on Windows, python3 on a Mac).
\end{mdframed}

\hrule
\input{Exercises/Chapter 01 Designing a large scale experiment quickly}
\hrule

\end{document}
