Testing and debugging: Linear Algebra Project#

Today’s assignment.

Linear algebra project in C#

Today we’ll start working on yet another toy project, but it’s getting a bit more realistic. This work (as it expands over the next couple of classes) has multiple objectives:

  • Get some practice with cmake and C / C++.

  • Understand some basic ideas behind object-oriented programming, and learn about structuring your code.

  • Learn some of the ideas behind C++ and how it makes things “prettier” than C.

  • Understand how multi-dimensional arrays work.

There is rather little in terms of (detailed) instructions today. So you’ll have to use whatever resources are available to you (prior knowledge, notes from previous clases, your team members, your friendly instructor, discord, google, AI, whatever…)

First steps#

(Nothing is really new here, but I’ll repeat it here as a checklist which might be useful for future reference until the process becomes natural.)

  • Sign up for the class repository (see link above).

  • VS Code will recognize that this is a cmake project (maybe you’ll have to install the “CMake Tools” extension first), and it’ll automatically configure, and even build your project in a build/ directory at the top-level of the cloned repository. If you prefer to run cmake by hand (again, certainly good practice), I strongly recommend duplicating that structure, ie., creating a build/ directory at the top level of your repo.

  • This assignment is another opportunity to work as a team. You can practice creating your own branch to work in, then pushing that branch to github and opening a pull request, to merge the changes into the main branch, which will make it show up in the “Feedback” pull request. (The “Feedback” pull request is really just a regular pull request, except that it’s not meant to be merged, it’s for me to look at your work and provide feedback.)

    As before, there are many options on how to collaborate, and you can use whatever works best for you. You can work together on the same branch (that probably means one person is doing the actual coding, while the other is providing input and suggestions), switchingroles as you make progress. Or you can each work on your own branch, and then merge your branches together. One way to do this, which will help avoid conflicts is for each team member to make their own branch, implement a certain task, then make a pull request. You may end up with multiple pull requests doing roughly the same thing, so at that point you could discuss which one you like the best, and just merge that one. That would be a good thing to document in the notes in the feedback PR, including a link to the PR(s) that you decided to not merge (and could then close).

Linear Algebra: It all starts with a dot product#

As opposed to Fortran, neither C nor C++ really have much concept of mathematical objects that are useful for numerical computations. So the idea here is to see what it takes to add some basic linear algebra capabilities.

In today’s repo, I started by writing a (very basic) vector_dot() function in c/vector_dot.c:

// ----------------------------------------------------------------------
// vector_dot
//
// returns the dot product of the two vectors
// x: first vector
// y: second vector

double vector_dot(double* x, double* y)
{
  double sum = 0.f;
  for (int i = 0; i < N; i++) {
    sum += x[i] * y[i];
  }
  return sum;
}

I also added a very basic (and not very automatic test). This is where it all begins…

Your turn#

  • Take the test part of the code out of vector_dot.c and make a separate test_vector_dot.c. Update the CMakeLists.txt accordingly.

  • Add a new file vector_add.c, and write a function vector_add(x, y, z) that adds x and y and puts the result into z. Write a test for this routine in a separate file. Update the CMakeLists.txt accordingly.

  • (more challenging) Add a new file matrix_vector_mul.c, and write a function matrix_vector_mul(A, x, y) that calculates y = Ax, where A is a N x N matrix. Write a test for it. Update the CMakeLists.txt accordingly.

  • Add at least one automated test for each of the three functions, which will be run by ctest .. (You can base these tests on testing code you already have.)

  • Build a library linalg that contains the three linear algebra functions above. Change your tests to link with that library.

  • Start thinking about how to make this library more generic, in particular, how to avoid having it work only with the hardcoded vector size N = 3.

Structs, argument passing and pointers#

Let’s switch gears for a minute, as the following will come in handy later. It seems like a good idea at this point to clarify some C/C++ features that often is non-intuitive to programmers not experienced in those languages.

Your turn#

[This may end up being a bit of a struggle. Don’t hesitate to ask for help if you get stuck, and it’s okay if not everything makes sense right now.]

For the following, write some small pieces of code. Create a new directory for it, set up cmake for it, too. Commit the various steps of your test code and copy&paste the output into your feedback pull request, together with some interpretation of what that output means.

  • Write a function void foo(double x). That function should print x, then set x to 10, then print it again. In the main function, make a variable double x = 99.; and pass it to foo(). Print the value of x in the main program before and after you call foo(x). What happens? Why?

  • Rename the variable you’re using in the implementation of foo() to y. What happens? Why?

  • Let’s do this again, but this time with an array of two doubles double arr[2]. Add double arr[2] = { 3., 4. }; to your main program, and implement a corresponding foo_array() function. Repeat the same steps as above, and see what happens. Why?

  • Finally, let’s use a struct – let’s say arr was actually a point, with arr[0] being the x-coordinate and arr[1] the y-coordinate. We can make a struct point:

struct point {
  double x;
  double y;
};

To initialize it, you can do

  struct point p;
  p.x = 2.;
  p.y = 3.;
  // or all in one line:
  // struct point p = { .x = 2., .y = 2. };

Pass p to a new foo_point() function, and implement that function to print its argument there, and reassign new values. Again, what happens, and why?

  • [This is not trivial]. Change your code such that the new values assigned in foo...() actually make it back to the calling main() function, for all of the variants of foo...() you have.

Homework#

  • As usual, finish the class work above, and don’t forget to commit as you go.

  • As I mentioned in the previous class, I’m giving you an opportunity to have a second go at the class 3/4 homework and add a reflection, as an opportunity to get back some missing credit for it.

    In particular, in order to do so, go through the sample solution for the class 3/4 homework, and using that, review your own work by adding comments to your feedback PR. You don’t have to redo everything, but you should at least identify what you missed, what you got right, what you learned, and what still doesn’t make sense. You can go and do some things you have mixed previously in your own code, by making more commits and pushing (syncing) them.