{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "conv-student-000",
   "metadata": {},
   "source": [
    "# Homework 1\n",
    "\n",
    "In this assignment, we will connect a small calculation by hand with the output of a program and learn how to choose a useful test.\n",
    "\n",
    "The steps are:\n",
    "\n",
    "- **A:** find the output shape and one output value\n",
    "- **B:** complete a short implementation and compare it with SciPy\n",
    "- **C:** identify an error\n",
    "- **D:** choose a question of our own and carry out a small investigation\n",
    "- Optional feedback at the end\n",
    "\n",
    "**Related material:** Practice 3, exercises 1.1, 1.2 and 5.1. You can also consult the worked exercises covered in the tutorials."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-001",
   "metadata": {},
   "source": [
    "## Before we start\n",
    "\n",
    "The assignment uses NumPy, SciPy and Matplotlib.\n",
    "To install them, run `%pip install numpy scipy matplotlib` in a separate cell and restart the kernel.\n",
    "\n",
    "The starter notebook runs even with the answers left blank. `None` means “no answer has been provided yet”. Here, `np.nan` is a placeholder for a missing number. The helper functions are provided. Write your code between `### TODO: ADD YOUR CODE HERE` and `### END of TODO`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "student-identification",
   "metadata": {
    "ecey523": {
     "answer_id": "identity"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "student_name = \"Name Surname\"  # Replace with your full name.\n",
    "student_id = \"12345678\"  # Replace with your student ID.\n",
    "### END of TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "homework-imports",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from scipy.signal import convolve2d"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-002",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.382056Z",
     "iopub.status.busy": "2026-10-31T15:34:38.381985Z",
     "iopub.status.idle": "2026-10-31T15:34:38.740672Z",
     "shell.execute_reply": "2026-10-31T15:34:38.740381Z"
    }
   },
   "source": [
    "## Step A\n",
    "\n",
    "We use the input and kernel from exercise 1.1. Indexing starts at 0. In `X[m, n]`, the first index selects a row and the second selects a column."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-004",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T20:01:11.496928Z",
     "iopub.status.busy": "2026-10-31T20:01:11.496795Z",
     "iopub.status.idle": "2026-10-31T20:01:11.500702Z",
     "shell.execute_reply": "2026-10-31T20:01:11.498935Z"
    }
   },
   "outputs": [],
   "source": [
    "X = np.array([[1, 2, 0],\n",
    "              [3, 1, -1],\n",
    "              [2, 0, 1]], dtype=float)\n",
    "H = np.array([[1, 2],\n",
    "              [-1, 0]], dtype=float)\n",
    "print(\"Input X:\\n\", X)\n",
    "print(\"Kernel H:\\n\", H)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-004-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.742001Z",
     "iopub.status.busy": "2026-10-31T15:34:38.741880Z",
     "iopub.status.idle": "2026-10-31T15:34:38.744691Z",
     "shell.execute_reply": "2026-10-31T15:34:38.744412Z"
    }
   },
   "source": [
    "$X$ is the input matrix and $H$ is the kernel, which contains the filter weights. $y[m,n]$ is one output value. For this kernel:\n",
    "\n",
    "$$y[m,n]=\\sum_{i=0}^{1}\\sum_{j=0}^{1}H[i,j]X[m-i,n-j].$$\n",
    "\n",
    "The indices $m,n$ select the output position. The indices $i,j$ run through the rows and columns of the kernel. The two sums mean that we add four products. Every value of $X$ outside the given matrix is taken to be zero.\n",
    "\n",
    "### A1. Output shape\n",
    "If the input has $M$ rows and $N$ columns, and the kernel has $M_h$ rows and $N_h$ columns, the full output has shape\n",
    "\n",
    "$$(M+M_h-1,\\ N+N_h-1).$$\n",
    "\n",
    "The subscript $h$ distinguishes the kernel dimensions from the input dimensions. We write the shape as `(rows, columns)`. This notation is called a **tuple** and keeps the two values together. For example, a matrix with 2 rows and 5 columns has shape `(2, 5)`.\n",
    "\n",
    "Find the output shape for the matrices above and fill in `shape_by_hand` in the same format.\n",
    "\n",
    "### A2. One value by hand\n",
    "In class, we calculated $y[1,1]=5$. Now find **$y[1,2]$**, the entry in the second row and third column of the output. First, complete the table in the separate answer cell below, without calling SciPy.\n",
    "\n",
    "Add the four products and write the result in `value_by_hand`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "answer-alpha2-table",
   "metadata": {
    "ecey523": {
     "answer_id": "a_table"
    }
   },
   "source": [
    "**Answer A2**\n",
    "\n",
    "| $(i,j)$ | $H[i,j]$ | Position $X[1-i,2-j]$ | Input value | Product |\n",
    "|---|---:|---|---:|---:|\n",
    "| $(0,0)$ | 1 | … | … | … |\n",
    "| $(0,1)$ | 2 | … | … | … |\n",
    "| $(1,0)$ | −1 | … | … | … |\n",
    "| $(1,1)$ | 0 | … | … | … |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-006",
   "metadata": {
    "ecey523": {
     "answer_id": "a_values"
    },
    "execution": {
     "iopub.execute_input": "2026-10-31T20:01:11.502999Z",
     "iopub.status.busy": "2026-10-31T20:01:11.502912Z",
     "iopub.status.idle": "2026-10-31T20:01:11.512460Z",
     "shell.execute_reply": "2026-10-31T20:01:11.511885Z"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "shape_by_hand = None  # Replace with the output (rows, columns) as a tuple, e.g. (1, 2).\n",
    "value_by_hand = None  # Replace with the value y[1, 2].\n",
    "### END of TODO\n",
    "\n",
    "print(\"Predicted shape:\", shape_by_hand)\n",
    "print(\"Calculated y[1, 2]:\", value_by_hand)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-006-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.745894Z",
     "iopub.status.busy": "2026-10-31T15:34:38.745791Z",
     "iopub.status.idle": "2026-10-31T15:34:38.755656Z",
     "shell.execute_reply": "2026-10-31T15:34:38.755459Z"
    }
   },
   "source": [
    "## Step B\n",
    "### B1. Two-dimensional convolution\n",
    "\n",
    "`conv2d_full(x, h)` computes the full convolution of two non-empty, real-valued, two-dimensional arrays. The lowercase names `x, h` are the function parameters. When we call it with `X, H`, it will use the matrices from A.\n",
    "\n",
    "Complete **both TODO regions**:\n",
    "\n",
    "1. The input indices `mi, nj` corresponding to `m, n, i, j`.\n",
    "2. The product added to `y[m, n]`.\n",
    "\n",
    "The loops, output shape, boundary check and `return` are already provided.\n",
    "\n",
    "In NumPy, negative indices read elements from the end of an array. The boundary check ensures that only samples inside the input contribute to the calculation, consistent with zero extension.\n",
    "\n",
    "Follow the formula in step A. In this formula, the flip is expressed through the input indices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-010",
   "metadata": {
    "ecey523": {
     "answer_id": "b_function"
    },
    "execution": {
     "iopub.execute_input": "2026-10-31T20:01:11.513923Z",
     "iopub.status.busy": "2026-10-31T20:01:11.513852Z",
     "iopub.status.idle": "2026-10-31T20:01:11.516231Z",
     "shell.execute_reply": "2026-10-31T20:01:11.515825Z"
    }
   },
   "outputs": [],
   "source": [
    "def conv2d_full(x, h):\n",
    "    x = np.asarray(x, dtype=float)\n",
    "    h = np.asarray(h, dtype=float)\n",
    "    M, N = x.shape\n",
    "    Mh, Nh = h.shape\n",
    "    y = np.zeros((M + Mh - 1, N + Nh - 1), dtype=float)\n",
    "\n",
    "    for m in range(y.shape[0]):\n",
    "        for n in range(y.shape[1]):\n",
    "            for i in range(Mh):\n",
    "                for j in range(Nh):\n",
    "                    ### TODO: ADD YOUR CODE HERE\n",
    "                    mi, nj = None, None\n",
    "                    ### END of TODO\n",
    "                    if mi is None or nj is None:\n",
    "                        return np.full(y.shape, np.nan)\n",
    "                    if 0 <= mi < M and 0 <= nj < N:\n",
    "                        ### TODO: ADD YOUR CODE HERE\n",
    "                        y[m, n] += np.nan\n",
    "                        ### END of TODO\n",
    "    return y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-010-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.756596Z",
     "iopub.status.busy": "2026-10-31T15:34:38.756526Z",
     "iopub.status.idle": "2026-10-31T15:34:38.759009Z",
     "shell.execute_reply": "2026-10-31T15:34:38.758772Z"
    }
   },
   "source": [
    "### B2. Convolution using SciPy\n",
    "\n",
    "SciPy provides a ready-made implementation. With `mode=\"full\"`, we keep the entire output. With `boundary=\"fill\", fillvalue=0`, we set the values outside the input to zero. SciPy already handles convolution, so we pass it the original kernel.\n",
    "\n",
    "`compare_outputs` displays the shapes of the two results. If they match, it computes the maximum absolute difference, which is the largest distance between two corresponding values. `np.allclose` returns `True` when all corresponding values agree within a small numerical tolerance.\n",
    "\n",
    "A difference very close to zero indicates numerical agreement for this example. Small differences, such as `1e-15`, can arise from the order of floating-point operations.\n",
    "\n",
    "While `np.nan` remains in the function's TODO regions, the difference is shown as `nan` and numerical agreement as `False`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-012",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T20:01:11.517774Z",
     "iopub.status.busy": "2026-10-31T20:01:11.517694Z",
     "iopub.status.idle": "2026-10-31T20:01:11.521942Z",
     "shell.execute_reply": "2026-10-31T20:01:11.521515Z"
    }
   },
   "outputs": [],
   "source": [
    "def compare_outputs(actual, reference, label):\n",
    "    print(label)\n",
    "    print(\"Our output shape:\", actual.shape)\n",
    "    print(\"Reference shape:\", reference.shape)\n",
    "    if actual.shape != reference.shape:\n",
    "        print(\"The shapes differ. Check the dimensions.\")\n",
    "        return\n",
    "    print(\"Maximum absolute difference:\", np.max(np.abs(actual - reference)))\n",
    "    print(\"Numerical agreement:\", np.allclose(actual, reference))\n",
    "\n",
    "Y_reference = convolve2d(X, H, mode=\"full\", boundary=\"fill\", fillvalue=0)\n",
    "Y_mine = conv2d_full(X, H)\n",
    "compare_outputs(Y_mine, Y_reference, \"The example from A\")\n",
    "\n",
    "print(\"Reference output:\\n\", Y_reference)\n",
    "print(\"Our computed output:\\n\", Y_mine)\n",
    "print(\"Our predicted shape:\", shape_by_hand)\n",
    "print(\"Reference shape:\", Y_reference.shape)\n",
    "print(\"y[1, 2] by hand:\", value_by_hand)\n",
    "print(\"Reference y[1, 2]:\", Y_reference[1, 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-012-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.759811Z",
     "iopub.status.busy": "2026-10-31T15:34:38.759740Z",
     "iopub.status.idle": "2026-10-31T15:34:38.762424Z",
     "shell.execute_reply": "2026-10-31T15:34:38.762229Z"
    }
   },
   "source": [
    "### B3. Checking correctness\n",
    "\n",
    "To check our algorithm, we can design small tests that examine its results on different types of input. This lets us try the implementation in more cases. In programming, these are called “unit tests” and are useful for checking corner cases, error handling and similar situations.\n",
    "\n",
    "Testing different inputs can reveal errors that do not appear in the original example and give us greater confidence in our implementation. Here, we will examine the following case: the first example has a square 3×3 input and a square 2×2 kernel. If we accidentally use the number of rows in place of the number of columns, the error may go unnoticed because the two numbers are equal. Let's see what happens with a 4×6 input and a 3×2 kernel, whose dimensions differ. The full output has shape `(6, 7)`, so the number of rows and columns also differs in the result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-014",
   "metadata": {},
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(4)\n",
    "x_rect = rng.normal(size=(4, 6))\n",
    "h_rect = rng.normal(size=(3, 2))\n",
    "y_rect_ref = convolve2d(x_rect, h_rect, mode=\"full\", boundary=\"fill\", fillvalue=0)\n",
    "y_rect_mine = conv2d_full(x_rect, h_rect)\n",
    "compare_outputs(y_rect_mine, y_rect_ref, \"Rectangular data\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "beta3-own-check-instructions",
   "metadata": {},
   "source": [
    "### B4. A test of your own\n",
    "\n",
    "Choose another small input and a kernel to test a different case of `conv2d_full`. Briefly explain why you chose these data and what you expect to happen. Fill in the two arrays in the code cell and compare the output with SciPy. Then add to your answer whether the result agrees with your prediction."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "beta3-own-check-answer",
   "metadata": {
    "ecey523": {
     "answer_id": "b4_reason"
    }
   },
   "source": [
    "*Write your choice, prediction and, after running the code, your conclusion here.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "beta3-own-check-code",
   "metadata": {
    "ecey523": {
     "answer_id": "b4_code"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "x_check = None  # Enter a small two-dimensional np.array.\n",
    "h_check = None  # Enter the kernel as a two-dimensional np.array.\n",
    "### END of TODO\n",
    "\n",
    "if x_check is not None and h_check is not None:\n",
    "    y_check = conv2d_full(x_check, h_check)\n",
    "    y_check_ref = convolve2d(x_check, h_check, mode=\"full\", boundary=\"fill\", fillvalue=0)\n",
    "    compare_outputs(y_check, y_check_ref, \"Our own test\")\n",
    "    print(\"Our computed output:\\n\", y_check)\n",
    "    print(\"Reference output:\\n\", y_check_ref)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-014-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.763139Z",
     "iopub.status.busy": "2026-10-31T15:34:38.763035Z",
     "iopub.status.idle": "2026-10-31T15:34:38.765025Z",
     "shell.execute_reply": "2026-10-31T15:34:38.764790Z"
    }
   },
   "source": [
    "## Step C\n",
    "### C1. Identifying an error\n",
    "\n",
    "`conv2d_candidate` contains an error in its calculation. It runs and returns the correct shape. **First, look at its results.**\n",
    "\n",
    "We will compare this function with SciPy, first for a kernel whose weights are all 1, and then for the non-symmetric `H` from A. Predict which case makes it easier to spot an error in the flip.\n",
    "\n",
    "Leave the function unchanged for now."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-018",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T20:01:11.526394Z",
     "iopub.status.busy": "2026-10-31T20:01:11.526320Z",
     "iopub.status.idle": "2026-10-31T20:01:11.528514Z",
     "shell.execute_reply": "2026-10-31T20:01:11.528227Z"
    }
   },
   "outputs": [],
   "source": [
    "def conv2d_candidate(x, h):\n",
    "    x = np.asarray(x, dtype=float)\n",
    "    h = np.asarray(h, dtype=float)\n",
    "    M, N = x.shape\n",
    "    Mh, Nh = h.shape\n",
    "    y = np.zeros((M + Mh - 1, N + Nh - 1), dtype=float)\n",
    "    for m in range(y.shape[0]):\n",
    "        for n in range(y.shape[1]):\n",
    "            for i in range(Mh):\n",
    "                for j in range(Nh):\n",
    "                    mi, nj = m - i, n - j\n",
    "                    if 0 <= mi < M and 0 <= nj < N:\n",
    "                        y[m, n] += h[Mh - 1 - i, Nh - 1 - j] * x[mi, nj]\n",
    "    return y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-018-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.765606Z",
     "iopub.status.busy": "2026-10-31T15:34:38.765488Z",
     "iopub.status.idle": "2026-10-31T15:34:38.767579Z",
     "shell.execute_reply": "2026-10-31T15:34:38.767316Z"
    }
   },
   "outputs": [],
   "source": [
    "H_symmetric = np.ones((2, 2))\n",
    "for label, kernel in [(\"Symmetric kernel\", H_symmetric),\n",
    "                      (\"Non-symmetric kernel\", H)]:\n",
    "    expected = convolve2d(X, kernel, mode=\"full\", boundary=\"fill\", fillvalue=0)\n",
    "    candidate = conv2d_candidate(X, kernel)\n",
    "    compare_outputs(candidate, expected, label)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-019",
   "metadata": {},
   "source": [
    "#### C1.1. The first kernel\n",
    "\n",
    "Does the kernel whose entries are all 1 change when rotated by 180°? Fill in your answer in the next cell. Then briefly explain how this affects our ability to detect an incorrect flip."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "gamma1-structured",
   "metadata": {
    "ecey523": {
     "answer_id": "g1_flip_values"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "gamma1_kernel_changes = None  # True if it changes, False if it stays the same.\n",
    "### END of TODO"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-021",
   "metadata": {
    "ecey523": {
     "answer_id": "g1_reason"
    }
   },
   "source": [
    "**Explanation C1.1**\n",
    "\n",
    "*Write your explanation here.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "gamma1-factor-question",
   "metadata": {},
   "source": [
    "#### C1.2. Finding the incorrect term\n",
    "\n",
    "In the line that updates `y[m, n]`, which term reads the kernel with reversed indices? Write the `h[...]` term from the incorrect function inside the quotation marks. We will correct the function in C3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "gamma1-factor-answer",
   "metadata": {
    "ecey523": {
     "answer_id": "g1_factor_values"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "gamma1_faulty_term = \"h[...]\"  # The h[...] term you identified.\n",
    "### END of TODO"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "gamma1-shape-question",
   "metadata": {},
   "source": [
    "#### C1.3. Checking the shape\n",
    "\n",
    "Is the correct output shape enough to confirm that the function computes convolution correctly? Fill in your answer and justify it using the results you observed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "gamma1-shape-answer",
   "metadata": {
    "ecey523": {
     "answer_id": "g1_shape_values"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "gamma1_shape_sufficient = None  # True if checking the shape is enough, False if another check is needed.\n",
    "### END of TODO"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "gamma1-shape-reason",
   "metadata": {
    "ecey523": {
     "answer_id": "g1_shape_reason"
    }
   },
   "source": [
    "**Explanation C1.3**\n",
    "\n",
    "*Write your explanation here.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-022",
   "metadata": {},
   "source": [
    "### C2. Testing with a unit impulse\n",
    "\n",
    "A useful test has a result that we can easily predict. We use `[[1]]` as the input matrix, with zero values outside it. This input is the unit impulse at the origin of the two axes.\n",
    "\n",
    "Convolving this impulse with the kernel `H` returns `H` itself. The only nonzero input sample has value 1, so it reproduces the kernel entries at the corresponding output positions.\n",
    "\n",
    "Run the next cell and compare the two matrices:\n",
    "\n",
    "1. Does `conv2d_candidate` return the expected matrix `H`?\n",
    "2. Describe how the returned matrix differs from `H`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-023",
   "metadata": {},
   "outputs": [],
   "source": [
    "impulse = np.array([[1.0]])\n",
    "impulse_output = conv2d_candidate(impulse, H)\n",
    "print(\"Expected output H:\\n\", H)\n",
    "print(\"Output of conv2d_candidate:\\n\", impulse_output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "gamma2-structured",
   "metadata": {
    "ecey523": {
     "answer_id": "g2_values"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "gamma2_matches_kernel = None  # True if the two matrices are equal, False if they differ.\n",
    "### END of TODO"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "answer-gamma-2",
   "metadata": {
    "ecey523": {
     "answer_id": "g2_reason"
    }
   },
   "source": [
    "**Description of the difference in C2**\n",
    "\n",
    "*Describe the difference you observe between the two matrices here.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-023-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.772014Z",
     "iopub.status.busy": "2026-10-31T15:34:38.771889Z",
     "iopub.status.idle": "2026-10-31T15:34:38.774518Z",
     "shell.execute_reply": "2026-10-31T15:34:38.774137Z"
    }
   },
   "source": [
    "### C3. Correcting the error\n",
    "\n",
    "The next cell contains a second version of the function, so that we can keep the original incorrect implementation as an example. Correct the line between the TODO comments. Then run the definition again, followed by the two comparisons below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-025",
   "metadata": {
    "ecey523": {
     "answer_id": "g_function"
    },
    "execution": {
     "iopub.execute_input": "2026-10-31T20:01:11.535099Z",
     "iopub.status.busy": "2026-10-31T20:01:11.534947Z",
     "iopub.status.idle": "2026-10-31T20:01:11.537419Z",
     "shell.execute_reply": "2026-10-31T20:01:11.537258Z"
    }
   },
   "outputs": [],
   "source": [
    "def conv2d_repaired(x, h):\n",
    "    x = np.asarray(x, dtype=float)\n",
    "    h = np.asarray(h, dtype=float)\n",
    "    M, N = x.shape\n",
    "    Mh, Nh = h.shape\n",
    "    y = np.zeros((M + Mh - 1, N + Nh - 1), dtype=float)\n",
    "    for m in range(y.shape[0]):\n",
    "        for n in range(y.shape[1]):\n",
    "            for i in range(Mh):\n",
    "                for j in range(Nh):\n",
    "                    mi, nj = m - i, n - j\n",
    "                    if 0 <= mi < M and 0 <= nj < N:\n",
    "                        ### TODO: ADD YOUR CODE HERE\n",
    "                        y[m, n] += h[Mh - 1 - i, Nh - 1 - j] * x[mi, nj]\n",
    "                        ### END of TODO\n",
    "    return y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-025-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.775237Z",
     "iopub.status.busy": "2026-10-31T15:34:38.775112Z",
     "iopub.status.idle": "2026-10-31T15:34:38.777512Z",
     "shell.execute_reply": "2026-10-31T15:34:38.777256Z"
    }
   },
   "outputs": [],
   "source": [
    "compare_outputs(conv2d_repaired(impulse, H), H, \"After the correction: impulse\")\n",
    "compare_outputs(conv2d_repaired(X, H), Y_reference, \"After the correction: original input\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-026",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-31T15:34:38.778394Z",
     "iopub.status.busy": "2026-10-31T15:34:38.778313Z",
     "iopub.status.idle": "2026-10-31T15:34:38.780637Z",
     "shell.execute_reply": "2026-10-31T15:34:38.780357Z"
    }
   },
   "source": [
    "## Step D\n",
    "\n",
    "Choose **a question about the material covered so far in the tutorials (Lectures 1–3)**. Design a small experiment that will help you understand your chosen topic better.\n",
    "\n",
    "### Choosing a topic\n",
    "\n",
    "Choose something that interests you or that you found difficult and would like to spend more time on. You can start from an exercise we have covered in class, from the practice sheets, or from a question of your own.\n",
    "\n",
    "Some possible directions are:\n",
    "\n",
    "- Visualization: Create a visual representation that helps explain a mathematical concept or method.\n",
    "\n",
    "- Experimentation: Modify a familiar exercise or vary the parameters of a problem and examine how the result changes.\n",
    "\n",
    "- Implementation: Investigate alternative implementations of a method or the behaviour of an algorithm.\n",
    "\n",
    "- Verification: Check a solution, a hypothesis or a computational result.\n",
    "\n",
    "These directions are suggestions. You can follow any other approach that interests you, as long as it relates to the course material.\n",
    "\n",
    "### Designing the experiment\n",
    "\n",
    "The aim is to investigate a specific question and explain what you observe. Use a known solution or a plot as a starting point for that investigation.\n",
    "\n",
    "Design a **small test, comparison or visualization** that helps you answer your question. Consider what you will change, what you will keep fixed and what you expect to observe.\n",
    "\n",
    "**Example**\n",
    "\n",
    "One investigation we discussed in the tutorial was: “How do the eigenvalues affect the shape of the ellipse?”\n",
    "\n",
    "We can keep one eigenvalue fixed, vary the other and plot the corresponding ellipses. Comparing the results lets us examine how the semiaxes change and what this means for the sensitivity of the problem.\n",
    "\n",
    "In exercise 2.2 of Practice 1, we examined what affects the size and proportions of the ellipse. Keeping the eigenvalues $12$ and $0.03$ fixed, we plot the curves for $c=1,4,9$, where $c$ is the increase in cost above its minimum value. We check whether the ratio $a_2/a_1$ stays the same as the ellipse grows, where $a_1$ and $a_2$ are the lengths of its two semiaxes. Alternatively, we keep $c=1$, the first eigenvalue at $12$ and the axis directions fixed. We vary the second eigenvalue, plot the ellipse again and examine how the semiaxis ratio changes. In each experiment, we vary one quantity so that we can explain its effect on the result.\n",
    "\n",
    "This is an example of a small investigation: we start with a question, change something specific and observe the result.\n",
    "\n",
    "Your experiment can be simple and produce an ordinary result. Even if the result contradicts your initial prediction, it is still a useful observation.\n",
    "\n",
    "### Tools\n",
    "\n",
    "Your investigation must include **Python code that runs in the notebook**. You may use any library you like, as well as AI tools such as ChatGPT, Claude or Gemini. Using AI is allowed and encouraged as an aid to understanding a concept, designing an experiment, creating code or interpreting results.\n",
    "\n",
    "We encourage you to choose what you want to investigate first, based on your own questions and interests, and then use the tools to explore it."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta-deliverables",
   "metadata": {},
   "source": [
    "### Deliverables"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta1-instructions",
   "metadata": {},
   "source": [
    "#### D1. Question and initial prediction\n",
    "\n",
    "What question did you choose, why does it interest you and what did you expect to happen?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta1-answer",
   "metadata": {
    "ecey523": {
     "answer_id": "d_question"
    }
   },
   "source": [
    "*Write here.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta2-instructions",
   "metadata": {},
   "source": [
    "#### D2. Method and tools\n",
    "\n",
    "Describe what you changed, what you kept fixed and how you investigated your question. If you used AI, report the model and prompts."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta2-answer",
   "metadata": {
    "ecey523": {
     "answer_id": "d_method"
    }
   },
   "source": [
    "*Write here.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta3-instructions",
   "metadata": {},
   "source": [
    "#### D3. Experiment code\n",
    "\n",
    "Include your Python code in the next code cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "conv-student-031-2",
   "metadata": {
    "ecey523": {
     "answer_id": "d_code"
    },
    "execution": {
     "iopub.execute_input": "2026-10-31T20:01:11.753662Z",
     "iopub.status.busy": "2026-10-31T20:01:11.753573Z",
     "iopub.status.idle": "2026-10-31T20:01:11.757116Z",
     "shell.execute_reply": "2026-10-31T20:01:11.754986Z"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "# Write the Python code for your experiment here,\n",
    "# whether it is your own code or code from the AI tool you used.\n",
    "### END of TODO"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta4-instructions",
   "metadata": {},
   "source": [
    "#### D4. Result and observation\n",
    "\n",
    "Present the result as code output, a table, a plot or another suitable format. You can refer to the output of D3. Make it clear what you are comparing or checking. Explain what you conclude, whether the result agrees with your prediction and whether it answers your original question. Add anything you would like to investigate further, if applicable."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "delta4-answer",
   "metadata": {
    "ecey523": {
     "answer_id": "d_result"
    }
   },
   "source": [
    "*Write here.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conv-student-033",
   "metadata": {},
   "source": [
    "## Before submitting the assignment\n",
    "\n",
    "- Select **Restart Kernel and Run All** and save the notebook **with cell outputs visible**, including the tables, plots and printed results from step D. Executing the code cells and saving their outputs are part of the assessment. Do not select **Clear Outputs** before submitting.\n",
    "- Keep the incorrect results of `conv2d_candidate` as part of the example.\n",
    "- Your code must run without errors and produce correct results. If the code for a question fails to run, no credit is awarded for that question.\n",
    "- Make sure you have entered your details in the identification cell at the beginning of the notebook.\n",
    "- Optionally, use the last cell to give feedback on the assignment in 1–2 sentences. You can comment on the content, the time it took, the difficulty or a particular question you still have about the assignment."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b13e4ba",
   "metadata": {},
   "source": [
    "### Optional: Give us feedback!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fd9283f",
   "metadata": {},
   "source": [
    "How much did each part of the tutorial help your understanding?\n",
    "\n",
    "A. The analytical exercises\n",
    "\n",
    "B. Connecting the analytical exercises with a Python implementation\n",
    "\n",
    "C. The open-ended experimental part"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ccfa26ac",
   "metadata": {
    "ecey523": {
     "answer_id": "feedback_ratings"
    }
   },
   "outputs": [],
   "source": [
    "### TODO: ADD YOUR CODE HERE\n",
    "# Rate from 0 (Not at all) to 10 (Very much).\n",
    "\n",
    "feedback_A = None  # analytical exercises\n",
    "feedback_B = None  # connecting analytical exercises with a Python implementation\n",
    "feedback_C = None  # open-ended experimental part\n",
    "### END of TODO"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e36311f",
   "metadata": {
    "ecey523": {
     "answer_id": "feedback_text"
    }
   },
   "source": [
    "_You can write any other comments here._"
   ]
  }
 ],
 "metadata": {
  "ecey523": {
   "assignment": "hw1",
   "format_version": 2,
   "variant": "student"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
