Welcome!

A personal blog for some personal projects. I'm interested in physical simulation, and am working on a Wasm-based fluid solver at sph.tarinyoom.io.

Under Pressure

This is a continuation of my $N$-part series on getting this SPH renderer working. My goal here is to model some kind of liquid. SPH is a technique that supposedly models water flow pretty well. So we’ll see how well this works in the browser at interactive rates.

First, a little bit of math. We start with the incompressible Navier-Stokes equations:

$$\mu \nabla^2 u - \nabla p + f_{ext} = \rho\frac{D u}{D t},$$

subject to

$$\nabla \cdot u = 0.$$

$u$, $p$, $f_{ext}$, and $\rho$ are all functions defined over our spatial domain representing velocity, pressure, external force (gravity), and density, respectively. $\mu$ is a viscosity constant. $\frac{D u}{D t}$ is the material derivative of the velocity of the fluid, which is just acceleration for our particle-based approach. The second equation is our incompressibility constraint, which requires that nowhere in our fluid do we expand or compress our material.

The first equation may seem intimidating, but the key point is that it relates the forces on the left hand side of the equation to the acceleration on the right. So in that sense, it’s a lot like the familiar $F = ma$ equation of Newtonian physics. The three forces the fluid experiences are a viscosity force (think friction), a pressure gradient force (where fluid is pushed from high to low pressure), and an external force (which is just gravity for now). If we can compute these forces and the fluid density, then we can compute the acceleration of the fluid and see it move.

As with many differential equations, it’s hard to find an exact solution. So instead we need to choose some discrete approximation, which will be SPH. I’ll lean heavily on the approach in J. Monaghan’s 1988 paper, “An Introduction to SPH”. The approach rests on this idea that you can approximate smooth functions (pressure, density, velocity) by storing these values only at discrete particles by smoothing them over with convolutional kernels. Once you have the smoothed functions, you can do the calculus you need to solve our Navier-Stokes equations.

So to compute densities, we have:

$$\rho(x) \approx \sum_{i} W(x - x_i) m_i,$$

and to compute our pressure gradient, we have:

$$\nabla p(x) \approx \rho(x) \sum_{i} \left(\frac{p(x)}{\rho(x)^2} + \frac{p(x_i)}{\rho(x_i)^2}\right) \nabla W(x - x_i) m_i.$$

which are consequences of this smoothing approach (smoothed with kernel $W$). Finally, we’ll need to compute our pressure function, for which we can use an equation of state (EoS). This is a cheap way of relating densities to pressures while still (mostly) respecting our divergence-free constraint $\nabla \cdot u = 0$. This EoS will likely need to be refined later.

So our algorithm is:

  1. From our particle positions, use kernel smoothing technique to compute our fluid density at each particle
  2. From these density values, use our EoS to compute corresponding pressures
  3. Use kernel smoothing again to compute our pressure gradient
  4. Use our pressure gradient and density to accelerate our particle
  5. Add accelerations from other effects (viscosity, external force)
  6. Repeat!

So even though density, pressure, and acceleration are continuous functions over our spatial domain, we now have an algorithm that can approximately solve the Navier-Stokes equation by only computing values at discrete points.

Here are some visualizations. First, densities:

Density visualization

Higher density regions are red, while lower density regions are blue.

Here are pressures, computed using the EoS:

Pressure visualization

It looks kind of similar to density but changes more sharply from low to high. So this is to say that our pressure force will be harsher than just “linear in our change in density”, which will help us better enforce our zero-divergence condition.

Finally, here is most of the algorithm implemented. I’ve skipped viscosity for now.

Fluid visualization

So we already get some fluid-like effects. We see our fluid flowing from the denser region in the lower left to the sparser region in the lower right. We also see some splashing, which is good.

It also seems to be gaining energy, which might be fixed by implementing viscosity. That seems like relatively low-hanging fruit.

This doesn’t look that incompressible though, especially looking at the bottom left. The wave-like behavior in the lower left looks fluid-like, which is good, but is showing pressure waves, which we don’t want in our incompressible fluid. In terms of our mathematical equations, we imposed our incompressibility as a zero-divergence constraint, and chose an EoS that could somewhat respect that constraint. But that EoS may need to be refined.

Kernel Visualization

The core idea of SPH is to use values stored at discrete particles to model the differentiable functions used in partial differential equations (PDEs). We do this by interpolating between our stored values using a convolutional kernel, so that our differentiable function looks something like a weighted average of all nearby particles.

For example, say we have a discrete set of points $X = {x_1, \dots, x_n} \subset \mathbb{R}^3$, and a pressure function defined over that set of points $\hat p: X \rightarrow \mathbb{R}$. We know what $\hat p(x_1)$ and $\hat p(x_2)$ are, but not $\hat p(y)$ for any $y \notin X$. Can we construct a differentiable pressure function $p$ defined over all of $\mathbb{R}^3$?

The answer is yes, which I’ll be implement and explain over the course of this project. The first step will be to implement a convolutional kernel, which will be the core mechanism that smooths our discrete values into continuous functions. Below is a visualization of that kernel affixed to a single point. No fluid physics yet, just some colors representing the value of the kernel as the particle travels through space.

Kernel visualization

So I’ve chosen $p_1$ to be my particle of interest, and for each other particle $p$ in the simulation, I evaluate the kernel at $p$’s distance from $p_1$. If $p$ is close to $p_1$ it’ll be red (high kernel value), and as $p$ drifts farther away, it fades to blue. At some threshold of distance $p$ is just the same blue, no matter how much farther it gets. Nothing too profound here, just measuring distances, passing them through this kernel, and rendering colors accordingly.

But what if we affixed this kernel to every particle in the simulation, and summed their contributions? Instead of the color at $p$ being a measure of its distance from $p_1$, it now becomes a measure of its kernel-weighted proximity to all other particles. Where particles are dense, they tend to be more red, and where they are sparse, they tend to be more blue:

Density visualization

This computation on our particles will be our density calculation, which will be required in the computation of other fluid properties. Computationally, this is still fairly straightforward: around 100 lines of simulation code. But even so, we’ve already been able to approximate a continuous function using only finitely many points.

Browser Dynamics

This is my first post on implementing fluid dynamics in the browser. The goal is to implement a real-time fluid solver in my browser. Previously, I’d implemented an online ODE integrator for comparing explicit Euler, implicit Euler, and RK4 integration methods for a particle in a gravity field. I don’t plan on working on that project further, but it did provide me with some context for what I like and what I don’t like.

What I liked:

  1. Physical simulation in the browser. It just seems like an interesting medium. I’ve seen a fair number of physics solvers that follow a common paradigm: run a monstrous executable built on the tears of a few generations of Ph.D. students, and then make sense of the result. But with modern improvements to JavaScript engines, I don’t see why we can’t model simple physical phenomena in a more interactive way.
  2. A focus on using this kind of app as a learning tool. At some level, I don’t think the math underpinning these phenomena is that hard… once you get it. But to get it, you have to have the right picture in your head of what’s going on. A real-time browser app is just a modern way of getting that image in your head.

What I want to do differently:

  1. I want to solve a PDE instead of an ODE. This is less of a philosophical change and more just seeking a challenge. It’s also the type of differential equation that’s closer to the world of AI. So that’s interesting.
  2. I added way too many “things” last time. I got involved with backending some of the numerical computation with a Lambda, as well as procedurally generated sound, and some other unneeded frills. So this time, narrowly scoping the project is a priority.
  3. I want this to be real-time. Again with the challenge part, this just seems like a tricky problem: how can you get this little browser to simulate the right thing with its tight computational budget? Some potential tasking will involve benchmarking, cross-browser validation, and perhaps a foray into WASM, à la Rust.

So far, I’ve implemented a simple particles-in-a-box example, using a library called three.js. This is a lightweight library that sits atop WebGL, providing the structure of a scene and basic rendering.

I’ve added gravity and some simple elastic collisions against invisible walls:

Particles in a box

At this point it doesn’t really look like a fluid, as much as a lot of tiny bouncing balls. The goal will be to implement smoothed-particle hydrodynamics (SPH), a technique for approximating the continuous properties of a fluid with discrete particles by interpolating between them via smooth kernel functions. Or so the lore goes.

Dependency Shedding

This is the second time I’ve set up a personal site. Previously, I had a simple website I’d made with a tool called create-react-app, with components from MaterialUI. It had a few cards pointing to personal projects I’ve worked on in the past, like so:

Old Personal Website

This worked well, until the years passed and my dependabot began harassing me with streams of urgent security vulnerabilities that needed my attention. Of course, I could npm audit fix to upgrade to newer patched versions of my dependencies. But even then, my project needed further updating. create-react-app had lost popularity to next.js, and I now had to reason about an entirely different framework for setting up my website. The entire React ecosystem seemed to have reinvented itself.

I once had a professor tell me that learning web technologies is like catching a tiger by its tail: it’s hard to catch on, and once you do, you need to hold on tight. For me, front-end frameworks may just not be a tiger I’m trying to catch.

Some key takeaways:

  1. Dependencies can bring quick functionality, but they’re also additional developer responsibility. Make sure you understand the role of the recursive dependencies you’re bringing in before committing to them.
  2. Take time to understand the stability of the technology. Just because a technology is widely used doesn’t mean it won’t change. In some cases, because it’s widely used, the technology may change more as the community demands richer and richer features.
  3. Unstable technologies are okay, but they should really be in your most active area of development. Because most of my current work deals in high-performance computing, I have less bandwidth to keep abreast of trends in front-end frameworks.

My current website just uses plain HTML + CSS. That sounds just fine to me for now.

Hello, World!

Welcome to my dev journal!

I started this journal as a replacement for my personal site to better document my personal journey as a developer. While it’s fun to write code and build functionality, it’s easy to get lost in the weeds and forget about the motivations for writing that code and the outcome of building that functionality.

This journal is my approach for reflecting and ensuring that my actions priorities make sense from a bird’s-eye view.