Hide code cell content

import mmf_setup; mmf_setup.nbinit()
import os, sys
from pathlib import Path
FIG_DIR = Path(mmf_setup.ROOT) / '../Docs/_build/figures/'
os.makedirs(FIG_DIR, exist_ok=True)
import logging; logging.getLogger("matplotlib").setLevel(logging.CRITICAL)
%matplotlib
import numpy as np, matplotlib.pyplot as plt
try: from myst_nb import glue
except: glue = None

from phys_581.contexts import FPS

This cell adds /home/docs/checkouts/readthedocs.org/user_builds/wsu-phys-581-computation/checkouts/fall-2025/src to your path, and contains some definitions for equations and some CSS for styling the notebook. If things look a bit strange, please try the following:

  • Choose "Trust Notebook" from the "File" menu.
  • Re-execute this cell.
  • Reload the notebook.

Using matplotlib backend: module://matplotlib_inline.backend_inline

Physics 581: Compute Anything –
Effective Scientific Computing#

  • Who: Graduate students or upper-level undergraduates from across the university with a strong preparation in mathematics (strong calculus and linear algebra with exposure to differential equations). Programming experience would be helpful, but we will provide support those with weaker backgrounds.

  • When: MW(F)2-4pm: Fridays are part of the iSciMath Coffee Hours and will be for working on problems, discussions, etc.

Imagine designing a rocket: You need to minimize weight, but maintain strength. Do you need to start a million-dollar program to model structures, or can you use computer modeling to help? What will get you to space fastest, and keep you in one piece? Compute Anything will lower the barrier for computing, allowing you to quickly explore problems as both a programmer and a scientist. We will show you that programming can fun, dynamic, and interactive, accelerating your research and your career.

If you have any questions, please contact Michael McNeil Forbes m.forbes@wsu.edu.

FAQ#

  • Q: What language are we using?

    A: Python with the NumPy, SciPy, and Matplotlib packages. However, if you have experience with another language, we encourage multilinguality.

  • Q: I’ve already taken a programming course. Why should I take this one?

    A: This course will teach you much more than just programming. We will teach you programming best-practices like version control and documentation for reproducibility, and testing to ensure correctness – techniques you can directly use to improve your research. By the end of the course, you will have an online programming portfolio, the skills to write a paper or thesis with publication quality figures, and know how to reliably and reproducibly explore data.

  • Q: What will class be like?

    A: We will typically start with a brief lecture establishing the mathematical underpinning of a technique. Then we will quickly implement simple examples together to ensure that things work as expected and develop intuition. Once these basics are established we will explore simply problems, then try to break our code with edge-cases as we write tests. Later in the course, we will revisit and use our validated code to explore more complex problems, profiling and optimizing as needed for performance, or targeting our code for high-performance computing (HPC) clusters.

  • Q: Do I need to be an experience programmer?

    A: While programming experience will help, we will provide support for those with limited programming experience to help you get up to speed. If you are concerned, please reach out for some introductory materials to help you get started.

  • Q: I don’t need any more courses. Why should I take this class?

    A: This class is aimed at improving your productivity. If your research is well under way, then you are encouraged to bring a relevant project to the class, and will receive credit for completing a research-relevant programming project demonstrating the principles of pragmatic programming: i.e., a version-controlled repository of well-documented and well tested code aimed at helping your research, with a skeleton paper or thesis meeting all the technical and formatting requirements of a relevant journal or the graduate school.

For example, in addition to the technical Learning Outcomes at the heart of scientific computing, we will emphasize the following concepts:

  • Language: At a basic level, we must be able to tell computers what we want them to do, but this is only one function of programming languages: they also enable you to think differently. I thus strongly encourage you to become multilingual, especially learning languages from different programming paradigms.

  • Development Process and Optimization: Computers tend to be unforgiving of mistakes. The software engineering community has developed processes to reduce these mistakes, and the same ideas can be used to optimize your research. For example, agile development, documentation, testing, and debugging all have analogous roles in a vibrant research program. These ideas, espoused in The Pragmatic Programmer, will be emphasized in the course. Students will be taught to optimize both their code, and their development practices, to minimize the time to solution while ensuring correctness.

  • Correctness: Quickly getting results is good, but ensuring that those results are correct is even more important. We will focus on verifying results with comprehensive testing and continuous integration (CI), enabling you to confidently use library codes and tools like LLMs (ChatGPT, CoPilot, etc.) to quickly get work done correctly.

  • Reproducible Research: Software development practices like version control with git, mercurial, or jujutsu, can be repurposed to keep track of your papers and your research work (and to back them up in the cloud). Coupled with good documentation, these enable reproducible science, both by others, and by your future self.

  • Thinking like a programmer can help you approach problems differently and better use your tools. Many repetitive or complex tasks can be solved by simple programming – E.g., using regular expressions in your editor to do a complicated search and replace, using Makefiles to programmatically download and install software you need, using LaTeX to write your thesis, having it automatically collect, organize, and format your references.

Programming Examples#

Here are a few examples of the types of problems we will solve in this course. Each of these examples is code-complete, using only NumPy and SciPy to compute the solution, and Matplotlib to plot (with a custom function FPS() to help make movies). By the end of the course, you should be able to write similar code within a matter of hours to study problems relevant to your research.

Molecular Dynamics#

Complex material properties can be simply modeled with molecular dynamics simulations that simply apply Newton’s laws to a collection of particles. Here we show a simple example of a projectile impacting a slab. See Molecular Dynamics for more details.

This idea can be easily generalized for your research: e.g. orbiting planets, galaxy formation, traffic flow, protein folding, and biomechanics.

Hide code cell source

#%%time
import numpy as np
_EPS = np.finfo(float).eps

class MolecularDynamics:
    L1 = 250
    L2 = 40
    epsilon = 5
    sigma = 1
    m = 1
    N1xy = (40, 40)
    N2xy = (160, 40)
    v1 = 0 - 10j
    v2 = 0 + 0j
    r_cut_sigma = 2.5
    dt = 0.00005
    
    sep = 0+5j  # Initial separation (units of dx)

    # For colouring
    kinetic_energy_max = 5
    cmap = plt.colormaps['viridis']

    def __init__(self, **kw):
        for key in kw:
            if not hasattr(self, key):
                raise ValueError(f"Unknown {key=}")
            setattr(self, key, kw[key])
        self.init()
    
    def init(self):
        self.dx = 2**(1/6) * self.sigma   # Equilibrium spacing
        self.z0_1 = (-self.N1xy[0]/2 + (self.N1xy[1]-1)*1j +  self.sep) * self.dx
        self.z0_2 = (-self.N2xy[0]/2 + 0j) * self.dx
        
    def F_r(self, r2):
        """Return F/r where F is the Lennard-Jones force for separation r.
        
        I.e., F_ij = (F/r)*r
        
        where r = r_j - r_i.
        
        Arguments
        =========
        r2 : array-like
            Square of the separation distance.
        """
        # Prevent divide-by-zero errors
        r2_ = r2 + _EPS
        # s = (sigma/r)**6
        s = np.where(r2 > 0, (self.sigma**2 / r2_)**2, 0)
        s *= s**2
        f = 24 * self.epsilon * s / r2_ * (1 - 2*s)
        return f
        
    def get_initial_state(self):
        (X1, Y1), (X2, Y2) = [
            np.meshgrid(*[np.arange(N)* self.dx + x0
                          for (N, x0) in zip(Nxy, (z0.real, z0.imag))],
                        indexing='ij')
            for Nxy, z0 in [(self.N1xy, self.z0_1), 
                            (self.N2xy, self.z0_2)]
        ]
        Z1 = X1 + 1j*Y1
        Z2 = X2 + 1j*Y2
        V1 = 0*Z1 + self.v1
        V2 = 0*Z2 + self.v2
        Z, V = [np.concatenate(list(map(np.ravel, A)))
                for A in ((Z1, Z2), (V1, V2))]
        return (0, Z, V, 0*Z)
    
    def plot(self, state, ax):
        t, z, v, a = state
        kinetic_energy = self.m * (v.conj()*v).real / 2
        ax.scatter(z.real, z.imag, c=self.cmap(kinetic_energy/self.kinetic_energy_max))
        ax.set(title=f"{t=:.4f}", aspect=1)
        return ax
    
    def get_F(self, z, v):
        """Return the total force on particles at z."""
        r_ = z[None, :] - z[:, None]
        return (self.F_r(r2=abs(r_)**2) * r_).sum(axis=1)
        
    def step(self, state):
        """Return the acceleration on each particle.
        
        This uses the Velocity-Størmer-Verlet method.
        """
        dt = self.dt
        t0, z0, v0, a0 = state
        z1 = z0 + dt * v0 + dt**2/2 * a0
        a1 = self.get_F(z1, v0) / self.m
        v1 = v0 + dt / 2 * (a0 + a1)
        return (t0 + dt, z1, v1, a1)


md = MolecularDynamics(
    v1=-5j, N1xy=(4*2, 4*2), N2xy=(16*2, 4*2), sep=-10.3+2j,
    kinetic_energy_max=10, dt=0.00005*40)
s = md.get_initial_state()

fig, ax = plt.subplots(layout="constrained")
for frame in FPS(100, fig=fig, embed=True, filename="_static/MD.mp4"):
    for n in range(20):
        s = md.step(s)
    ax.cla()
    ax = md.plot(s, ax=ax)
    ax.set(xlim=(-12*2, 12*2), ylim=(-5, 10*2))
    ax.set_axis_off()
    fig.colorbar

Shallow-Water Equations#

Here is another complete example of one of the core applications that will be taught in the course: solving the Navier-Stokes equations in 1D for incompressible fluid flowing over a rough surface. In this example, we excite a fluid in a harmonic trap with a bump. As the fluid flows back and forth over the bump, viscosity dissipates energy and the sloshing slows down.

This idea can be easily generalized for your research: e.g. economic models, quantum mechanics, and biological response.

Hide code cell source

#%%time
# Example using a pseudo-spectral Chebyshev basis to solve the shallow-water
# equations for an oscillating fluid.  This code is complete, using only
# NumPy and SciPy except for plotting, which uses Matplotlib and a custom
# function FPS to facilitate making movies.
#
# Not included, but part of the class code, will be supporting code for
# testing the results, and better organization to facilitate reuse.

import numpy as np, matplotlib.pyplot as plt
import scipy.fft, scipy.integrate
sp = scipy

Nx = 128
th0 = (2*np.arange(Nx) + 1) * np.pi / 2 / Nx
k = np.arange(Nx)
x0 = np.cos(th0)

g_m = 1.0  # Coupling constant g/m
nu = 0.2  # Viscosity coefficient

w = np.sqrt(2)  # Trap frequency
V0_m = 0.5  # Bump height
sigma = 0.1  # Bump width

# Initial density provile 
X0_shift = 0.5  # Initial shift of the cloud
X0 = x0 + X0_shift

# Initial density provile 
n0 = w**2/2/g_m * (1 - x0**2)
n0_x0 = -w**2 / g_m * x0

def diff(f, d=1, k=k, th=th0):
    """Return the dth derivative of d.
    
    The derivative here is computed using the discrete cosine and
    sine transforms associated with the Chebyshev peudospectral basis.
    """
    ft = sp.fft.dct(f, type=2, norm='forward')
    s = np.sin(th)

    # Shift the coefficients and pad with zero.
    df_dth = sp.fft.dst(np.concatenate([-(ft*k)[1:], [0]]), type=3)
    if d == 1:
        df_dx = -df_dth / s
        return df_dx 
    elif d == 2:
        d2f_dth2 = sp.fft.idct(-ft*k**2, type=2, norm='forward')
        d2f_dx2 = (d2f_dth2 - df_dth / np.tan(th)) / s**2
        return d2f_dx2
    return diff(df_dx, d=d-1)
    
def pack(X, X_t):
    """Return a single 1D array with X and X_t."""
    return np.ravel([X, X_t])

def unpack(y):
    """Return (X, X_t) from y."""
    return np.reshape(y, (2, Nx))

def unpack_xnu(y):
    """Return (x, n, u) from y."""
    X, X_t = unpack(y)
    X_x0 = np.array([diff(x) for x in X])
    n = n0 / X_x0
    u = X_t
    x = X
    return x, n, u

def compute_dy_dt(t, y):
    """Return pack(X_t, X_tt), the rhs of the ODE."""
    X, X_t = unpack(y)
    X_x0 = diff(X)
    X_x0x0 = diff(X, d=2)
    n_x = n0_x0 / X_x0**2 - n0 * X_x0x0 / X_x0**3
    
    u_xx = (diff(X_t, d=2)) / X_x0**2 - diff(X_t) * X_x0x0 / X_x0**3
    u_xx[0] = u_xx[-1] = 0  # Zero-laplacian boundary conditions
    
    a_ext = - w**2 * X
    a_ext += V0_m * np.exp(-X**2/2/sigma**2) * X/sigma**2
    X_tt = a_ext - g_m * n_x + nu * u_xx
    return pack(X_t, X_tt)


Tw = 2*np.pi / w  # Trapping period
T = 20*Tw
Nt = 200  # Number of evaluation times
t_eval = np.linspace(0, T, Nt)

y0 = pack(X0, 0*X0)  # Packed initial state
res = sp.integrate.solve_ivp(
    compute_dy_dt, y0=y0, t_span=(0, T), t_eval=t_eval, method="BDF")

if not res.success:
    print(res.message)

# Plot the results and make a movie.
import matplotlib.pyplot as plt

from phys_581.contexts import FPS

ts = res.t

# Get the density from the solution
X, X_t = res.y.reshape((2, Nx, Nt))
X_x0 = np.transpose([diff(x) for x in X.T])
n = n0[:, None] / X_x0
    
# The following is a little complicated - it translates from the hydrodynamic
# picture with density n into the shallow-water picture with a fluid of height
# h above the stream bed of hight b.  The latter is more familiar (water in a glass)
# than the compressible-gas fluid dynamics, allowing us to better assess
# the qualitative nature of the solution

x_ = np.linspace(X.min(), X.max(), 200)
V_ext = w**2 * X**2 / 2
V_ext += V0_m * np.exp(-X**2/2/sigma**2)
b = V_ext / g_m

b_ = w**2 * x_**2 / 2
b_ += V0_m * np.exp(-x_**2/2/sigma**2)
b_ /= g_m 

h = b + n

fig, ax = plt.subplots(figsize=(6, 3), layout="constrained")
for i in FPS(Nt, fig=fig, embed=True, fps=10, filename="_static/SW.mp4"):
    t = ts[i]
    ax.cla()
    ax.plot(x_, b_, 'k-')
    ax.fill_between(X[:, i], b[:, i], h[:, i], color='cyan')
    ax.set(title=rf"$t={t/Tw:.2f}T_{{\omega}}$",
           ylabel="Height", xlabel="$x$",
           xlim=(X.min(), X.max()), ylim=(0, h.max()));

For another example, see Model Fitting.

Learning Outcomes#

In addition to the Specific Skills listed in the Syllabus, by the end of the course, students should be able to:

  • Produce publication quality figures and animations (e.g., 2D and 3D plots, animations, tables, etc.) following best practices of visualization of quantitative data (i.e., as espoused by E. Tufte.)

  • Produce a manuscript meeting all formatting requirements for a paper in their field of focus, or for a thesis. (E.g., using a suitable LaTeX template and conforming to the submission requirements for mathematics, figures, citations, typesetting, etc.)

  • Work with data in a variety of formats for analysis, sharing, and reproducible science.

  • Use version control system to managing code or documents, and establish a portfolio of code on sites like GitLab or GitHub with testing via continuous integration (CI) and documentation.

  • Profile and optimize their code.

  • Use a code editor that supports regexp search-and-replace, linting, checking, etc. (E.g., Emacs, Vi, VS Code).

  • Numerically compute integrals and derivatives with quantified error estimates.

  • Use numerical linear algebra libraries to diagonalize and factorize matrices: QR, SVD, Cholesky.

  • Generate pseudo-random numbers with any desired distribution.

  • Use MCMC techniques to characterize and quantify Bayesian posteriors for fitting and model selection.

  • Use local optimization techniques (gradient descent, Nelder-Meade, BFGS, Broyden) with and without gradients.

  • Use techniques like Runge-Kutta to solve initial value problems.

  • Derive scaling results to validate these techniques.

Notes#

This is a live working document hosted on Read The Docs that will be used to collect and display additional information about the course, including:

These documents are built using JupyterBook (see JupyterBook Demonstration) and include all of the source code needed to generate the figure, plots etc. For example, to see how a figure was made, look in the preceding code cell. The complete source code for this documentation is available at wsu-courses/physics-581-physics-inspired-computation.

Funding Statement#

https://nsf.widen.net/content/txvhzmsofh/png/

Some of the material presented here is based upon work supported by the National Science Foundation under Grant Number 2309322. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.