Code
import numpy as np
from scipy.interpolate import splrep, splev
import matplotlib.pyplot as plt
This section covers essential concepts for working with spectroscopic data, including file formats, peak detection, and visualization techniques.
import numpy as np
from scipy.interpolate import splrep, splev
import matplotlib.pyplot as plt
Scientific instruments store both measurements and metadata in standardized formats. JCAMP-DX, a common spectroscopy format, illustrates key principles found in ROD files:
# Example format with metadata and measurements
= """
spectrum ##TITLE=Reference Sample
##BLOCKS=1
##XUNITS=1/CM
##YUNITS=ABSORBANCE
##XYDATA=(X++(Y..Y))
500.0 0.033 0.037 0.041
600.0 0.042 0.043 0.044
"""
# Extract data section
for line in spectrum.split('\n'):
if line.startswith('##'):
print(f"Metadata: {line}")
elif line.strip():
print(f"Data: {line}")
Metadata: ##TITLE=Reference Sample
Metadata: ##BLOCKS=1
Metadata: ##XUNITS=1/CM
Metadata: ##YUNITS=ABSORBANCE
Metadata: ##XYDATA=(X++(Y..Y))
Data: 500.0 0.033 0.037 0.041
Data: 600.0 0.042 0.043 0.044
Cubic splines preserve continuity through second derivatives, essential for peak analysis. Compare with simpler linear interpolation:
# Generate sparse, uneven samples
= np.array([0, 0.2, 0.7, 0.8, 1.0])
x_sparse = np.sin(2*np.pi*x_sparse)
y_sparse
# Fit spline
= splrep(x_sparse, y_sparse)
spl = np.linspace(0, 1, 100)
x_dense = splev(x_dense, spl)
y_spline
=(8,4))
plt.figure(figsize'ko', label='Data Points')
plt.plot(x_sparse, y_sparse, 'b-', label='Spline')
plt.plot(x_dense, y_spline, 0], x_sparse[1]], [y_sparse[0], y_sparse[1]],
plt.plot([x_sparse['r--', label='Linear')
True)
plt.grid(
plt.legend()'Spline vs Linear Interpolation')
plt.title( plt.show()
First derivatives identify maxima (zero crossings), while second derivatives characterize peak shapes:
# Generate example peak
= np.linspace(-2, 2, 20)
x = np.exp(-x**2) + 0.05*np.random.randn(len(x))
y
# Fit and compute derivatives
= splrep(x, y)
spl = np.linspace(-2, 2, 200)
x_fine = splev(x_fine, spl)
y_smooth = splev(x_fine, spl, der=1)
dy = splev(x_fine, spl, der=2)
d2y
= plt.subplots(2, 1, figsize=(8,6))
fig, (ax1, ax2) # Original and smoothed data
'ko', label='Data')
ax1.plot(x, y, 'b-', label='Spline')
ax1.plot(x_fine, y_smooth, True)
ax1.grid(
ax1.legend()'Peak Data')
ax1.set_title(
# Derivatives
'r-', label='First Der.')
ax2.plot(x_fine, dy, 'g-', label='Second Der.')
ax2.plot(x_fine, d2y, =0, color='k', linestyle='--')
ax2.axhline(yTrue)
ax2.grid(
ax2.legend()'Derivative Analysis')
ax2.set_title(
plt.tight_layout() plt.show()
Molecular spectra exhibit peaks with varying widths and intensities:
# Generate peaks with different properties
= np.linspace(0, 10, 200)
x = np.exp(-(x-3)**2/0.1) # Narrow peak
sharp = 0.7*np.exp(-(x-5)**2/1.0) # Wide peak
broad = sharp + broad
combo
=(8,4))
plt.figure(figsize'b--', label='Sharp')
plt.plot(x, sharp, 'r--', label='Broad')
plt.plot(x, broad, 'k-', label='Combined')
plt.plot(x, combo, True)
plt.grid(
plt.legend()'Peak Width Variation')
plt.title( plt.show()
Focused analysis requires careful region selection:
# Generate data with multiple features
= np.linspace(0, 10, 200)
x = (np.exp(-(x-3)**2/0.2) +
y 0.7*np.exp(-(x-5)**2/0.3) +
0.4*np.exp(-(x-7)**2/0.4))
# Analyze specific region
= (x >= 4.5) & (x <= 5.5)
region = x[region]
x_roi = y[region]
y_roi
=(8,4))
plt.figure(figsize'b-', label='Full Range', alpha=0.5)
plt.plot(x, y, 'r-', label='ROI', linewidth=2)
plt.plot(x_roi, y_roi, =0.2, color='red')
plt.fill_between(x_roi, y_roi, alphaTrue)
plt.grid(
plt.legend()'Region Selection')
plt.title( plt.show()
Clear peak visualization requires appropriate styling:
= np.linspace(0, 5, 30)
x_demo = np.exp(-(x_demo-2.5)**2/0.3)
y_demo
=(8,4))
plt.figure(figsize# Base data
'b-', label='Signal', alpha=0.5)
plt.plot(x_demo, y_demo, # Peak marker
2.5, np.max(y_demo), 'ro', markersize=10,
plt.plot(='none', label='Peak')
markerfacecolor# Baseline
=0, color='k', linestyle=':')
plt.axhline(y# Region highlight
2.3, 2.7, color='yellow', alpha=0.2)
plt.axvspan(
True)
plt.grid(
plt.legend()'Peak Visualization Elements')
plt.title( plt.show()
Robust data processing requires careful validation:
def validate_spectrum(x, y):
"""Example validation checks"""
if len(x) != len(y):
return False, "Length mismatch"
if not all(x[i] <= x[i+1] for i in range(len(x)-1)):
return False, "X values not monotonic"
if any(np.isnan(y)):
return False, "Contains NaN values"
return True, "Valid spectrum"
# Test cases
= np.array([1, 2, 3, 2])
x_test = np.array([0.1, 0.2, 0.3, 0.4])
y_test print(validate_spectrum(x_test, y_test))
(False, 'X values not monotonic')
Complex analysis requires multiple linked views:
# Generate example spectrum
= np.linspace(0, 10, 200)
x = (np.exp(-(x-3)**2/0.2) +
y 0.7*np.exp(-(x-6)**2/0.3) +
0.1*np.random.randn(len(x)))
# Create multi-panel figure
= plt.figure(figsize=(8,8))
fig = fig.add_gridspec(3, 1, height_ratios=[2,1,1], hspace=0.3)
gs
# Full spectrum
= fig.add_subplot(gs[0])
ax1 'k-', label='Data')
ax1.plot(x, y, True)
ax1.grid(
ax1.legend()'Full Range')
ax1.set_title(
# First zoom region
= fig.add_subplot(gs[1])
ax2 = (x >= 2.5) & (x <= 3.5)
mask1 'b-')
ax2.plot(x[mask1], y[mask1], True)
ax2.grid('First Peak Region')
ax2.set_title(
# Second zoom region
= fig.add_subplot(gs[2])
ax3 = (x >= 5.5) & (x <= 6.5)
mask2 'r-')
ax3.plot(x[mask2], y[mask2], True)
ax3.grid('Second Peak Region')
ax3.set_title(
plt.show()
The Ising model demonstrates how complex physical behavior emerges from simple rules. This section covers implementation aspects of Monte Carlo simulation.
The Ising model represents one of physics’ most successful simplified models, capturing complex collective behavior from simple local interactions.
The model assigns binary spins (\(s_i \in \{-1,+1\}\)) to lattice sites. The energy depends on nearest-neighbor interactions:
\(E = -J\sum_{\langle i,j \rangle} s_i s_j\)
where \(J\) is the coupling constant (typically set to 1) and \(\langle i,j \rangle\) denotes summation over nearest neighbors.
import numpy as np
import matplotlib.pyplot as plt
# Nearest neighbor, Ising
= np.array([
grid -1, 1, -1],
[1, -1, 1],
[ -1, 1, -1]
[
])
=(4,4))
plt.figure(figsize='coolwarm')
plt.imshow(grid, cmap='Spin')
plt.colorbar(label'Simple Spin Configuration')
plt.title(
plt.show()
# Energy calculation for center spin
= 1, 1
i, j = grid[i,j]
center = (grid[i-1,j] + grid[i+1,j] +
neighbors -1] + grid[i,j+1])
grid[i,j= -center * neighbors
E_center print(f"Energy contribution from center: {E_center}")
Energy contribution from center: 4
The Ising model exhibits a phase transition between ordered (low temperature) and disordered (high temperature) states. The inverse temperature β controls this behavior:
\(P(\text{flip}) = \frac{1}{1 + e^{\beta \Delta E}}\)
# Visualize flip probability
= np.linspace(-4, 4, 100)
dE = [0.2, 0.5, 1.0]
betas
=(8,4))
plt.figure(figsizefor beta in betas:
= 1/(1 + np.exp(beta * dE))
P =f'β={beta}')
plt.plot(dE, P, label
True)
plt.grid('ΔE')
plt.xlabel('P(flip)')
plt.ylabel('Flip Probability vs Energy Change')
plt.title(
plt.legend() plt.show()
Periodic boundaries minimize edge effects by wrapping the lattice:
def show_periodic(N=5):
# Generate simple pattern
= np.arange(N)
x = np.arange(N)
y = np.meshgrid(x, y)
X, Y = np.sin(2*np.pi*X/N) * np.sin(2*np.pi*Y/N)
pattern
=(8,4))
plt.figure(figsize
# Original
121)
plt.subplot(='coolwarm')
plt.imshow(pattern, cmap'Original Grid')
plt.title(
# With periodic extension
= np.zeros((N+2, N+2))
extended 1:-1, 1:-1] = pattern
extended[# Add periodic boundaries
0, 1:-1] = pattern[-1, :] # Top
extended[-1, 1:-1] = pattern[0, :] # Bottom
extended[1:-1, 0] = pattern[:, -1] # Left
extended[1:-1, -1] = pattern[:, 0] # Right
extended[
122)
plt.subplot(='coolwarm')
plt.imshow(extended, cmap'With Periodic Boundaries')
plt.title(
plt.tight_layout()
plt.show()
show_periodic()
Local energy changes can be computed efficiently:
def energy_change_example(grid, i, j):
"""energy change calculation"""
= len(grid)
N = grid[i,j]
current_spin # Get neighbors with periodic boundaries
= (grid[(i+1)%N, j] + grid[i-1, j] +
neighbors +1)%N] + grid[i, (j-1)%N])
grid[i, (j
# Energy before and after flip
= -current_spin * neighbors
E_before = current_spin * neighbors # Flipped spin
E_after return E_after - E_before
# Example grid
= 4
N = np.random.choice([-1, 1], size=(N,N))
grid = 1, 1
i, j = energy_change_example(grid, i, j)
dE print(f"Energy change for flip at ({i},{j}): {dE}")
Energy change for flip at (1,1): -4
The average magnetization \(M = \frac{1}{N^2}\sum_{i,j} s_{ij}\) serves as an order parameter:
# magnetization calculation
def calculate_magnetization(grid):
return np.mean(grid)
# Generate random configurations at different "temperatures"
= 20
N = 3
n_configs = plt.subplots(1, n_configs, figsize=(12,4))
fig, axes
for i, p in enumerate([0.1, 0.5, 0.9]):
# p controls bias towards +1
= np.random.choice([-1, 1], size=(N,N), p=[1-p, p])
grid = calculate_magnetization(grid)
m
='coolwarm')
axes[i].imshow(grid, cmapf'M = {m:.2f}')
axes[i].set_title('off')
axes[i].axis(
'Configurations with Different Magnetizations')
plt.suptitle(
plt.tight_layout() plt.show()
Effective visualization helps track system evolution:
def plot_state_evolution(states, times):
"""Plot grid states at different times"""
= len(times)
n = plt.subplots(1, n, figsize=(3*n, 3))
fig, axes
for ax, state, t in zip(axes, states, times):
='coolwarm')
ax.imshow(state, cmapf't = {t}')
ax.set_title('off')
ax.axis(
plt.tight_layout()
plt.show()
# Generate example evolution
= 10
N = []
states = [0, 10, 50]
times for t in times:
# Simulate different stages of ordering
= min(0.5 + t/100, 0.9)
p = np.random.choice([-1, 1], size=(N,N), p=[1-p, p])
state
states.append(state)
plot_state_evolution(states, times)
The quality of random numbers affects simulation results:
# Compare random number generators
= 1000
n_samples = np.random.default_rng() # Newer generator
rng = rng.random(n_samples)
rand1 = np.random.rand(n_samples) # Legacy
rand2
=(8,4))
plt.figure(figsize=30, alpha=0.5, label='Default RNG')
plt.hist(rand1, bins=30, alpha=0.5, label='Legacy')
plt.hist(rand2, binsTrue)
plt.grid(
plt.legend()'Random Number Distribution')
plt.title( plt.show()
Building robust command-line applications requires careful handling of input validation, error cases, and program output. This section shows key patterns.
The secant method approximates roots through successive linear interpolation:
\(x_{n+1} = x_n - f(x_n)\frac{x_n - x_{n-1}}{f(x_n) - f(x_{n-1})}\)
Unlike Newton’s method, it avoids derivative calculations but requires two initial points.
Python’s sys.argv provides command-line arguments as strings:
import sys
# Example command: python script.py 1.1 1.4
def parse_args():
try:
# Skip script name (argv[0])
= sys.argv[1:]
args if len(args) != 2:
print("Usage: script.py a b", file=sys.stderr)
return
print(f"Arguments received: {args}")
except Exception as e:
print(f"Error: {str(e)}", file=sys.stderr)
Convert and validate string inputs to appropriate types:
def validate_numeric(value_str):
try:
return float(value_str)
except ValueError:
raise ValueError(f"'{value_str}' is not numeric")
# Example usage
= ["1.23", "abc", "2.5"]
test_values for val in test_values:
try:
= validate_numeric(val)
num print(f"Converted {val} to {num}")
except ValueError as e:
print(f"Error: {e}")
Converted 1.23 to 1.23
Error: 'abc' is not numeric
Converted 2.5 to 2.5
Python provides separate streams for normal output and errors:
import sys
def output_streams():
# Normal output goes to stdout
print("Processing data...")
# Errors and warnings go to stderr
print("Warning: value out of range", file=sys.stderr)
# Usage example
output_streams()
Processing data...
Importing external functions requires careful path handling:
import os
import sys
# Add current directory to path
'.'))
sys.path.append(os.path.abspath(
# Import function from module
try:
from func import f
except ImportError:
print("Error: Cannot import function", file=sys.stderr)
1) sys.exit(
Root bracketing requires checking signs:
import math
def check_bracket(f, a, b):
"""Verify root bracketing using Bolzano's theorem"""
try:
= f(a), f(b)
fa, fb if fa * fb >= 0:
return False, "Values don't bracket root"
return True, "Valid bracket"
except Exception as e:
return False, f"Evaluation error: {str(e)}"
# Example with cos(x)
= math.cos
f print(check_bracket(f, 0, math.pi/2)) # No root
print(check_bracket(f, 0, math.pi)) # Contains root
(False, "Values don't bracket root")
(True, 'Valid bracket')
Control numeric output precision:
= 1.23456789
x # Various formatting options
print(f"{x:.6f}") # Fixed precision
print(f"{x:.2e}") # Scientific
print(f"{x:.20f}") # High precision
1.234568
1.23e+00
1.23456788999999989009
Structure try-except blocks for clarity:
def process_value(x):
try:
# Main operation block
= float(x)
result
except ValueError:
# Handle type conversion
print("Invalid number format", file=sys.stderr)
return None
except ZeroDivisionError:
# Handle computational error
print("Division by zero", file=sys.stderr)
return None
except Exception as e:
# Catch unexpected errors
print(f"Unexpected error: {e}", file=sys.stderr)
return None
return result
Proper program termination with status:
import sys
def main():
try:
# Main program logic
process_data()return 0 # Success
except ValueError:
print("Invalid input", file=sys.stderr)
return 1 # Error code
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 2 # Different error code
if __name__ == "__main__":
sys.exit(main())