Analysis in this course is done in Python. You are not expected to arrive knowing it; you are expected to leave able to load a CSV, fit a model, and make a publication-quality figure with error bars.
Setup¶
Any of these works. Pick one and stay with it.
Recommended — a local environment.
python -m venv phys320
source phys320/bin/activate # Windows: phys320\Scripts\activate
pip install numpy scipy matplotlib pandas jupyterlab uncertainties imageio pyserial
jupyter labAlternative — Google Colab. Run in a browser. Check which packages are available in the current runtime and install missing ones in the first cell. Save the notebook and raw data to storage you control; the runtime is temporary.
Alternative — Anaconda. Create an environment and add any packages the experiment needs. Record the environment so someone else can rerun the work.
Two packages beyond the standard scientific stack are worth having:
uncertainties— arithmetic on numbers that carry an uncertainty, with correlations tracked automatically. Turns half a page of propagation algebra into one line.lmfit— optional, but a real improvement overcurve_fitwhen a model has bounded or fixed parameters, which happens in Experiments 8, 12, and 13.
The five things you will do every week¶
1. Load data¶
import numpy as np
import pandas as pd
df = pd.read_csv("week06_led_data.csv") # header row with units in the name
print(df.head())
V, sV = df["V_on_V"].to_numpy(), df["sigma_V_V"].to_numpy()Keep the raw file exactly as the instrument wrote it, and do the cleaning in the notebook. A CSV that has been hand-edited in a spreadsheet cannot be regenerated, and when the fit misbehaves you will want to know whether the data or the cleaning is at fault.
2. Fit¶
See the template in Uncertainty and Curve Fitting. When you have defensible standard
uncertainties for each data point, pass sigma= and set
absolute_sigma=True. Examine residuals before trusting parameter errors.
3. Propagate¶
from uncertainties import ufloat, umath
L = ufloat(2.143, 0.002) # m
dt = ufloat(14.3e-9, 0.4e-9) # s
c = 2 * L / dt # round trip
print(f"c = {c:.4g} m/s") # uncertainty carried through automaticallyuncertainties tracks correlations, so x - x is exactly zero with zero
uncertainty, as it should be — something the quadrature formula gets wrong if
applied blindly.
4. Plot¶
import matplotlib.pyplot as plt
plt.rcParams.update({
"figure.dpi": 130, "font.size": 11,
"axes.grid": True, "grid.alpha": 0.3,
"errorbar.capsize": 3,
})Data are markers with error bars; models are lines without markers; axes carry a quantity and a unit; the caption says what the reader should notice. Save as PDF or SVG, not PNG, for anything that goes in a report.
5. Report the number¶
def quote(value, err, unit=""):
"""Round uncertainty to one significant digit (two if it starts with 1)."""
from math import floor, isfinite, log10
if not isfinite(err) or err <= 0:
raise ValueError("err must be a positive finite standard uncertainty")
power = floor(log10(err))
leading = err / 10**power
digits = 2 if leading < 2 else 1
rounded_err = float(f"{err:.{digits}g}")
places = digits - 1 - floor(log10(rounded_err))
rounded_value = round(value, places)
fmt = f".{max(places, 0)}f"
return f"{rounded_value:{fmt}} ± {rounded_err:{fmt}} {unit}".strip()
print(quote(633.4271, 0.8113, "nm")) # -> 633.4 ± 0.8 nmReading data out of the instruments¶
Getting numbers off the bench and into Python
Instrument | Export path | Notes |
|---|---|---|
Tektronix / Rigol oscilloscope | USB stick → CSV | Two columns, time and volts, with a multi-line header. |
Thorlabs spectrometer kit (EDU-SPEA1/SPEB1) | Kit software → CSV | Wavelength in nm, intensity in counts. Take a dark spectrum and subtract it. |
GM counter with serial output |
| See the logging snippet below. |
Arduino / Raspberry Pi Pico | Serial print → captured text | Print CSV lines directly; do not invent a binary format. |
Phone or webcam image | PNG/JPEG → | Shoot RAW or turn off auto-exposure and HDR, or the intensities are not linear. |
Legacy PASCO | Capstone → File ▸ Export Data → CSV | Only needed for the archived data in |
Legacy Vernier | Logger Pro → File ▸ Export As ▸ CSV | Same. |
Logging a serial stream¶
import serial, time, csv
with serial.Serial("/dev/ttyACM0", 115200, timeout=2) as ser, \
open("counts.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["t_s", "counts"])
t0 = time.time()
while time.time() - t0 < 3600: # one hour
line = ser.readline().decode().strip()
if line:
w.writerow([round(time.time() - t0, 3), line])
f.flush() # flush Python's output bufferThe f.flush() call sends each line from Python’s buffer to the operating
system, so a Python process crash is less likely to lose the last readings.
It does not guarantee that data reached the storage device; copy the raw file
to durable storage after acquisition.
Images to intensity profiles¶
Experiments 4 and 5 photograph a fringe pattern and extract a profile.
import numpy as np, imageio.v3 as iio
img = iio.imread("fringes.png").astype(float)
if img.ndim == 3:
img = img[..., :3].mean(axis=2) # to grayscale; see the caution below
row = slice(540, 560) # a band through the pattern
prof = img[row, :].mean(axis=0) # averaging the band kills noise
x_px = np.arange(prof.size)Notebook hygiene¶
One notebook per experiment, named for it, kept with the raw data.
Restart and run all before you submit. A notebook whose cells were run out of order does not reproduce, and the number in the text may not be the number the code now produces.
Never hard-code a number you computed elsewhere. If the fit gives the slope, the report’s number must come from the fit variable.
Comment the physics, not the syntax.
# lambda from fringe count, Eq. 3is useful;# fit the datais not.Submit the notebook with the report. It is the analysis’s raw notebook, and it is graded on the same contemporaneity principle.