Homework 2

Assignment Details

Assigned: 31 August
Due: Sunday, 13 September at 23:59

Gradescope: Homework 2 | Setup | How to Submit

Starter: hw2-starter.zip

Getting Started Guide: View Guide

Data: External Link

Overview

Three numerical computing problems: spectral peak estimation with splines, Gibbs sampling of the 2-D Ising model, and a command-line secant root finder with input validation.

Getting Started

Download the starter code: hw2-starter.zip

unzip hw2-starter.zip
cd hw2-starter
python generate_datasets.py --seed 541

This creates data/ with a placeholder raman.rod in the same layout as the real file but with random peaks. Download the real file from the Data link into data/ to replace it.

Each problem directory contains the script named in the problem, with a docstring stating how it is run and what it prints or writes. q1/ and q3/ also contain a test_interfaces.py that runs the script and checks its output. Run it from inside the problem directory:

python -m pytest test_interfaces.py

Problem 1: Peak Finding

Requirements

You may use any standard NumPy or SciPy packages or experiment with your own algorithms for this problem.

Raman spectroscopy is a technique that uses inelastic scattering of light to identify unknown chemical substances. Spectral “peaks” indicate vibrational and rotational modes and are of special importance because they act like a chemical fingerprint. Raman spectroscopy measures photon intensity vs. Raman shift. The Raman shift relates the frequencies of the exciting laser and the scattered photons and is often reported as a wavenumber – the frequency difference in wavelengths per cm (i.e., cm\(^{-1}\)).

Generate a molecular fingerprint using the spectroscopic data in raman.rod. The file contains intensity vs. wavenumber data for an unknown chemical sample. A Raman Open Database (ROD) file includes content in addition to the raw intensity data:

# content
more content
_raman_spectrum.intensity
wavenumber1 intensity1
wavenumber2 intensity2
...
wavenumbern intensityn

Use string matching to ignore all lines before _raman_spectrum.intensity. Load valid (wavenumber, intensity) pairs until the first invalid intensity line (or upon reaching the end of file).

Use the method below to estimate the wavenumbers of all spectral peaks.

First detect peaks in the raw spectral data. Use the peak locations to focus on regions of interest within the spectrum. For instance: if you detect peaks at \(x_1\) cm\(^{-1}\) and \(x_2\) cm\(^{-1}\) use regions of interest: \([x_1 - n_1, x_1 + n_1]\) and \([x_2 - n_2, x_2 + n_2]\). Experiment to find “good” widths \(n_1\), \(n_2\), etc. Then use a spline to interpolate intensity within each region of interest. Calculate zero-crossings of the derivative to estimate wavenumbers with maximum intensity.

a. Print the wavenumber estimates for the eight largest spectral peak to STDOUT sorted by magnitude (largest first).

b. Create a figure that shows the Raman data (intensity vs. wavenumber) and mark each of the maximum intensity values.

c. Produce a “zoomed-in” figure for the “regions of interest” corresponding to the four largest peaks. Plot the raw spectral data and overlay your interpolating function. Use a marker to show the wavenumber with maximal intensity.

Deliverables

See Submission. peaks.py is your implementation. q1.pdf contains the eight wavenumber estimates, the intensity vs. wavenumber figure, and the four zoomed-in figures.

Problem 2: Gibbs Sampler

Requirements

Use only Python standard library modules, numpy, and matplotlib for this problem.

A Gibbs sampler generates samples from a joint probability distribution by iteratively sampling each variable conditioned on the others. In this problem, you will implement Gibbs sampling to analyze the 2-dimensional Ising model, which represents a lattice of binary spins interacting with nearest neighbors. This model is used in statistical physics to study phase transitions and collective behavior arising from local spin interactions.

Part A: Sampler

Implement a Gibbs sampler for a \(N = 20\) grid (i.e. \(20 \times 20\)) where each site has state \(s_i \in \{-1, +1\}\). The system energy is:

\[E(\mathbf{s}) = -\sum_{\langle i,j \rangle} s_i s_j\]

where the sum is over nearest-neighbor pairs. The probability of a configuration is:

\[P(\mathbf{s}) \propto \exp(-\beta E(\mathbf{s}))\]

Initialize the grid randomly and store the initial state.

Part B: Energy

Run the sampler for \(\beta = \{0.2, 0.4, 0.6\}\). For each site update:

\[P(\text{flip}) = \frac{1}{1 + \exp(\beta\Delta E_i)},\]

where \(\Delta E_i = \text{energy change if site } i \text{ flips}\). Record the energy after every 10 iterations for 1000 total iterations. Create a figure showing \(E(\mathbf{s})\) versus iteration number for each \(\beta\).

Part C: Magnetization

Calculate the average magnetization:

\[M = \frac{1}{N^2} \sum_{\langle i,j \rangle} s_i = \frac{1}{N^2} \sum_{i=1}^{N^2} s_i.\]

Plot \(M\) versus iteration number. Create visualizations of the grid state at iterations \(\{0, 100, 500, 1000\}\) using:

plt.imshow(grid, cmap='binary')
plt.colorbar()
plt.savefig(f'state_{beta}_{iter}.png')

Deliverables

See Submission. gibbs.py is your implementation. q2.pdf contains the energy and magnetization plots for each \(\beta\) and the grid state visualizations.

Problem 3: Root Finding

Requirements

Use only Python standard library modules (https://docs.python.org/3/library/) and matplotlib for this problem, i.e. do not import numpy, scikit, or any other non-standard package.

The secant method is an iterative root-finding algorithm. It uses a sequence of secant line roots to approximate \(c\) such that \(f(c) = 0\) for a continuous function \(f\). Unlike Newton’s method it does not require knowledge or evaluation of the derivative \(f'\). The secant method is defined by the recurrence:

\[x_n = x_{n-1} - f(x_{n-1}) \frac{x_{n-1} - x_{n-2}}{f(x_{n-1}) - f(x_{n-2})}.\]

Write a python script that uses the secant method to approximate the root of a continuous function \(f\) in the interval \([a, b]\). You may assume that \(f\) has at most one root in \([a,b]\). Use \(|x_{k+1} - x_{k}| < 10^{-10}\) as the convergence criterion. Let \(N\) be the number of iterations to reach convergence. Output \(N\) followed by the three root approximations \(x_{N-2}\), \(x_{N-1}\), \(x_N\). Output each number to its own line and use precision sufficient to show convergence.

Import the function \(f\) from a file named func.py in the same directory as your script – i.e. from func import f – example func.py (included in starter code). You may assume that \(f\) is continuous on \([a,b]\) and that func.f(x) returns a scalar float for all \(x \in [a,b]\).

Your script should accept a and b as two numeric command line arguments, i.e. python secant.py "1.1" "1.4". Your script must validate that a and b are numeric, verify that a < b, and check that f(a)f(b) < 0 – see Bolzano’s Theorem. Write “Range error” to STDERR (standard error) if any of these three conditions fail and immediately terminate.

Grading

Your script is run with a different func.py and command line arguments. Output must match the format above exactly.

Deliverables

See Submission. secant.py is your script. Include the func.py you tested against.


Submission {#submission}

README.md
.gitignore
requirements.txt
generate_datasets.py
q1/
├── peaks.py
├── test_interfaces.py
└── q1.pdf
q2/
├── gibbs.py
└── q2.pdf
q3/
├── secant.py
├── func.py
└── test_interfaces.py

Do not commit data/ — the starter’s .gitignore excludes it.