Exercises

Targeted exercises

Writing text files with arbitrary formatting using with open

Exercise 0

Produce a narrative fit report, that explains the model, the fit approach, and the results for the linear fit produced by the final code in Chapter 5. You can append the following to the code for Chapter 5.

from codechembook.quickTools import quickFormula
report = f'''
The extinction coefficient for {quickFormula('(C4N2S2)2Ni', html=False).unicode},
taken at {l_max} is {result.params['slope'].value / 1:5.2f} {cs.math.plusminus} {result.params['slope'].stderr:4.2f} M{cs.typography.sup_minus}{cs.typography.sup_1}cm{cs.typography.sup_minus}{cs.typography.sup_1}.
This was obtained from a least squares fit to the lmfit {result.model}
'''

print(report)

which will output:

The extinction coefficient for (C₄N₂S₂)₂Ni,
taken at 580.0 is 10.27 ± 0.09 M⁻¹cm⁻¹.
This was obtained from a least squares fit to the lmfit Model(linear)

Exercise 1

Modify the example code shown for Section \ref{Drawing_molecules_with_RDKit} to save the molecular drawing as both svg and png images. This can be done by append the following to the end of the code:

Draw.MolToFile(acetate_mol, "acetate.png", size = (100, 100)) # saves a png

svg_data = Draw.MolToSVG(acetate_mol, size = (100, 100)) # generates svg specifications as a string

with open("acetate.svg") as a:
	a.write(svg_data)

Once you have the images, bring both of them into a Microsoft Word document or Powerpoint presentation (or similar software). Make both 3 inches wide. Change the colors of the bonds (if you can). Do you notice any advantages of svg or png?

Making Microsoft word tables using docx

Exercise 1

Write out the slope and intercept values from the final code in Chapter 5 as a table in Microsoft word. If you add the following code to the end of the Chapter 5 code, it will produce a work document with a table in it.

from docx import Document
from docx.shared import Inches

# Create a new Word document
doc = Document()
doc.add_heading('Linear Fit Parameters', level=1)

# Add table with 3 rows and 3 columns (Parameter, Value, Uncertainty)
table = doc.add_table(rows=3, cols=3)
table.style = 'Table Grid'

# Header row
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Parameter'
hdr_cells[1].text = 'Value'
hdr_cells[2].text = 'Uncertainty'

# Slope row
slope_cells = table.rows[1].cells
slope_cells[0].text = 'Slope'
slope_cells[1].text = f"{result.params['slope'].value:.4e}"
slope_cells[2].text = f"{result.params['slope'].stderr:.4e}"

# Intercept row
intercept_cells = table.rows[2].cells
intercept_cells[0].text = 'Intercept'
intercept_cells[1].text = f"{result.params['intercept'].value:.4e}"
intercept_cells[2].text = f"{result.params['intercept'].stderr:.4e}"

# Save the document
doc.save('calibration_fit_parameters.docx')
print("Word document saved: calibration_fit_parameters.docx")

Comprehensive exercises

Exercise 1

We have just learned we can open files and write them, using the with open() construction. We could have done this to read files as well. For well-structured files, functions like genfromtxt() are quicker and more efficient. However, there are times that you will need to use the with open() to process a file line-by-line. This is done using: \begin{lstlisting} with open ( < file > , ‘r ‘) as f : # the ‘r ’ indicates the file is open in read mode for line in f : # this reads the file line by line < do what you want with each line > \end{lstlisting} With this knowledge, go back and re-create the plot we made at the end of Chapter 2, but import the data from the file using the with open() construction.

'''
A program to plot one uv-vis spectrum from a .csv file
    Requires: a .csv file with col 1 as wavelength and col 2 as intensity    
    Written by: Ben Lear and Chris Johnson (authors@codechembook.com)
    v1.0.0 - 250131 - initial version
'''
import numpy as np # needed for genfromtxt()
from plotly.subplots import make_subplots # needed to plot
from codechembook.quickTools import quickOpenFilename

# Specify the name and place for data
data_file = quickOpenFilename()

with open(data_file, "r") as f:
    x_data, y_data = [], []
    for row in f:
        print(row)
        try:
            x_data.append(float(row.split(",")[0]))
            y_data.append(float(row.split(",")[1]))
        except:
            pass

x_data = np.array(x_data)
y_data = np.array(y_data)     

# Construct the plot - here UVvis holds the figure object
UVvis = make_subplots() # make a figure object
UVvis.add_scatter(x = x_data, y = y_data, # make a scatter trace object
    mode = 'lines', # this ensures that we will only get lines and not markers
    showlegend = False) # this prevents a legend from being automatically created

# Format the figure
UVvis.update_yaxes(title = 'absorbance')
UVvis.update_xaxes(title = 'wavelength /nm', range = [270, 1100])
UVvis.update_layout(template = 'simple_white') # set the details for the appearance 

# Display the figure
UVvis.show('png') # show an interactive plot and in the spyder Plots window

Exercise 1

Starting with the code written at the end of this chapter, modify it so that it also includes the image of the fit result that is produced by the code. Include a helpful caption. You can use the Spyder plots window to save the image to a file.

from docx import Document
from docx.oxml import OxmlElement, parse_xml
from docx.oxml.ns import qn, nsdecls
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.shared import Inches
from codechembook.quickTools import importFromPy, quickOpenFilename, quickSaveFilename, quickPopupMessage
importFromPy('DecomposeSpectrum.py', 'result')

# Start a new Word document
doc = Document()

# Add a section title - we have to handle the subscript as a separate paragraph
title = doc.add_heading(level=1) # Use the preset heading font
title_run = title.add_run('Composition of the Ni(pz)') # Text for the start of the heading; pz = pyrazine
subscript_run = title.add_run('2') # Text that is supposed to be subscripted
subscript_run.font.subscript = True # Change the text formatting to subscript
title.add_run(' MLCT band') # Change back to normal font and keep going

# Add the introductory paragraph with subscript
paragraph = doc.add_paragraph('Parameters extracted from the spectral decomposition of the MLCT band for Ni(pz)')
paragraph_run = paragraph.add_run('2')
paragraph_run.font.subscript = True
paragraph.add_run(' into Gaussians contributions. ')

# Create a table
parameters = ['amplitude', 'center', 'sigma']
ncomponents = 3 # the number of gaussians
ncols = len(parameters)*2 # each gaussian has three parameters, each with uncertainty
table = doc.add_table(rows=ncomponents + 1, cols=ncols + 1) # create a blank table
table.style = 'Table Grid' # style the table

# Add header row with the names of the fit parameters
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Component'
for i, name in enumerate(parameters):
    hdr_cells[2*i+1].text = f'{name}'
    hdr_cells[2*i+2].text = f'{name} uncertainty'

# Make text in header cells bold and shaded gray, and with a light gray backgound
for cell in hdr_cells:
    # Get the instructions for Word to apply the text shading and giving it to the cell
    shading_elm = parse_xml(r'<w:shd {} w:fill="D9D9D9"/>'.format(nsdecls('w'))) # must create each time
    cell._tc.get_or_add_tcPr().append(shading_elm)
    # Loop through each run in each paragraph in the cell
    for paragraph in cell.paragraphs:
        for run in paragraph.runs:
            run.font.bold = True
            shading_elm = OxmlElement('w:shd') # create a shading element
            shading_elm.set(qn('w:fill'), 'C0C0C0') # set the text color
            run._element.append(shading_elm) # add it back in

# Loop through each gaussian componenet and add the parameter values to the table
for i, model in enumerate(['g1', 'g2', 'g3']):
    data_cells = table.rows[i+1].cells # get a collection of new cells
    data_cells[0].text = f'{model}' # set the row label
    # Loop through the parameters and print their values in the corresponding cell
    for j, parameter in enumerate(parameters):
        data_cells[2*j+1].text = f'{result.params[f"{model}_{parameter}"].value:.2f}'
        data_cells[2*j+2].text = f'{result.params[f"{model}_{parameter}"].stderr:.2f}'
        data_cells[2*j+1].paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
        data_cells[2*j+2].paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT

image = doc.add_paragraph()
image_run = image.add_run()
quickPopupMessage(message = 'Choose an image file to add.')
image_run.add_picture(str(quickOpenFilename('Select Image to Import')), width = Inches(6.5))

# Open a file dialog to pick the file to save to
quickPopupMessage(message = 'Choose a filename for the Word document.')
file = quickSaveFilename(title = 'Please choose a filename to save as.', 
                         filetypes = 'Word Documents, *.docx')
  
# Save the document
doc.save(file)