Skip to content

Onboarding Problem

You will implement two components of a 2D heat diffusion solver:

  1. A grid class that stores the 2D temperature field in memory.
  2. The stencil kernel that advances the simulation by one time step.

Everything else, i.e., the time integrator, boundary conditions, initial conditions, and I/O, is provided. You DO NOT need to know any thermal physics or theory.

Heat spreads across a 2D surface according to the following Partial Differential Equation:

Tt  =  α(2Tx2+2Ty2)\frac{\partial T}{\partial t} \;=\; \alpha \left( \frac{\partial^2 T}{\partial x^2} + \frac{\partial^2 T}{\partial y^2} \right)

where T(x,y,t)T(x,y,t) is the temperature and α\alpha is a constant. Again, you do not need to understand the physics, math, theory, or origin of the equation; this is merely for context.

Replace the continuous derivatives with finite differences:

Tt+1(i,j)=Tt(i,j)+Δtα ⁣(Tt(i+1,j)2Tt(i,j)+Tt(i1,j)Δx2+Tt(i,j+1)2Tt(i,j)+Tt(i,j1)Δy2)T_{t+1}(i,j) = T_t(i,j) + \Delta t \cdot \alpha \!\left( \frac{T_t(i{+}1,j) - 2T_t(i,j) + T_t(i{-}1,j)}{\Delta x^2} + \frac{T_t(i,j{+}1) - 2T_t(i,j) + T_t(i,j{-}1)}{\Delta y^2} \right)

With constants α\alpha, Δx\Delta x, Δy\Delta y, and Δt\Delta t chosen for stability, the equation simplifies to the following:

  Tt+1(i,j)  =  0.5Tt(i,j)  +  0.125(Tt(i1,j)+Tt(i+1,j)+Tt(i,j1)+Tt(i,j+1))  \boxed{\;T_{t+1}(i,j) \;=\; 0.5 \cdot T_t(i,j) \;+\; 0.125 \cdot \bigl( T_t(i{-}1,j) + T_t(i{+}1,j) + T_t(i,j{-}1) + T_t(i,j{+}1) \bigr)\;}

Notice how this is just a weighted average. In code, the above could look something like this:

new_arr[i][j] = 0.5 * old_arr[i][j] +
0.125 * (old_arr[i-1][j] +
old_arr[i+1][j] +
old_arr[i][j-1] +
old_arr[i][j+1]
);

To provide a more visual understanding and intuition of what is happening, consider the diagram below. You can picture the heat from each “bubble” (grid cell) leaking into its neighbors.

The five-point stencil

The cell at i,j is weighted by 0.5 and each of its four neighbours — i,j+1 above, i,j-1 below, i-1,j to the left and i+1,j to the right — is weighted by 0.125.

i,j+1i,j-1i-1,ji+1,ji,j× 0.125× 0.125× 0.125× 0.125× 0.5centerneighbor

Applying that update over and over is the whole simulation. Below, a hot block at the centre of a grid spreads into the cells around it as the stencil is applied repeatedly:

Heat spreading across the gridAn 11 by 11 grid seeded with a hot three-by-three block at its centre. Repeatedly applying the five-point stencil spreads that heat outwards into the surrounding cells, and the peak temperature falls as it does. The outermost ring is boundary and never changes.hotcoolboundaryinterior
The same five-point update applied repeatedly. Heat spreads outward from the centre and the peak falls, because every cell is being averaged with its neighbours. Only the interior is updated; the boundary ring is copied unchanged.

Design and implement a Grid class that stores a 2D field of double values with rows rows and cols columns. In the notation above, index i selects the row and index j selects the column.

Both this class and the stencil function go in src/submission.hpp, which ships with the interface declared and no implementations:

#pragma once
#include <cstddef>
class Grid {
private:
std::size_t rows_;
std::size_t cols_;
public:
Grid(std::size_t rows, std::size_t cols);
double& operator()(std::size_t i, std::size_t j);
double operator()(std::size_t i, std::size_t j) const;
};

The two operator() overloads give read/write and read-only access to the cell at row i, column j. The evaluation harness uses only these overloads to set initial conditions and read back your results, so they must work no matter how you store the field internally. Keep this interface; everything else is yours. We want to see how you think and how you approach such problems.

Requirements:

  1. Default-initialized to zero.
  2. Implement both operator() overloads; the evaluation harness relies on them.
  3. You are free to choose the memory layout and overall design of the Grid class. Be prepared to discuss your decisions during the interview.

Note that the harness never asks your Grid for its dimensions, so no size accessors are prescribed. Your stencil will still need the dimensions, which makes exposing them part of your design.

Implement the function that applies the five-point stencil over all interior grid points (1i<rows11 \leq i < \text{rows} - 1,   1j<cols1\; 1 \leq j < \text{cols} - 1). It is declared in the same file:

void apply_stencil(const Grid& old_grid, Grid& new_grid);

Boundary values must remain unchanged. You are not required to implement any boundary conditions; simply copy the boundary values from the old grid to the new grid.

The boundary copy below is illustrative; the interior update is up to you.

void apply_stencil(const Grid& old_grid, Grid& new_grid) {
// The signature carries no dimensions, so your Grid has to make its own
// size reachable from here. How you do that is your decision.
const std::size_t rows = /* rows in old_grid */;
const std::size_t cols = /* columns in old_grid */;
// Copy boundaries
for (std::size_t i{}; i < rows; ++i) {
new_grid(i, 0) = old_grid(i, 0);
new_grid(i, cols - 1) = old_grid(i, cols - 1);
}
for (std::size_t j{}; j < cols; ++j) {
new_grid(0, j) = old_grid(0, j);
new_grid(rows - 1, j) = old_grid(rows - 1, j);
}
// Your implementation...
}

All testing and benchmarking will be performed automatically on team infrastructure. Submit a fork of the provided repository containing your implementation of both the Grid class and apply_stencil, in the single file src/submission.hpp.

Everything else in the repository — bench/, CMakeLists.txt, CMakePresets.json, and the GitHub Actions workflow — is the evaluation harness. Do not edit it.

Submissions will be evaluated on correctness, implementation quality, design decisions, and performance. Performance will be measured using wall clock execution time on a team benchmark machine.

Correctness alone is not sufficient. We expect thoughtful consideration of memory layout, performance, and overall design. Applications are reviewed holistically, and advancement is not determined solely by benchmark results.

Your submission must be C++17. The build is pinned to that standard and the build configuration is not yours to edit, so anything from a later standard will not compile. Within C++17 you may use any standard library facility and add whatever methods, helper functions, or internal data structures you need.

Push your changes. A GitHub Action sends the committed version to the UWHPC evaluator and reports the result as a commit check. After your submission, we will invite you to a short virtual chat to discuss your design further. This is solely to get an idea of your thinking and how you approach problems.

We encourage using AI to learn and explore ideas. That said, your submission should be your own work; copy-pasting from AI will be obvious, especially during the chat.

Good luck.