Functional Fluid Dynamics in Clojure
bestinclass.dk
bestinclass.dk
pure function diffusion(x0) result(x)
real, intent(in), dimension(:,:) :: x0
real, intent(out),dimension(:,:) :: x
x = x0(2:n+1,1:n ) &
+ x0(0:n-1,1:n ) &
+ x0(1:n ,2:n+1) &
+ x0(1:n ,0:n-1) &
-a*x0(1:n ,1:n )
end function
Some things to note:- The "pure" keyword guarantees that this function has no side effects.
- No do loops are needed! Fortran array slicing is very handy.
- The compiler will convert this to use SIMD instructions
- Adding some OpenMP hints to make it run on all cores is also very easy.
So this type of code in Fortran is short, very easy to understand and you are guaranteed extreme performance. Maybe functional programming has some benefits when you're dealing with more complex datastructures (for instance I'm working on a code right now which uses parallel octrees, kind off a pain in Fortran), but for simple things like this, I fail to see the point.
I want to believe, so perhaps someone here can enlighten me?
However, don't lose hope just yet. Languages like Haskell or OCaml, with more mature compilers, might not have such limitations. Also, I think that while many small examples might turn out essentially equivalent when comparing imperative and functional styles, the difference becomes more pronounced on a whole-program scale. Pure functions are easier to reason about, and compose better.
Fortran, C and C++ will probably keep their place as tools to use when absolute best performance is needed, but with modern compilers and virtual machines, functional languages do not have to be slow either.
Finally, I hope to see functional programming languages succeed simply because functional programming is a lot of fun.
def __init__(self, mesh):
div_matrix = op_mesh.n_to_nm1_boundary.T * op_mesh.F_matrix
boundary_matrix = op_mesh.F_matrix - op_mesh.FB_matrix
self.C_matrix = sparse.vstack((div_matrix, boundary_matrix))
self.CCT_matrix = self.C_matrix * self.C_matrix.T
def Project(self, velocities):
lam = linalg.cg(-self.CCT_matrix, self.C_matrix * velocities)[0]
return velocities + self.C_matrix.T * lam
It's pretty simple (constrained least squares optimization) and it's missing some stuff (preconditioner), but it runs fast enough (~1 minute/frame for > 1M tetrahedra), and only required minimal testing: it's all built up from operators I was already using elsewhere. Since it's the fourth or fifth projection method I've tried for this code, that makes a big difference. The fact that I'm using Python is almost immaterial: all of the "interesting" code is library calls.Have you seen the FiPy project? It's a full-featured finite volume code in Python, which is very easy to extend (they did struggle with performance in early releases, not sure how the current status is).
I guess that's the area where dynamic or functional languages can be useful, in that they are easier to build generic libraries for, and can give rise to codes with easier extensibility.
I kinda understand what do you mean by whole-program scale, but in fact, most of our programs aren't much bigger than this! Typically, the number of lines of code doesn't exceed 50k.
I agree about functional programming being fun, but unfortunately I don't think my coworkers would agree, and especially not the companies funding our research!
(let [sum 0.0
count 0.0]
(convolution 1
(+= sum %1)
(+= count 1))
(/ sum count))
"convolution" is a keyword that iterates over the neighbors (with a radius of 1, in this case), and does not overrun the boundaries of the source textures, hence the need to keep a running count.For a more extensive example, see this implementation of Sobel edge detection on the GPU: http://github.com/ztellman/penumbra/blob/master/src/example/...
!acc region
!acc end region
with the PGI Accelerator. It would have to be a pretty big loop for it to pay off, though, since transfer to/from the GPU is very expensive.I feel (as I do so often when projects written in certain languages that may or may not start with "Haskell" show up here) that the main take away is "Look, we can write working apps, too, and they're Better-Because-They're-Functional!" And yes, there are a lot of things that are better when you code them functionally, but simple numerical algorithms like this one play specifically to the strengths of imperative languages, and I see no clear benefit.
It's the same code smell that I get when people start putting together utility libraries, macro tools, and bytecode processors just so they can pretend to do functional programming in Java: if you find yourself struggling to do things non-idiomatically in one language that would be trivially natural in another, why not just switch languages? Especially if they all live on the same JVM and play fairly well together...
Please tell me he is kidding. Maybe I don't get out of my cave often enough but it seems uncommon that someone could grok FP bypassing C altogether.
Oh, and it isn't strange to grok FP bypassing C altogether.
If you're dealing with fluids with low diffusion compared to advection, some higher order Runge-Kutta scheme is typically used. For fluids with high diffusion, however, this will give you very high restrictions on the time step. For this cases, a hybrid scheme is often used, where the advection term is integrated explicitly using an Adam-Basforth scheme and the diffusion term is treated semi-implicitly with a Crank-Nicholson scheme. This means you have to solve additional linear equation systems for the velocities for each time step (Helmholtz type equations), but this is still faster since you can use longer time steps.
All of this may sound very complicated, but it all follows the same patterns, so it's really fairly straight forward.