Using Wolfram Language and WLJS libraries
In this notebook, we will explore a simple technique for simulating 2D incompressible fluids for visual effects. This work is mostly based on Jos Stam. Stable Fluids SIGGRAPH 1999 as well as a tutorial by Karl Sims
Download original notebookWorking with a Gridβ
Here we will use the Euler method to represent a fluid, i.e., by storing velocity as a vector field discretized to a uniform grid.
grid = Table[Cross[{i,j,0}, {0,0,1}][[;;2]], {i, 5}, {j, 5}]; % // MatrixForm
((*GB[*){{((*GB[*){{1}(*||*),(*||*){-1}}(*]GB*))(*|*),(*|*)((*GB[*){{2}(*||*),(*||*){-1}}(*]GB*))(*|*),(*|*)((*GB[*){{3}(*||*),(*||*){-1}}(*]GB*))(*|*),(*|*)((*GB[*){{4}(*||*),(*||*){-1}}(*]GB*))(*|*),(*|*)((*GB[*){{5}(*||*),(*||*){-1}}(*]GB*))}(*||*),(*||*){((*GB[*){{1}(*||*),(*||*){-2}}(*]GB*))(*|*),(*|*)((*GB[*){{2}(*||*),(*||*){-2}}(*]GB*))(*|*),(*|*)((*GB[*){{3}(*||*),(*||*){-2}}(*]GB*))(*|*),(*|*)((*GB[*){{4}(*||*),(*||*){-2}}(*]GB*))(*|*),(*|*)((*GB[*){{5}(*||*),(*||*){-2}}(*]GB*))}(*||*),(*||*){((*GB[*){{1}(*||*),(*||*){-3}}(*]GB*))(*|*),(*|*)((*GB[*){{2}(*||*),(*||*){-3}}(*]GB*))(*|*),(*|*)((*GB[*){{3}(*||*),(*||*){-3}}(*]GB*))(*|*),(*|*)((*GB[*){{4}(*||*),(*||*){-3}}(*]GB*))(*|*),(*|*)((*GB[*){{5}(*||*),(*||*){-3}}(*]GB*))}(*||*),(*||*){((*GB[*){{1}(*||*),(*||*){-4}}(*]GB*))(*|*),(*|*)((*GB[*){{2}(*||*),(*||*){-4}}(*]GB*))(*|*),(*|*)((*GB[*){{3}(*||*),(*||*){-4}}(*]GB*))(*|*),(*|*)((*GB[*){{4}(*||*),(*||*){-4}}(*]GB*))(*|*),(*|*)((*GB[*){{5}(*||*),(*||*){-4}}(*]GB*))}(*||*),(*||*){((*GB[*){{1}(*||*),(*||*){-5}}(*]GB*))(*|*),(*|*)((*GB[*){{2}(*||*),(*||*){-5}}(*]GB*))(*|*),(*|*)((*GB[*){{3}(*||*),(*||*){-5}}(*]GB*))(*|*),(*|*)((*GB[*){{4}(*||*),(*||*){-5}}(*]GB*))(*|*),(*|*)((*GB[*){{5}(*||*),(*||*){-5}}(*]GB*))}}(*]GB*))
Then one can easily visualize it as a vector field:
grid // ListVectorPlot
(*VB[*)(FrontEndRef["7fa3d105-973d-43df-aa2e-88d286b02c82"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKm6clGqcYGpjqWpobp+iaGKek6SYmGqXqWlikGFmYJRkYJVsYAQCGjBWk"*)(*]VB*)
For the best performance, we will use Map or MapIndexed with a pure function inside. Then Wolfram Kernel is able to use JIT compilation. For example:
Map[Function[vector, ((*GB[*){{0(*|*),(*|*)-1}(*||*),(*||*){1(*|*),(*|*)0}}(*]GB*)) . vector], grid, {2}] ListVectorPlot[%]
{{{1,1},{1,2},{1,3},{1,4},{1,5}},{{2,1},{2,2},{2,3},{2,4},{2,5}},{{3,1},{3,2},{3,3},{3,4},{3,5}},{{4,1},{4,2},{4,3},{4,4},{4,5}},{{5,1},{5,2},{5,3},{5,4},{5,5}}}
(*VB[*)(FrontEndRef["9d29d953-9bbb-46aa-805e-391ff5054311"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKW6YYWaZYmhrrWiYlJemamCUm6loYmKbqGlsapqWZGpiaGBsaAgCEmxVJ"*)(*]VB*)
Custom Syntax Sugarβ
Another possibility to visualize using pure syntax sugar would be to define our own output form for a single velocity vector.
The most efficient way is to define a decorator function in Javascript. Let it be a small arrow pointing to a desired direction.
.js core.ArrowGuide = async (args, env) => { const dir = await interpretate(args[0], env); const mag = dir.map((el)=>el*el).reduce((c,a) => c+a); const angle = Math.round(180 * Math.atan2(dir[0], dir[1]) / Math.PI); console.log(angle); env.element.style.transform = `rotate(${angle}deg)`; if (mag < 0.01) { //do not display if it is too small env.element.style.opacity = 0.5; env.element.innerHTML = "."; } else { env.element.innerHTML = "↑"; } }
core.ArrowGuide = async (args, env) => { const dir = await interpretate(args[0], env); const mag = dir.map((el)=>el*el).reduce((c,a) => c+a); const angle = Math.round(180 * Math.atan2(dir[0], dir[1]) / Math.PI); console.log(angle); env.element.style.transform = `rotate(${angle}deg)`; if (mag < 0.01) { //do not display if it is too small env.element.style.opacity = 0.5; env.element.innerHTML = "."; } else { env.element.innerHTML = "↑"; } }
Now we define a standard form ArrowGuide, which will use the defined Javascript function to display itself inside a cell.
ArrowGuide /: MakeBoxes[a_ArrowGuide, StandardForm] := ViewBox[a//First, a]
The first argument will be an actual expression used as input; First will break our wrapper and substitute an original vector.
ArrowGuide[{1,1}]
(*VB[*)({1, 1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKkAEwCJmgvW"*)(*]VB*)
Now one can do some cool tricks like this one:
(*VB[*)({1, 1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKkAEwCJmgvW"*)(*]VB*) . (*VB[*)({1, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKnIBGIGAImWC9U="*)(*]VB*)
1
Now we can basically apply this decoration wrapper to our matrix using multidimensional Map function and then again output it as a matrix
Map[ArrowGuide, grid, {2}] // MatrixForm
((*GB[*){{(*VB[*)({1, -1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKnI/A8EAJOMD9E="*)(*]VB*)(*|*),(*|*)(*VB[*)({2, -1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBPEy/wMBAJOVD9I="*)(*]VB*)(*|*),(*|*)(*VB[*)({3, -1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAYyMv8DAQCTng/T"*)(*]VB*)(*|*),(*|*)(*VB[*)({4, -1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBDEy/wMBAJOnD9Q="*)(*]VB*)(*|*),(*|*)(*VB[*)({5, -1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAUyMv8DAQCTsA/V"*)(*]VB*)}(*||*),(*||*){(*VB[*)({1, -2})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKnI/Pf//38Ak4gP0A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({2, -2})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBPEy//3//x8Ak5EP0Q=="*)(*]VB*)(*|*),(*|*)(*VB[*)({3, -2})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAYyMv/9//8fAJOaD9I="*)(*]VB*)(*|*),(*|*)(*VB[*)({4, -2})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBDEy//3//x8Ak6MP0w=="*)(*]VB*)(*|*),(*|*)(*VB[*)({5, -2})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAUyMv/9//8fAJOsD9Q="*)(*]VB*)}(*||*),(*||*){(*VB[*)({1, -3})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKnI/Pv//38Ak4QPzw=="*)(*]VB*)(*|*),(*|*)(*VB[*)({2, -3})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBPEy//7//x8Ak40P0A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({3, -3})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAYyMv/+//8fAJOWD9E="*)(*]VB*)(*|*),(*|*)(*VB[*)({4, -3})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBDEy//7//x8Ak58P0g=="*)(*]VB*)(*|*),(*|*)(*VB[*)({5, -3})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAUyMv/+//8fAJOoD9M="*)(*]VB*)}(*||*),(*||*){(*VB[*)({1, -4})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKnI/PP//38Ak4APzg=="*)(*]VB*)(*|*),(*|*)(*VB[*)({2, -4})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBPEy//z//x8Ak4kPzw=="*)(*]VB*)(*|*),(*|*)(*VB[*)({3, -4})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAYyMv/8//8fAJOSD9A="*)(*]VB*)(*|*),(*|*)(*VB[*)({4, -4})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBDEy//z//x8Ak5sP0Q=="*)(*]VB*)(*|*),(*|*)(*VB[*)({5, -4})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAUyMv/8//8fAJOkD9I="*)(*]VB*)}(*||*),(*||*){(*VB[*)({1, -5})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBKnI/P3//38Ak3wPzQ=="*)(*]VB*)(*|*),(*|*)(*VB[*)({2, -5})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBPEyf////x8Ak4UPzg=="*)(*]VB*)(*|*),(*|*)(*VB[*)({3, -5})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAYyMn////8fAJOOD88="*)(*]VB*)(*|*),(*|*)(*VB[*)({4, -5})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBDEyf////x8Ak5cP0A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({5, -5})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JZAUyMn////8fAJOgD9E="*)(*]VB*)}}(*]GB*))
Let's define a helper function
showAsMatrix[l_] := Map[ArrowGuide, l, {2}] // MatrixForm
Divergenceβ
Conservation of mass for incompressible fluid dictates
where is velocity vector field we played with earlier - grid.
Exampleβ
Let us start with a simples example
grid = Table[{0,0}, {i,5}, {j,5}]; grid // showAsMatrix
((*GB[*){{(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}}(*]GB*))
And put a single vector in the middle
grid = ((*GB[*){{(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 1})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMmSBlAImRC9U="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}(*||*),(*||*){(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)(*|*),(*|*)(*VB[*)({0, 0})(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRAeF5BwLCrKL3cvzUxJTWOCqfDJLC7JBNIMYAIAiY0L1A=="*)(*]VB*)}}(*]GB*));
To visualize the divergence, one can use DensityPlot
{intX, intY} = { ListInterpolation[grid[[All,All,1]]], ListInterpolation[grid[[All,All,2]]] }; (*BB[*)(*interpolate data for the convenience*)(*,*)(*"1:eJxTTMoPSmNhYGAo5gcSAUX5ZZkpqSn+BSWZ+XnFaYwgCS4g4Zyfm5uaV+KUXxEMUqxsbm6exgSSBPGCSnNSg9mAjOCSosy8dLBYSFFpKpoKkDkeqYkpEFXBILO1sCgJSczMQVYCAOFrJEU="*)(*]BB*) CoolColor[ z_ ] := RGBColor[z, 0.5, 1 - z]; (*BB[*)(*find divergence*)(*,*)(*"1:eJxTTMoPSmNhYGAo5gcSAUX5ZZkpqSn+BSWZ+XnFaYwgCS4g4Zyfm5uaV+KUXxEMUqxsbm6exgSSBPGCSnNSg9mAjOCSosy8dLBYSFFpKpoKkDkeqYkpEFXBILO1sCgJSczMQVYCAOFrJEU="*)(*]BB*) Div[{intX[x,y], intY[x,y]}, {x,y}]; ContourPlot[%, {x,1,5}, {y,1,5}, ColorFunction->CoolColor]
(*VB[*)(FrontEndRef["15f4029b-3d6d-46e2-8893-95deb4f9c83e"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKG5qmmRgYWSbpGqeYpeiamKUa6VpYWBrrWpqmpCaZpFkmWxinAgB8URWN"*)(*]VB*)
From a physical point of view, it means that there is a source node at the bottom and a drain at the top, which should not be a feature of a closed system with incompressible fluid.
Removing the Divergenceβ
One can try to solve this problem iteratively by taking a field with non-zero divergency and apply artificial tranformation on it to make it satisfy the equation.
There is a clever algorithm for removing the divergence of an arbitrary 2D vector field, which acts like a cellular automaton