Skip to content

Commit 0c68700

Browse files
Merge pull request #33 from syungb/syungb/1d-linear-convection-sim-demo
Add post on writing simulation code with Java arrays
2 parents 4969b2f + 8edfaca commit 0c68700

File tree

2 files changed

+184
-1
lines changed

2 files changed

+184
-1
lines changed

site/db.edn

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,14 @@
6262
:image "https://avatars.githubusercontent.com/u/11580?v=4"
6363
6464
:affiliation [:clojurecamp]
65-
:links [{:icon "github" :href "https://github.com/samumbach"}]}]
65+
:links [{:icon "github" :href "https://github.com/samumbach"}]}
66+
{:id :syungb
67+
:name "Siyoung Byun"
68+
:url "https://github.com/syungb"
69+
:image "https://avatars.githubusercontent.com/u/1319016?v=4"
70+
71+
:affiliation [:scicloj]
72+
:links [{:icon "github" :href "https://github.com/syungb"}]}]
6673

6774
:affiliation
6875
[{:id :clojure.core
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
^{:kindly/hide-code true
2+
:clay
3+
{:title "Simulating 1-D Convection in Clojure — From Equations to Arrays"
4+
:quarto {:author :syungb
5+
:description "A quick exploration to simulate a classic fluid dynamics equation in Clojure using Java arrays."
6+
:type :post
7+
:date "2025-07-15"
8+
:category :clojure
9+
:tags [:computationalfluiddynamics :cfd :python :conversion]}}}
10+
(ns scicloj.cfd.intro.linear-1d-convection-with-array
11+
(:require
12+
[scicloj.kindly.v4.api :as kindly]
13+
[scicloj.kindly.v4.kind :as kind]))
14+
15+
^:kindly/hide-code
16+
(def tex (comp kindly/hide-code kind/tex))
17+
^:kindly/hide-code
18+
(def compose-plot-data #(into [] (map (fn [x u] (hash-map :x x :y u)) % %2)))
19+
;;
20+
;; Earlier this year I gave a [talk](https://youtu.be/RXr9i-aw0lM?si=NpnGOHh8TjC1gufI) at the first online
21+
;; [Scinoj Light Conference](https://scicloj.github.io/scinoj-light-1/), sharing a [ongoing project](https://github.com/scicloj/cfd-python-in-clojure)
22+
;; to port [Computational Fluid Dynamics(CFD) learning materials](https://github.com/barbagroup/CFDPython)
23+
;; from Python to Clojure.
24+
;;
25+
;; In this post, I'll demonstrate a simple one-dimensional linear [convection](https://en.wikipedia.org/wiki/Convection) simulation implemented
26+
;; in Clojure using Java primitive arrays. This example shows a glimpse into the porting effort,
27+
;; with a focus on expressing numerical simulations using only built-in Clojure functions.
28+
;;
29+
;; ## The Equation
30+
;;
31+
;; _(This section won't take up too much of your time...)_
32+
;;
33+
;; We're going to simulate the 1-D linear convection equation:
34+
;;
35+
(tex "\\frac{\\partial u }{\\partial t} + c \\frac{\\partial u}{\\partial x} = 0")
36+
;;
37+
;; This explains how the flow velocity `u` changes over time `t` and position `x`,
38+
;; with `c` which is the wave speed.
39+
;;
40+
;; Instead of focusing on the physical interpretation(since I am no expert),
41+
;; the main focus on this post will be its implementation side - expressing it numerically and coding it in **Clojure**!
42+
;;
43+
;; Using a finite-difference scheme, the discretized form becomes:
44+
;;
45+
(tex "\\frac{u_i^{n+1} - u_i^n}{\\Delta t} + c \\frac{u_i^n - u_{i-1}^n}{\\Delta x} = 0")
46+
;;
47+
;; Then, solving for `u_i^{n+1}` gives:
48+
;;
49+
(tex "u_i^{n+1} = n_i^n - c \\frac{\\Delta t}{\\Delta x}(u_i^n - u_{i-1}^n)")
50+
;;
51+
;; ## Initial Setup
52+
;;
53+
;; We begin by defining initial simulation parameters to start:
54+
;;
55+
;; * nx: number of sliced steps for spatial point `x` from `x-start` and `x-end`
56+
;; * nt: number of time steps we want to propagate
57+
;; * dx: each sliced `x` calculated from `dx = (x-end - x-start) / (nx - 1)`
58+
;; * dt: sliced each time step
59+
;; * c: speed of wave
60+
;;
61+
(def init-params
62+
{:x-start 0
63+
:x-end 2
64+
:nx 41
65+
:nt 25
66+
:dx (/ (- 2 0) (dec 41))
67+
:dt 0.025
68+
:c 1})
69+
;;
70+
;; ### Creating the `x` Grid
71+
;;
72+
;; With the given `init-params` we defined earlier, we create a float-array of spatial points `x`:
73+
;;
74+
(def array-x (let [{:keys [nx x-start x-end]} init-params
75+
arr (float-array nx)
76+
step (/ (- x-end x-start) (dec nx))]
77+
(dotimes [i nx]
78+
(aset arr i (float (* i step))))
79+
arr))
80+
;;
81+
^:kindly/hide-code array-x
82+
;;
83+
;; ### Defining Initial Flow Velocity Condition
84+
;;
85+
;; The initial flow velocity(when `t = 0`) is 2 when `x ∈ [0.5, 1.0]`, and is 1 elsewhere:
86+
;;
87+
(def init-cond-fn #(float (if (and (>= % 0.5) (<= % 1.0)) 2 1)))
88+
;;
89+
(def array-u
90+
(let [nx (:nx init-params)
91+
u (float-array nx)]
92+
(dotimes [i nx]
93+
(let [x-i (aget array-x i)
94+
u-i (init-cond-fn x-i)]
95+
(aset u i u-i)))
96+
u))
97+
98+
(def init-array-u (float-array array-u))
99+
^:kindly/hide-code init-array-u
100+
;;
101+
;; We can visualize this initial `u`:
102+
^:kindly/hide-code
103+
(kind/vega-lite
104+
{:mark "line"
105+
:width 500 :height 300
106+
:encoding {:x {:field "x" :type "quantitative"}
107+
:y {:field "y" :type "quantitative"}}
108+
:data {:values (compose-plot-data array-x init-array-u)}})
109+
;;
110+
;; ### Wait, Why `dotimes`?
111+
;;
112+
;; Since we're working with mutable Java arrays(float-array), `dotimes` is an efficient choice here.
113+
;; Because it gives direct, index-based iteration. And it pairs naturally with `aget` and `aset`
114+
;; for reading and writing array values.
115+
;;
116+
;; ## Implementing and the Simulation
117+
;;
118+
;; With the initial setup complete, we now apply the discretized convection equation at each time step.
119+
;;
120+
;; ### Step Function
121+
;;
122+
;; Given the previous time step's `array-u` and the `init-params`,
123+
;; We compute and mutate the flow velocity in-place:
124+
;;
125+
(defn mutate-linear-convection-u
126+
[array-u {:keys [nx c dx dt]}]
127+
(let [u_i (float-array array-u)]
128+
(dotimes [i (- nx 2)]
129+
(let [idx (inc i)
130+
un-i (aget u_i idx)
131+
un-i-1 (aget u_i i)
132+
new-u-i (float (- un-i (* c (/ dt dx) (- un-i un-i-1))))]
133+
(aset array-u idx new-u-i))))
134+
array-u)
135+
;;
136+
;; ### Time Integration
137+
;;
138+
;; We run the step function `nt` times to run our simulation over time.
139+
;;
140+
(defn simulate!
141+
[array-u {:keys [nt] :as init-params}]
142+
(loop [n 0]
143+
(if (= n nt)
144+
array-u
145+
(do (mutate-linear-convection-u array-u init-params) (recur (inc n))))))
146+
;;
147+
;; Finally, we visualize the resulting `array-u`:
148+
149+
(simulate! array-u init-params)
150+
151+
^:kindly/hide-code
152+
(kind/vega-lite
153+
{:mark "line"
154+
:width 500 :height 300
155+
:encoding {:x {:field "x" :type "quantitative"}
156+
:y {:field "y" :type "quantitative"}}
157+
:data {:values (compose-plot-data array-x array-u)}})
158+
;;
159+
;; The plot shows how the flow velocity shifts from left to right over time,
160+
;; while also becoming smoother. Nice!
161+
;;
162+
;; ## The Summary
163+
;;
164+
;; This Simple example demonstrates a simulation process using low-level Java primitive arrays in Clojure.
165+
;;
166+
;; Choosing this approach provided mutable, non-persistent data structures. While this deviates from idiomatic Clojure,
167+
;; it offers significant performance benefits for big-size numerical simulations.
168+
;; However, it comes with trade-offs; by opting for mutability, we give up the guarantees of immutability
169+
;; and structural sharing, making the code less safe and more error-prone.
170+
;;
171+
;; As the porting project continues, we plan to evolve the design to better align with idiomatic Clojure principles.
172+
;; Stay tuned!
173+
174+
(comment
175+
(require '[scicloj.clay.v2.api :as clay])
176+
(clay/make! {:source-path "scicloj/cfd/intro/linear_1d_convection_with_array.clj"}))

0 commit comments

Comments
 (0)