Skip to content

Commit f3642ba

Browse files
Merge pull request #282 from coding-for-reproducible-research/julia_session_3
Julia session 3
2 parents 80fbda1 + 819d80d commit f3642ba

File tree

8 files changed

+910
-617
lines changed

8 files changed

+910
-617
lines changed

_toc.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ parts:
115115
- file: individual_modules/introduction_to_julia/arrays_and_matrices
116116
- file: individual_modules/introduction_to_julia/io
117117
- file: individual_modules/introduction_to_julia/package_management
118+
- file: individual_modules/introduction_to_julia/performant_code
119+
- file: individual_modules/introduction_to_julia/project_game_of_life
120+
- file: individual_modules/introduction_to_julia/implemented_game_of_life
121+
- file: individual_modules/introduction_to_julia/visualisation
122+
- file: individual_modules/introduction_to_julia/multi_file_project
118123
- file: course_homepages/python
119124
sections:
120125
- file: individual_modules/section_landing_pages/introduction_to_python
-24 Bytes
Binary file not shown.

individual_modules/introduction_to_julia/arrays_and_matrices.ipynb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,9 @@
16431643
"#### Exercise: assigning to a submatrix\n",
16441644
"\n",
16451645
"Use assignment with appropriate index slicing to change the values of the matrix of ones as follows:\n",
1646-
"$$\n",
1646+
"\n",
1647+
"\n",
1648+
"$\n",
16471649
"\\begin{bmatrix}\n",
16481650
" 1 & 1 & 1 & 1 \\\\\n",
16491651
" 1 & 1 & 1 & 1 \\\\\n",
@@ -1654,7 +1656,7 @@
16541656
" 0 & 1 & 1 & 0 \\\\\n",
16551657
" 0 & 1 & 1 & 0 \\\\\n",
16561658
"\\end{bmatrix}\n",
1657-
"$$"
1659+
"$"
16581660
]
16591661
},
16601662
{
@@ -2277,15 +2279,15 @@
22772279
],
22782280
"metadata": {
22792281
"kernelspec": {
2280-
"display_name": "Julia 1.10.9",
2282+
"display_name": "Julia 1.11.5",
22812283
"language": "julia",
2282-
"name": "julia-1.10"
2284+
"name": "julia-1.11"
22832285
},
22842286
"language_info": {
22852287
"file_extension": ".jl",
22862288
"mimetype": "application/julia",
22872289
"name": "julia",
2288-
"version": "1.10.9"
2290+
"version": "1.11.5"
22892291
}
22902292
},
22912293
"nbformat": 4,
Binary file not shown.
-16.4 KB
Loading

individual_modules/introduction_to_julia/implemented_game_of_life.ipynb

Lines changed: 137 additions & 143 deletions
Large diffs are not rendered by default.

individual_modules/introduction_to_julia/performant_code.ipynb

Lines changed: 639 additions & 424 deletions
Large diffs are not rendered by default.

individual_modules/introduction_to_julia/project_game_of_life.ipynb

Lines changed: 122 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,9 @@
6161
"```{admonition} Substep 1 Solution\n",
6262
":class: dropdown\n",
6363
"```Julia\n",
64-
"using Random\n",
65-
"\n",
6664
"const N = 100 # grid size\n",
6765
"# Create a random N×N Matrix of Bool\n",
68-
"grid = Matrix{Bool}(rand(Bool, N, N))\n",
66+
"grid = rand(Bool, N, N)\n",
6967
"```\n",
7068
"```"
7169
]
@@ -82,23 +80,18 @@
8280
},
8381
"source": [
8482
"### Substep 2: Neighbor Counting\n",
83+
"\n",
8584
"**Goal:** Count the number of live neighbors surrounding cell `(i, j)` in a finite `Matrix{Bool}` grid. \n",
8685
"```{admonition} Substep 2 Solution\n",
8786
":class: dropdown\n",
8887
"```Julia\n",
89-
"function count_neighbors_naive(grid::Matrix{Bool}, i::Int, j::Int)::Int\n",
90-
" cnt = 0\n",
91-
" for di in -1:1, dj in -1:1\n",
92-
" if di != 0 || dj != 0\n",
93-
" i2, j2 = i + di, j + dj\n",
94-
" if 1 ≤ i2 ≤ size(grid,1) && 1 ≤ j2 ≤ size(grid,2)\n",
95-
" cnt += grid[i2, j2]\n",
96-
" end\n",
97-
" end\n",
98-
" end\n",
99-
" return cnt\n",
88+
"function count_alive_neighbours(grid::Matrix{Bool}, i::Int, j::Int)::Int\n",
89+
" subset_i = max(1, i - 1):min(size(grid, 1), i + 1)\n",
90+
" subset_j = max(1, j - 1):min(size(grid, 2), j + 1)\n",
91+
" return sum(grid[subset_i, subset_j]) - grid[i, j] # don't count subject cell itself\n",
10092
"end\n",
10193
"```\n",
94+
"How do you think this could be made more efficient?\n",
10295
"```"
10396
]
10497
},
@@ -113,22 +106,38 @@
113106
"tags": []
114107
},
115108
"source": [
116-
"### Substep 3: Compute Next State Naïvely\n",
109+
"### Substep 3: Compute Next State\n",
110+
"\n",
117111
"**Goal:** Compute and return the next generation of the Game of Life grid by applying Conway’s rules, using a fresh `Matrix{Bool}` for the updated state. \n",
118112
"```{admonition} Substep 3 Solution\n",
119113
":class: dropdown\n",
120114
"```Julia\n",
121-
"function next_state_naive(grid::Matrix{Bool})::Matrix{Bool}\n",
115+
"function next_state(grid::Matrix{Bool})\n",
116+
" # Get grid dimensions: N1 rows, N2 columns\n",
122117
" N1, N2 = size(grid)\n",
118+
"\n",
119+
" # Pre-allocate a new grid of the same size, initially all false (dead)\n",
123120
" newgrid = falses(N1, N2)\n",
121+
"\n",
122+
" # Loop over every cell position (i, j)\n",
124123
" for i in 1:N1, j in 1:N2\n",
125-
" cnt = count_neighbors_naive(grid, i, j)\n",
124+
" # Count the live neighbors around (i, j)\n",
125+
" cnt = count_alive_neighbours(grid, i, j)\n",
126+
"\n",
127+
"\n",
128+
" # Apply the Game of Life rules:\n",
129+
" # - A live cell stays alive if it has 2 or 3 neighbors.\n",
130+
" # - A dead cell becomes alive if it has exactly 3 neighbors.\n",
131+
" # Otherwise it remains or becomes dead.\n",
126132
" newgrid[i,j] = (grid[i,j] && (cnt == 2 || cnt == 3)) ||\n",
127133
" (!grid[i,j] && cnt == 3)\n",
128134
" end\n",
135+
"\n",
136+
" # Return the updated grid for the next generation\n",
129137
" return newgrid\n",
130138
"end\n",
131139
"```\n",
140+
"Are there any opportunities for a boost in performance?\n",
132141
"```"
133142
]
134143
},
@@ -143,32 +152,101 @@
143152
"tags": []
144153
},
145154
"source": [
146-
"### Substep 4: Run Simulation and Visualise\n",
147-
"**Goal:** Animate the evolution of the Game of Life grid over a given number of steps and save the result as a GIF using Plots. \n",
155+
"### Substep 4: Run Simulation\n",
156+
"\n",
157+
"**Goal:** Simulate the Game of Life for a given number of steps. \n",
148158
"\n",
149159
"```{admonition} Substep 4 Solution\n",
150160
":class: dropdown\n",
151161
"```Julia\n",
152-
"using Plots\n",
162+
"function simulate(initial_grid::Matrix{Bool}, steps::Int)\n",
163+
" # Get grid dimensions\n",
164+
" N1, N2 = size(initial_grid)\n",
153165
"\n",
154-
"function run_naive_simulation(grid::Matrix{Bool}, steps::Int)\n",
155-
" anim = @animate for _ in 1:steps\n",
156-
" grid = next_state_naive(grid)\n",
166+
" # Make 3D array with time along the third axis.\n",
167+
" # Starts with only a single time index corresponding to initial\n",
168+
" # grid.\n",
169+
" simulations = reshape(initial_grid, N1, N2, 1)\n",
170+
" \n",
171+
" for step in 1:steps\n",
172+
" # Current latest generation\n",
173+
" current = simulations[:, :, end]\n",
174+
" \n",
175+
" # Update simulations with next generation\n",
176+
" next_gen = next_state(current)\n",
177+
" simulations = cat(simulations, next_gen; dims=3)\n",
178+
" end\n",
179+
" \n",
180+
" return simulations\n",
181+
"end\n",
182+
"```\n",
183+
"How do you think this could be made more performant?\n",
184+
"```"
185+
]
186+
},
187+
{
188+
"cell_type": "markdown",
189+
"id": "27592a70-12b5-4466-b832-1d70d94cf0c4",
190+
"metadata": {},
191+
"source": [
192+
"### Substep 5: Visualize\n",
193+
"\n",
194+
"**Goal:** Animate the evolution of the Game of Life grid and save the result as a GIF using `Plots`. \n",
195+
"\n",
196+
"```{admonition} Substep 5 Solution\n",
197+
":class: dropdown\n",
198+
"```Julia\n",
199+
"function visualize_simulations(\n",
200+
" simulations::Array{Bool, 3};\n",
201+
" print_interval_percent::Int=10\n",
202+
")\n",
203+
" # Compute how many steps correspond to one print interval:\n",
204+
" # at least 1, scaled by the given percentage of total steps\n",
205+
" n_steps = size(simulations, 3)\n",
206+
" step_interval = max(1, floor(Int, n_steps * print_interval_percent / 100))\n",
207+
"\n",
208+
" t_start = time()\n",
209+
" anim = @animate for step in axes(simulations, 3)\n",
210+
" # If we've reached a print interval, output progress stats\n",
211+
" if step % step_interval == 0\n",
212+
" pct = step / n_steps * 100\n",
213+
" elapsed = time() - t_start\n",
214+
" avg = elapsed / step\n",
215+
" remain = avg * (n_steps - step)\n",
216+
" @printf(\"Progress: %3.0f%% | Elapsed: %5.1fs | Remaining: %5.1fs\\n\",\n",
217+
" pct, elapsed, remain)\n",
218+
" end\n",
219+
"\n",
220+
" # Generate a heatmap frame for the current grid state\n",
157221
" heatmap(\n",
158-
" grid;\n",
159-
" color = :grays, \n",
160-
" axis = false, \n",
222+
" view(simulations, :, :, step);\n",
223+
" color = :grays,\n",
224+
" axis = false,\n",
161225
" legend = false,\n",
162226
" framestyle = :none,\n",
163227
" aspect_ratio = 1\n",
164228
" )\n",
165229
" end\n",
166-
" gif(anim, \"life_naive.gif\"; fps = 10)\n",
167-
" println(\"Saved naive animation to life_naive.gif\")\n",
168-
"end\n",
230+
" \n",
231+
" # Ensure the “data” directory exists\n",
232+
" mkpath(\"data\")\n",
169233
"\n",
170-
"# Example usage:\n",
171-
"run_naive_simulation(grid, 100)\n",
234+
" # Write the animation to GIF at 10 frames per second\n",
235+
" gif(anim, \"data/life_naive.gif\"; fps = 10)\n",
236+
" println(\"Saved animation to data/life_naive.gif\")\n",
237+
" \n",
238+
" # Detect if running inside a Jupyter notebook\n",
239+
" in_notebook = isdefined(Main, :IJulia) && IJulia.inited\n",
240+
" \n",
241+
" if in_notebook\n",
242+
" # Display the GIF inline in the notebook\n",
243+
" display(\"image/gif\", read(\"data/life_naive.gif\"))\n",
244+
" else\n",
245+
" # Otherwise, instruct the user to open the files manually\n",
246+
" println(\"Not running in a notebook.\")\n",
247+
" println(\"Open `data/life_naive.gif` in your OS image viewer to see the results.\")\n",
248+
" end\n",
249+
"end\n",
172250
"```\n",
173251
"```"
174252
]
@@ -206,14 +284,21 @@
206284
"### Formatted Output with `Printf`\n",
207285
"\n",
208286
"**Module**: `using Printf`\n",
287+
"\n",
209288
"**Macro**: `@printf` for C-style formatted printing to `STDOUT`.\n",
210-
"`@printf(\"Progress: %3.0f%% | Elapsed: %5.1fs | Remaining: %5.1fs\\n\",\n",
211-
" pct, elapsed, remain)`\n",
289+
"\n",
290+
"````julia\n",
291+
"@printf(\"Progress: %3.0f%% | Elapsed: %5.1fs | Remaining: %5.1fs\\n\",\n",
292+
" pct, elapsed, remain)\n",
293+
"````\n",
212294
"```\n",
213295
"\n",
214296
"```{admonition} Hint 2 \n",
215297
":class: dropdown\n",
216-
"### Animations with `Plots.jl` \n",
298+
"### Animations with `Plots.jl`\n",
299+
"\n",
300+
"**Module**: `using Plots`\n",
301+
"\n",
217302
"**Macro**: `@animate` collects plot frames in a loop, which can then be exported with `gif(anim, filepath; fps=10)`.\n",
218303
"\n",
219304
"````julia \n",
@@ -223,27 +308,19 @@
223308
"````\n",
224309
"```\n"
225310
]
226-
},
227-
{
228-
"cell_type": "code",
229-
"execution_count": null,
230-
"id": "c7ad76a0-fb5c-4a87-ade8-1690e2e1f61b",
231-
"metadata": {},
232-
"outputs": [],
233-
"source": []
234311
}
235312
],
236313
"metadata": {
237314
"kernelspec": {
238-
"display_name": "Julia 1.11.5",
315+
"display_name": "Julia 1.10.9",
239316
"language": "julia",
240-
"name": "julia-1.11"
317+
"name": "julia-1.10"
241318
},
242319
"language_info": {
243320
"file_extension": ".jl",
244321
"mimetype": "application/julia",
245322
"name": "julia",
246-
"version": "1.11.5"
323+
"version": "1.10.9"
247324
}
248325
},
249326
"nbformat": 4,

0 commit comments

Comments
 (0)