Application: Snell’s Law

Module 3: NumPy

Estimating a Refractive Index

When light moves from one medium to another, its path bends. This phenomenon is called refraction. Snell’s law relates the angle of incidence \(\theta_1\) to the angle of refraction \(\theta_2\):

\[ n_1 \sin(\theta_1) = n_2 \sin(\theta_2), \]

where \(n_1\) and \(n_2\) are the refractive indices of the two media. Air has a refractive index close to 1, while water has a refractive index close to 1.33.

In this application, you will use vectorized trigonometric functions and descriptive statistics to estimate the refractive index of an unknown material.

import numpy as np
ModuleNotFoundError: No module named 'numpy'

Experimental Data

Each row contains an incidence angle followed by its corresponding refraction angle. Both are measured in degrees.

# Columns: theta_1, theta_2
angles = np.array([
    [30, 18],
    [32, 19],
    [34, 21],
    [36, 22],
    [38, 23],
    [40, 24],
    [42, 25],
    [44, 26],
    [46, 27],
    [48, 28],
    [50, 29],
    [52, 30],
    [54, 31],
    [56, 32],
    [58, 33],
    [60, 33]
], dtype=float)

print(angles.shape)
NameError: name 'np' is not defined
Task A: Calculate Relative Refractive Indices

Write a function named index_of_refraction(theta_1, theta_2) that accepts two arrays of angles in degrees and returns

\[ \frac{\sin(\theta_1)}{\sin(\theta_2)}. \]

Remember that NumPy’s trigonometric functions expect radians.

Task B: Summarize the Measurements

Write a function named mean_and_std(values) that returns the mean and population standard deviation in the format mean +/- standard deviation. Display both numbers with two decimal places.

For [1, 1.41, 1.22], the result should be 1.21 +/- 0.17.

Task C: Estimate the Unknown Index

Write a function named estimate_index(angles_2d, n_1=1.0) that:

  1. extracts the incidence and refraction columns;
  2. calls index_of_refraction();
  3. calculates \(n_2 = n_1\sin(\theta_1)/\sin(\theta_2)\);
  4. calls mean_and_std(); and
  5. returns the formatted estimate.

Estimate the unknown material first with air as the initial medium and then with water, using n_1=1.33.

def index_of_refraction(
    theta_1: np.ndarray,
    theta_2: np.ndarray
) -> np.ndarray:
    """Return sin(theta_1) / sin(theta_2) for angles in degrees."""
    theta_1_rad = np.radians(theta_1)
    theta_2_rad = np.radians(theta_2)
    return np.sin(theta_1_rad) / np.sin(theta_2_rad)


def mean_and_std(values: np.ndarray) -> str:
    """Format the mean and population standard deviation."""
    mean = np.mean(values)
    standard_deviation = np.std(values)
    return f"{mean:.2f} +/- {standard_deviation:.2f}"


def estimate_index(angles_2d: np.ndarray, n_1: float = 1.0) -> str:
    """Estimate the refractive index of the second medium."""
    incidence_angles = angles_2d[:, 0]
    refracted_angles = angles_2d[:, 1]
    relative_indices = index_of_refraction(
        incidence_angles,
        refracted_angles
    )
    n_2_values = n_1 * relative_indices
    return mean_and_std(n_2_values)


print("From air:", estimate_index(angles))
print("From water:", estimate_index(angles, n_1=1.33))
NameError: name 'np' is not defined

Summary and Reflection

This application combines two-dimensional slicing, degree-to-radian conversion, vectorized trigonometric functions, and statistical aggregation.

Explain why the angles must be converted to radians before calling np.sin(). Then describe how measurement variability appears in the reported result.