Exercises
Targeted exercises
Importing libraries to add capabilities to Python
-
In the IDLE or console of an IDE, import Numpy, and calculate the following. It may be helpful to know the following about Numpy: if you import Numpy as
npthen base-10 logarithms are accessed usingnp.log10, the value of $\pi$ is accessed usingnp.pi, the value of $e$ is accessed usingnp.e.- $\ln(1)$
- $\ln(10)$
- $\ln(e)$
- $\log_{10}(1)$
- $\log_{10}(10)$
- $\log_{10}(e)$
import numpy as np np.log(1) np.log(10) np.log(np.e) np.log10(10) np.log10(np.e)
-
In this chapter we introduced log operations, but Numpy introduces more mathematical operations. Using the Numpy documentation, a web search, or an AI tool, figure out how to calculate the following using Numpy:
- $-e^\pi$
- $\sqrt{\pi}$
- $\sin{\frac{\pi}{3}}$
- Greatest common divisor of 275 and 385
- Least common multiple of 12 and 27
-1*np.exp(np.pi) np.sqrt(np.pi) np.sin(np.pi/3) np.gcd(275, 385) np.lcm(12, 27)
- Using Numpy, calculate the half life of a material with a decay rate of $1.3\times10^{-6} \text{ s}^{-1}$.
np.log(2)/(1.3e-6)
Operating on collections of data efficiently with Numpy arrays
-
Use a Numpy array to complete the following steps:
- Make an array that holds the following pH values: 1.1, 2.2, 3.3, …, 9.9.
- Use this array to calculate the concentration of protons at each p$H$.
- Dilute all concentrations by a factor of 10.
- Convert the diluted concentrations back to p$H$.
pHs = np.array([1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9]) print(pHs) # confirm answer proton_conc = 10**(-1*pHs) diluted_conc = proton_conc/10 print(diluted_conc) # confirm answer -1*np.log10(diluted_conc) print(proton_conc) # confirm answer
- You wish to make a simple syrup that contains alcohol. You mix 220g of glucose with 220g of ethanol and then add enough water to bring the final volume to 0.75L. What is the concentration of the glucose and the ethanol? Use a Numpy array to complete this calculation.
C = 12.01 H = 1.008 O = 16.00 MWs = np.array([C*6 + H*12 + O*6, C + H*3 + C + H*2 + O + H]) # MW of glucose, alcohol mols = 220/MWs # mols of glucose, alcohol concs = mols/0.75 # concentration of glucose, alcohol print(concs)
-
Numpy has many other functions, which are built to work with Numpy arrays. For instance,
numpy.sum()can give you the sum of all elements in an array. Use this knowledge to find out the total concentration of solutes in Exercise 4.np.sum(concs)
-
You are doing a variable temperature NMR experiment at 8 equally-spaced temperatures from -10$^\circ$C to 60$^\circ$C. You have two isomers that exchange and the $\Delta{G}$ of isomerization is $10kJ/mol$. For each species, calculate the concentration/relative population at each temperature.
R = 8.31446261815324e-3 temps_C = np.linspace(-10, 60, 8) temps_K = temps_C + 273 Keq = np.exp(-10/(R*temps_K)) # this is the relative concentrations print(Keq)
Interpreting Python error messages to fix mistakes
-
Write code that, when run, generates an error of each type below. You might wish to consult Chapter the chapter on errors or the internet.
- Errors in variable naming.
- Errors in making an array.
- Adding vectors of different shape.
- Using a function incorrectly.
- Syntax errors.
- Indentation error.
- Forgotten `:'.
2a = "aa" np.array(1, 3, 5) np.array([1, 3, 5]) + np.array([1, 2]) rounded = np.round(2.1, decimal = 1) a + 1 = 3 a = "a" b = "b" def test()
Structuring data using arrays of arrays
-
Make a $3\times3$ Numpy 2D array, in which each index holds a different number from 1–9:
- Make the numbers count up by one as you go across the “rows.”
- Make the numbers count up by one as you go down the “columns.”
np.array([ [1,2,3], [4,5,6], [7,8,9] ]) np.array([ [1, 4, 7], [2, 5, 8], [3, 6, 9] ])
-
Imagine you had 4 solutions that had concentrations of 1.0M, 0.70M, 0.55M, and 0.13M.
- Make a Numpy array that holds these concentrations.
- Create a 2D array that holds three sets of arrays that would represent serial dilution of these concentrations by a factor of 3, performed twice.
- Subtract the array you just made from a 2D array that represents 12 solutions all of 1.00M concentration.
concentrations = np.array([1.0, 0.70, 0.55, 0.13]) serial_dilutions = np.array([ concentrations, concentrations/3, concentrations/9 ]) print(serial_dilutions) print(np.array([[1,1,1,1],[1,1,1,1],[1,1,1,1]]) - serial_dilutions)
Generating arrays of all ones or zeros Numpy
-
Make a 15 element 2D Numpy array where all elements have a value of 0.33.
- Make this array, using
numpy.ones() - Make this array, using
numpy.ones_like() - Make this array, using
numpy.zeros() - Make this array, using
numpy.zeros_like()
first_array = np.ones([3,5])*0.33 print(first_array) np.ones_like(first_array)*0.33 np.zeros([3,5])+0.33 np.zeros_like(first_array)+first_array - Make this array, using
Generating an array of equally spaced numbers using Numpy
-
Create the following Numpy arrays. You cannot just type out the values to create the array, you must use at least
numpy.linspace()ornumpy.arange()in your solution:- An array from 0 to 10, not including 10, with 10 elements, using
numpy.arange(). - An array from 0 to 10, not including 10, with 10 elements, using
numpy.linspace(). - An array spanning 0 to 10, including 10, with 10 elements.
- An array spanning 0 to 10, including 10, with each element differing by 0.5.
- An array from 0 to $2\pi$, not including $2\pi$ with 8 elements.
- Make an array that, when squared, has elements 0 to 10, equally spaced.
- Using
numpy.linspace()andnumpy.sin(), make an array that has the values $[0, \sqrt{2}, 1, \sqrt{2}, 0, -\sqrt{2}, -1, -\sqrt{2}]$. - An array spanning 1 to $10^{14}$ (including $10^{14}$), where each element is 10 times larger than the element before it.
np.arange(0,10,1) np.linspace(0,9,10) np.linspace(0,10,10) np.arange(0,10.5,0.5) np.linspace(0, 2*np.pi, 8) np.sqrt(np.linspace(0, 10, 8)) np.sin(np.linspace(0, 7/4*np.pi, 8)) (np.ones(15)*10)**np.linspace(0,14,15) - An array from 0 to 10, not including 10, with 10 elements, using
Planning a multi-step solution to a complex problem
-
You have created a series of molecules that you think will serve as p$H$ sensors, and you need to calibrate their responses to changes in p$H$. Write a code that will specify how much of a 6N HCl solution to use for 11 standards, each 10 mL in volume and which area equally spaced between two p$H$ that the user of the code can specify.
def calc_vols (start_pH, stop_pH): pHs = np.linspace(start_pH, stop_pH, 11) H_conc = np.exp(-pHs) vols = H_conc*0.01/6 # concentration times 10mL / molarity of HCl return vols print(calc_vols(3, 7))
Writing scripts to execute complete solutions in one click
- Create a file containing the solution to Exercise 2 and save it to a directory on your computer.
Your Script should be in a file—perhaps named “half-life.py” and should read:
np.log(2)/(1.3e-6)
Keeping track of changes to your code using versioning practices
- You wrote a Python script. Y start at version 0.1.0 and go through the changes described below, in turn. Next to each entry, provide the version number that should be assigned to your script after the changes.
-
You add a new keyword_argument to a function, with a default value that was originally hard coded.
-
You notice a typo in a comment, and fix it
-
You notice a typo in a function that breaks the function, and fix it.
-
You add a new positional_argument (that does not have a default value) to a function.
-
You notice an mathematical error in a formula, and correct it.
-
You decide to reorder the structure of your code.
-
You have your code output additional text contextualizing results that are printed to the console.
-
Since the default value of the new keyword argument is the same as the original hard coded value, this is not a breaking change. However, it is not simply a bug fix, but adds new capabilities. We can assign this as 0.2.0.
-
This is a simple bug fix, that doesn’t even change anything about how the program works. You can increment to 0.2.1
-
This is almost the definition of a bug fix. increment to 0.2.2
-
Because this positional argument does not have a default value, it will break the functionality of previous versions. Increment to 1.0.0
-
This would be a bug fix. Increment to 1.0.1.
-
This does not change behavior at all, so it is similar to a bug fix. 1.0.2
-
This is a minor change that doesn’t add a capability, but just clarifies an existing one. A case can be made for either 1.0.3, or 1.1.0.
-
Comprehensive exercises
-
Take the final code from this chapter and adapt it for a 384 well plate. You should only need to change two numbers to do this. Try to ensure that the printed result is easy to follow.
# Version 0.2.0 - 240605 - Took the function and wrote a script to calculate volumes of stock solutions needed for a well plate of arbitrary size. # Version 0.1.0 - 240605 - A function that calculates volumes of stock solutions needed for a well in a well plate. import numpy as np # A function to calculate the volumes of stock solutions needed to create solutions with given catalyst, HCl, and ionic strength # All concentrations are in M # The final volume is in mL and assumed to be 1 unless provided def calcPlateVols(conc_cat, conc_HCl, I, vol_final = 1): # Define our stock solutions conc_stock_cat = 0.1 # M conc_stock_HCl = 6 # M conc_stock_NaCl = 3 # M # Calculate the volumes needed vol_cat = conc_cat / conc_stock_cat * vol_final vol_HCl = conc_HCl / conc_stock_HCl * vol_final vol_NaCl = (I - conc_HCl) / conc_stock_NaCl * vol_final # Calculate the water needed to make up 1 mL vol_water = vol_final - vol_cat - vol_HCl - vol_NaCl print('[ ] catalyst solution (mL)\n', vol_cat) print('[ ] HCl (mL)\n', vol_HCl) print('[ ] NaCl (mL)\n', vol_NaCl) print('[ ] water (mL)\n', vol_water) # Define the dimensions of our well plate rows = 16 cols = 24 # Define our experimental concentrations and ionic strengths conc_cat = 0.01 # M conc_HCl_start = 0.0 # M conc_HCl_end = 0.01 # M I_start = 0.02 # M I_end = 0.2 # M # Get the concentration of catalyst in each well cat = conc_cat * np.ones((rows, cols)) # Get the concentration of HCl in each well # We will do this by multiplying two 1D arrays to make a 2D array # First, define the concentration of HCl in each row MHCl_row = np.linspace(conc_HCl_start, conc_HCl_end, cols) # Next, each row should be the same, so make an array of all ones MHCl_col = np.ones(rows) # Finally, we can outer multiply these two arrays to make a 2D array MHCl = np.outer(MHCl_col, MHCl_row) # Now we need to get the ionic strengths in each well by a similar method # Here, the columns are all the same instead of the rows # First, make a row that is all ones ionic_row = np.ones(cols) # Next, make an array that represents one column ionic_col = np.linspace(I_start, I_end, rows) # Finally, outer multiply to get the 2D array ionic = np.outer(ionic_col, ionic_row) # Calculate and print the volumes calcPlateVols(cat, MHCl, ionic)If you print each of these out on a large enough piece of paper (or small enough font) you can get them to have each row span the paper. Then you can just check off the ones you have added.
-
You find that your pipette is only accurate to the nearest $0.1\mu$L. Modify the final code in this chapter so that it prints out volumes rounded to the nearest $0.1\mu$L. Looking at your answer, could you carry out this experiment if you had a pipet that was only accurate to $1\mu$L? How do you know?
# Version 0.2.0 - 240605 - Took the function and wrote a script to calculate volumes of stock solutions needed for a well plate of arbitrary size. # Version 0.1.0 - 240605 - A function that calculates volumes of stock solutions needed for a well in a well plate. import numpy as np # A function to calculate the volumes of stock solutions needed to create solutions with given catalyst, HCl, and ionic strength # All concentrations are in M # The final volume is in mL and assumed to be 1 unless provided def calcPlateVols(conc_cat, conc_HCl, I, vol_final = 1): # Define our stock solutions conc_stock_cat = 0.1 # M conc_stock_HCl = 6 # M conc_stock_NaCl = 3 # M # Calculate the volumes needed vol_cat = np.round(conc_cat / conc_stock_cat * vol_final, 4) vol_HCl = np.round(conc_HCl / conc_stock_HCl * vol_final, 4) vol_NaCl = np.round((I - conc_HCl) / conc_stock_NaCl * vol_final, 4) # Calculate the water needed to make up 1 mL vol_water = vol_final - vol_cat - vol_HCl - vol_NaCl print('[ ] catalyst solution (mL)\n', vol_cat) print('[ ] HCl (mL)\n', vol_HCl) print('[ ] NaCl (mL)\n', vol_NaCl) print('[ ] water (mL)\n', vol_water) # Define the dimensions of our well plate rows = 4 cols = 6 # Define our experimental concentrations and ionic strengths conc_cat = 0.01 # M conc_HCl_start = 0.0 # M conc_HCl_end = 0.01 # M I_start = 0.02 # M I_end = 0.2 # M # Get the concentration of catalyst in each well cat = conc_cat * np.ones((rows, cols)) # Get the concentration of HCl in each well # We will do this by multiplying two 1D arrays to make a 2D array # First, define the concentration of HCl in each row MHCl_row = np.linspace(conc_HCl_start, conc_HCl_end, cols) # Next, each row should be the same, so make an array of all ones MHCl_col = np.ones(rows) # Finally, we can outer multiply these two arrays to make a 2D array MHCl = np.outer(MHCl_col, MHCl_row) # Now we need to get the ionic strengths in each well by a similar method # Here, the columns are all the same instead of the rows # First, make a row that is all ones ionic_row = np.ones(cols) # Next, make an array that represents one column ionic_col = np.linspace(I_start, I_end, rows) # Finally, outer multiply to get the 2D array ionic = np.outer(ionic_col, ionic_row) # Calculate and print the volumes calcPlateVols(cat, MHCl, ionic)_Looking at the solutions, you can see that the volume of added $\ce{HCl}$ is 0.0003, 0.0007, 0.001, 0.0013 mL. If you had to round to 1$\mu$L, then these values would be 0.000, 0.001, 0.001, and 0.001. Therefore, this experiment could not be done. _
-
Your advisor decides that they want you to add a small amount of another solvent to your reaction mixture. Modify the final code in the chapter to allow for the same arbitrary amount of that solvent to be added to each well.
# Version 0.2.0 - 240605 - Took the function and wrote a script to calculate volumes of stock solutions needed for a well plate of arbitrary size. # Version 0.1.0 - 240605 - A function that calculates volumes of stock solutions needed for a well in a well plate. import numpy as np # A function to calculate the volumes of stock solutions needed to create solutions with given catalyst, HCl, and ionic strength # All concentrations are in M # The final volume is in mL and assumed to be 1 unless provided def calcPlateVols(conc_cat, conc_HCl, I, sol_vol, vol_final = 1): # Define our stock solutions conc_stock_cat = 0.1 # M conc_stock_HCl = 6 # M conc_stock_NaCl = 3 # M # Calculate the volumes needed vol_cat = conc_cat / conc_stock_cat * vol_final vol_HCl = conc_HCl / conc_stock_HCl * vol_final vol_NaCl = (I - conc_HCl) / conc_stock_NaCl * vol_final # new addition for solvent vol_sol = np.ones_like(vol_NaCl)*sol_vol # Calculate the water needed to make up 1 mL vol_water = vol_final - vol_cat - vol_HCl - vol_NaCl - sol_vol print('[ ] catalyst solution (mL)\n', vol_cat) print('[ ] HCl (mL)\n', vol_HCl) print('[ ] NaCl (mL)\n', vol_NaCl) print('[ ] Solvent (mL)\n', vol_sol ) print('[ ] water (mL)\n', vol_water) # Define the dimensions of our well plate rows = 4 cols = 6 # Define our experimental concentrations and ionic strengths conc_cat = 0.01 # M conc_HCl_start = 0.0 # M conc_HCl_end = 0.01 # M I_start = 0.02 # M I_end = 0.2 # M sol_vol = 0.002 # Get the concentration of catalyst in each well cat = conc_cat * np.ones((rows, cols)) # Get the concentration of HCl in each well # We will do this by multiplying two 1D arrays to make a 2D array # First, define the concentration of HCl in each row MHCl_row = np.linspace(conc_HCl_start, conc_HCl_end, cols) # Next, each row should be the same, so make an array of all ones MHCl_col = np.ones(rows) # Finally, we can outer multiply these two arrays to make a 2D array MHCl = np.outer(MHCl_col, MHCl_row) # Now we need to get the ionic strengths in each well by a similar method # Here, the columns are all the same instead of the rows # First, make a row that is all ones ionic_row = np.ones(cols) # Next, make an array that represents one column ionic_col = np.linspace(I_start, I_end, rows) # Finally, outer multiply to get the 2D array ionic = np.outer(ionic_col, ionic_row) # Calculate and print the volumes calcPlateVols(cat, MHCl, ionic, sol_vol)

