├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── exercises ├── 10-threads │ ├── Makefile │ ├── README.md │ ├── area.cpp │ ├── instructions.md │ └── instructions.pdf ├── 2.1-class-types │ ├── Makefile │ ├── README.md │ ├── complex.cpp │ ├── complex.hpp │ └── test.cpp ├── 2.2-complex │ ├── Makefile │ ├── README.md │ ├── complex.cpp │ ├── complex.hpp │ └── test.cpp ├── 3-containers │ ├── README.md │ ├── part1 │ │ ├── Makefile │ │ ├── test.cpp │ │ ├── vector_ex.cpp │ │ └── vector_ex.hpp │ └── part2 │ │ ├── Makefile │ │ ├── test.cpp │ │ ├── vector_ex.cpp │ │ └── vector_ex.hpp ├── 4-pointers │ ├── Makefile │ └── pointers.cpp ├── 5-templates │ ├── README.md │ ├── part1 │ │ ├── Makefile │ │ └── sum.cpp │ └── part2 │ │ ├── Makefile │ │ ├── complex.cpp │ │ ├── complex.hpp │ │ └── test.cpp ├── 6.1-my-array │ ├── part1 │ │ ├── Makefile │ │ └── my_array.cpp │ ├── part2 │ │ ├── Makefile │ │ └── my_array.cpp │ └── part3 │ │ ├── Makefile │ │ └── my_array.cpp ├── 6.2-special-pointers │ ├── part1 │ │ ├── Makefile │ │ └── unique.cpp │ └── part2 │ │ ├── Makefile │ │ └── shared.cpp ├── 6.3-morton-order │ ├── Makefile │ ├── README.md │ ├── bits.hpp │ ├── config.mk │ ├── instructions.md │ ├── instructions.pdf │ ├── mortonorder.png │ ├── range.hpp │ ├── step1 │ │ ├── Makefile │ │ ├── matrix.hpp │ │ └── test_matrix_base.cpp │ ├── step2 │ │ ├── Makefile │ │ ├── matrix.hpp │ │ ├── test_matrix_base.cpp │ │ └── test_matrix_iter.cpp │ ├── test.hpp │ └── test_bits.cpp ├── 7-inheritance │ ├── part1 │ │ ├── Makefile │ │ ├── complex.cpp │ │ ├── complex.hpp │ │ └── test.cpp │ ├── part2 │ │ ├── Makefile │ │ ├── complex.cpp │ │ ├── complex.hpp │ │ └── test.cpp │ ├── part3 │ │ ├── Makefile │ │ ├── complex.cpp │ │ ├── complex.hpp │ │ └── test.cpp │ └── part4 │ │ ├── main.cpp │ │ ├── poly.cpp │ │ └── poly.hpp ├── 8-algorithm │ ├── Makefile │ ├── README.md │ └── ex.cpp ├── 9-eigen │ ├── Makefile │ ├── README.md │ ├── explicit.cpp │ ├── implicit.cpp │ ├── modules.sh │ ├── movie.py │ └── sparse.cpp ├── README.md └── include │ └── catch.hpp └── lectures ├── 0-course-intro-2-days ├── README.md └── index.html ├── 0-course-intro-3-days ├── README.md └── index.html ├── 1-cpp-intro ├── README.md ├── auto │ └── auto.cpp ├── frank_mon.jpg ├── hello │ └── hello.cpp ├── index.html ├── octodog.jpg ├── sak.jpg └── sum │ └── sum.cpp ├── 10.1-threads ├── README.md └── index.html ├── 10.2-threads-cont ├── README.md ├── index.html └── omp-v-thread.png ├── 2-classes ├── README.md ├── complex_numbers.svg └── index.html ├── 3-loops-containers ├── README.md ├── domain_decomp.png └── index.html ├── 4-resources ├── README.md ├── index.html ├── mem_layout.jpg └── sample │ ├── .gitignore │ ├── Makefile │ ├── arr1.cpp │ ├── arr2.cpp │ ├── arr3.cpp │ ├── dyn1.cpp │ ├── dyn2.cpp │ ├── dyn3.cpp │ └── shared.cpp ├── 5-templates ├── README.md └── index.html ├── 6.1-RAII ├── README.md ├── index.html ├── mem_layout.jpg └── sample │ ├── .gitignore │ ├── Makefile │ ├── arr1.cpp │ ├── arr2.cpp │ ├── arr3.cpp │ ├── dyn1.cpp │ ├── dyn2.cpp │ ├── dyn3.cpp │ └── shared.cpp ├── 6.2-RAII-smart-pointers ├── README.md ├── index.html ├── mem_layout.jpg └── sample │ ├── .gitignore │ ├── Makefile │ ├── arr1.cpp │ ├── arr2.cpp │ ├── arr3.cpp │ ├── dyn1.cpp │ ├── dyn2.cpp │ ├── dyn3.cpp │ └── shared.cpp ├── 7-combining-classes ├── Polymorphism-in-CPP.png ├── README.md ├── diamond-problem-in-cpp.webp └── index.html ├── 8-algorithms-lambdas ├── README.md ├── index.html └── looptests │ ├── opt0.svg │ └── opt2.svg ├── 9-eigen ├── README.md └── index.html ├── README.md └── template ├── cpptheme.js ├── mathjax-setup.js ├── style.css └── thumbs_up.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | catch.hpp 3 | 4 | # Ignore executables created by the exercises 5 | exercises/2.1-class-types/test 6 | exercises/2.2-complex/test 7 | exercises/3-containers/part1/test 8 | exercises/3-containers/part2/test 9 | exercises/4-pointers/pointers 10 | exercises/5-templates/part1/sum 11 | exercises/5-templates/part2/test 12 | exercises/6.1-my-array/part*/my_array 13 | exercises/6.2-special-pointers/part1/unique 14 | exercises/6.2-special-pointers/part2/shared 15 | exercises/8-algorithm/ex 16 | exercises/9-eigen/explicit 17 | exercises/9-eigen/implicit 18 | exercises/9-eigen/sparse 19 | exercises/9-eigen/*.txt 20 | exercises/9-eigen/*.gif 21 | exercises/10-threads/area 22 | 23 | lectures/1-cpp-intro/auto/auto 24 | lectures/1-cpp-intro/hello/hello 25 | lectures/1-cpp-intro/sum/sum 26 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Rupert Nash 2 | Joseph Lee -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern C++ for Computational Scientists 2 | 3 | [Repository view](https://github.com/EPCCed/archer2-cpp/) 4 | 5 | [Pages view](https://epcced.github.io/archer2-cpp/) 6 | 7 | Since the 2011 revision to the C++ language and standard library, the 8 | ways it is now being used are quite different. Used well, these 9 | features enable the programmer to write elegant, reusable and portable 10 | code that runs efficiently on a variety of architectures. 11 | 12 | However it is still a very large and complex tool. This set of 13 | lectures and practical exercises, will cover a minimal set of features 14 | to allow an experienced non-C++ programmer to get to grips with 15 | language. These include: 16 | * defining your own types 17 | * overloading 18 | * templates 19 | * containers 20 | * iterators 21 | * lambdas 22 | * standard algorithms 23 | * threading 24 | 25 | It concludes with a brief discussion of modern frameworks for portable 26 | parallel performance which are commonly implemented in C++. 27 | 28 | * [Lectures](lectures/) 29 | * [Practical exercises](exercises/) 30 | -------------------------------------------------------------------------------- /exercises/10-threads/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for mandelbrot area code 2 | 3 | # 4 | # C compiler and options for Intel 5 | # 6 | CC= g++ 7 | CFLAGS = -O2 -std=c++11 8 | LIB= -lm -lpthread 9 | 10 | # 11 | # Object files 12 | # 13 | OBJ= area.o 14 | 15 | # 16 | # Compile 17 | # 18 | area: $(OBJ) 19 | $(CC) -o $@ $(OBJ) $(LIB) 20 | 21 | .cpp.o: 22 | $(CC) $(CFLAGS) -c $< 23 | 24 | # 25 | # Clean out object files and the executable. 26 | # 27 | clean: 28 | rm *.o area 29 | -------------------------------------------------------------------------------- /exercises/10-threads/README.md: -------------------------------------------------------------------------------- 1 | This document is available in multiple formats: 2 | * [PDF](instructions.pdf) 3 | * [Markdown](instructions.md) 4 | -------------------------------------------------------------------------------- /exercises/10-threads/area.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using complex = std::complex; 6 | 7 | // Determine if a complex number is inside the set 8 | bool in_mandelbrot(const complex& c) { 9 | const auto MAXITER = 2000; 10 | auto z = c; 11 | for (auto i = 0; i < MAXITER; ++i) { 12 | z = z*z + c; 13 | if (std::norm(z) > 4.0) 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | int main() { 20 | const auto NPOINTS = 2000; 21 | 22 | const auto scale_real = 2.5; 23 | const auto scale_imag = 1.125; 24 | const auto eps = 1.0e-7; 25 | const auto shift = complex{-2.0 + eps, 0.0 + eps}; 26 | using clock = std::chrono::high_resolution_clock; 27 | auto start = clock::now(); 28 | 29 | // Outer loops run over npoints, initialise z=c 30 | // Inner loop has the iteration z=z*z+c, and threshold test 31 | int num_inside = 0; 32 | for (int i = 0; i < NPOINTS; ++i) { 33 | for (int j = 0; j < NPOINTS; ++j) { 34 | const auto c = shift + complex{(scale_real * i) / NPOINTS, 35 | (scale_imag * j) / NPOINTS}; 36 | if (in_mandelbrot(c)) 37 | num_inside++; 38 | } 39 | } 40 | auto finish = clock::now(); 41 | 42 | // Calculate area and error and output the results 43 | auto area = 2.0 * scale_real * scale_imag * double(num_inside) / double(NPOINTS * NPOINTS); 44 | auto error = area / double(NPOINTS); 45 | 46 | std::printf("Area of Mandlebrot set = %12.8f +/- %12.8f\n", area, error); 47 | auto dt = std::chrono::duration(finish-start); 48 | std::printf("Time = %12.8f seconds\n", dt.count()); 49 | } 50 | -------------------------------------------------------------------------------- /exercises/10-threads/instructions.md: -------------------------------------------------------------------------------- 1 | # C++ threads 2 | ## Mark Bull 3 | ## m.bull@epcc.ed.ac.uk 4 | 5 | Source for this can be obtained from Github. Get a new copy with: 6 | 7 | ``` 8 | git clone https://github.com/EPCCed/archer2-cpp 9 | ``` 10 | 11 | or update your existing one with 12 | 13 | ``` 14 | git pull 15 | ``` 16 | 17 | then you can 18 | 19 | ``` 20 | cd archer2-cpp/exercises/threads 21 | ``` 22 | 23 | `area.cpp` contains a C++ version of the Mandelbrot example which you used in Threaded Programming. 24 | 25 | Parallelise the outer loop of the main computation using C++ 26 | threads. Try using either a function pointer or a lambda expression. 27 | 28 | Try different mechanisms for synchronising the update to the shared 29 | counter: a mutex, and lock guard or an atomic int. 30 | 31 | -------------------------------------------------------------------------------- /exercises/10-threads/instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/exercises/10-threads/instructions.pdf -------------------------------------------------------------------------------- /exercises/2.1-class-types/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 -I../include 2 | 3 | test : complex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | test.o : catch.hpp 7 | 8 | catch.hpp : 9 | wget https://github.com/catchorg/Catch2/releases/download/v2.13.6/catch.hpp 10 | 11 | run : test 12 | ./test 13 | 14 | clean : 15 | rm -rf *.o test 16 | -------------------------------------------------------------------------------- /exercises/2.1-class-types/README.md: -------------------------------------------------------------------------------- 1 | # Class types exercise 2 | 3 | In your clone of this repository, find the `2.1-class-types` exercise and list the files 4 | 5 | ``` 6 | $ cd archer2-cpp/exercises/2.1-class-types 7 | $ ls 8 | Makefile README.md complex.cpp complex.hpp test.cpp 9 | ``` 10 | 11 | The files `complex.hpp` and `complex.cpp` contain the beginings of a complex number class. Follow the instructions in the comments to complete the missing declarations in `complex.hpp` and then add the required out of line definitions in `complex.cpp`. 12 | 13 | To test your implementation, `test.cpp` holds some basic unit tests created using the Catch2 unit testing library. 14 | 15 | You can compile by running: 16 | 17 | ``` 18 | $ make 19 | g++ --std=c++14 -I../include -c -o complex.o complex.cpp 20 | g++ complex.o test.o -o test 21 | ``` 22 | 23 | Then run the tests using: 24 | 25 | ``` 26 | $ ./test 27 | =============================================================================== 28 | All tests passed (36 assertions in 5 test cases) 29 | ``` 30 | -------------------------------------------------------------------------------- /exercises/2.1-class-types/complex.cpp: -------------------------------------------------------------------------------- 1 | #include "complex.hpp" 2 | #include 3 | 4 | Complex::Complex(double real) : re(real) { 5 | } 6 | 7 | Complex::Complex(double real, double imag) : re(real), im(imag) { 8 | } 9 | 10 | double Complex::real() { 11 | // Return real component 12 | } 13 | 14 | /* Add definition of a member function to access the imaginary component */ 15 | 16 | Complex Complex::conj() { 17 | // Return complex conjugate 18 | } 19 | 20 | /* Add definition of 'norm' member function. Hint: Look up std::sqrt from the 21 | cmath header to help calculate the magnitude of a complex number */ 22 | 23 | /* Add definition of 'add' member function */ 24 | 25 | bool Complex::equals(Complex other_complex) { 26 | // Return true if the real and imaginary parts of the complex numbers are 27 | // equal. False otherwise. 28 | } -------------------------------------------------------------------------------- /exercises/2.1-class-types/complex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_COMPLEX_COMPLEX_HPP 2 | #define CPPEX_COMPLEX_COMPLEX_HPP 3 | 4 | // Simple complex number class 5 | class Complex { 6 | public: 7 | /* Add declarations to create: 8 | - A default constructor 9 | - A constructor using just a real component 10 | - A constructor using real and imaginary components 11 | */ 12 | 13 | // Access components 14 | double real(); 15 | /* Add declaration to access the imaginary component */ 16 | 17 | // Compute the complex conjugate 18 | Complex conj(); 19 | 20 | /* Add declaration for member function 'norm' that takes no arguments and 21 | returns the magnitude of the complex number. 22 | */ 23 | 24 | /* Add declaration for an 'add' member function as so: z = i.add(j) 25 | I.e. For complex numbers i and j, z is the result of i + j. 26 | */ 27 | 28 | // Check if two complex numbers are equal 29 | bool equals(Complex other_complex); 30 | 31 | /* Add private member variables to store the real and imaginary components of 32 | the complex number. These should have type 'double' and a suitable default 33 | value. 34 | */ 35 | }; 36 | 37 | #endif -------------------------------------------------------------------------------- /exercises/2.1-class-types/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "complex.hpp" 7 | 8 | TEST_CASE("Complex numbers are constructed real/imag parts readable") { 9 | Complex zero; 10 | REQUIRE(zero.real() == 0.0); 11 | REQUIRE(zero.imag() == 0.0); 12 | 13 | Complex one{1.0}; 14 | REQUIRE(one.real() == 1.0); 15 | REQUIRE(one.imag() == 0.0); 16 | 17 | Complex i{0, 1}; 18 | REQUIRE(i.real() == 0.0); 19 | REQUIRE(i.imag() == 1.0); 20 | 21 | Complex z1{1, -83}; 22 | Complex z2 = z1; 23 | REQUIRE(z1.real() == z2.real()); 24 | REQUIRE(z1.imag() == z2.imag()); 25 | REQUIRE(z2.real() == 1.0); 26 | REQUIRE(z2.imag() == -83.0); 27 | } 28 | 29 | TEST_CASE("Complex numbers can have magnitude computed") { 30 | REQUIRE(Complex{}.norm() == 0.0); 31 | REQUIRE(Complex{3,4}.norm() == 5.0); 32 | } 33 | 34 | // Pure real => z == z* 35 | void CheckConjReal(double x) { 36 | Complex z{x}; 37 | Complex z_conj = z.conj(); 38 | REQUIRE(z_conj.equals(z)); 39 | } 40 | // Pure imaginary => z* == -z 41 | void CheckConjImag(double y) { 42 | Complex z{0.0, y}; 43 | Complex expected{0.0, -y}; 44 | Complex z_conj = z.conj(); 45 | REQUIRE(z_conj.equals(expected)); 46 | } 47 | 48 | TEST_CASE("Complex numbers be conjugated") { 49 | CheckConjReal(0); 50 | CheckConjReal(1); 51 | CheckConjReal(-3.14); 52 | CheckConjReal(1.876e6); 53 | 54 | CheckConjImag(0); 55 | CheckConjImag(1); 56 | CheckConjImag(-3.14); 57 | CheckConjImag(1.876e6); 58 | } 59 | 60 | TEST_CASE("Complex numbers can be compared") { 61 | Complex zero; 62 | Complex one{1.0}; 63 | Complex i{0, 1}; 64 | REQUIRE(zero.equals(zero)); 65 | REQUIRE(one.equals(one)); 66 | REQUIRE(i.equals(i)); 67 | REQUIRE(!zero.equals(one)); 68 | REQUIRE(!zero.equals(i)); 69 | REQUIRE(!one.equals(i)); 70 | } 71 | 72 | void CheckZplusZeq2Z(Complex z) { 73 | Complex expected = Complex{2*z.real(), 2*z.imag()}; 74 | Complex result = z.add(z); 75 | REQUIRE(result.equals(expected)); 76 | } 77 | void CheckZminusZeq0(Complex z) { 78 | Complex expected = Complex{}; 79 | Complex z_minus = Complex{-z.real(), -z.imag()}; 80 | Complex result = z.add(z_minus); 81 | REQUIRE(result.equals(expected)); 82 | } 83 | 84 | TEST_CASE("Complex number can be added") { 85 | CheckZplusZeq2Z(1); 86 | CheckZplusZeq2Z(0); 87 | CheckZplusZeq2Z(-1); 88 | CheckZplusZeq2Z(Complex{1, 2}); 89 | CheckZplusZeq2Z(Complex{-42, 1e-3}); 90 | 91 | CheckZminusZeq0(1); 92 | CheckZminusZeq0(0); 93 | CheckZminusZeq0(-1); 94 | CheckZminusZeq0(Complex{1, 2}); 95 | CheckZminusZeq0(Complex{-42, 1e-3}); 96 | } -------------------------------------------------------------------------------- /exercises/2.2-complex/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 -I../include 2 | 3 | test : complex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | test.o : catch.hpp 7 | 8 | catch.hpp : 9 | wget https://github.com/catchorg/Catch2/releases/download/v2.13.6/catch.hpp 10 | 11 | run : test 12 | ./test 13 | 14 | clean : 15 | rm -rf *.o test 16 | -------------------------------------------------------------------------------- /exercises/2.2-complex/README.md: -------------------------------------------------------------------------------- 1 | # Complex class exercise 2 | 3 | In your clone of this repository, find the `complex` exercise and list the files 4 | 5 | ``` 6 | $ cd archer2-cpp/exercises/complex$ ls Makefile complex.cpp complex.hpp test.cpp 7 | ``` 8 | 9 | The files `complex.hpp` and `complex.cpp` contain a partially working complex number class and `test.cpp` holds some basic unit tests. 10 | 11 | You can compile and run with: 12 | 13 | ``` 14 | $ make && ./test 15 | g++ --std=c++14 -I../include -c -o complex.o complex.cpp 16 | g++ --std=c++14 -I../include -c -o test.o test.cpp 17 | g++ complex.o test.o -o test 18 | =============================================================================== 19 | All tests passed (34 assertions in 6 test cases) 20 | ``` 21 | 22 | But to get to this point you need to complete the code and fix a few bugs! 23 | -------------------------------------------------------------------------------- /exercises/2.2-complex/complex.cpp: -------------------------------------------------------------------------------- 1 | #include "complex.hpp" 2 | #include 3 | 4 | 5 | 6 | double const& Complex::real() { 7 | return re; 8 | } 9 | 10 | double Complex::imag() const { 11 | return im; 12 | } 13 | 14 | Complex Complex::conj() const { 15 | return Complex{re, -im}; 16 | } 17 | 18 | double Complex::norm() const { 19 | return std::sqrt(norm2()); 20 | } 21 | 22 | double Complex::norm2() const { 23 | return re*re + im*im; 24 | } 25 | 26 | bool operator==(Complex const& a, Complex const& b) { 27 | return (a.re == b.re) && (a.im == b.re); 28 | } 29 | bool operator!=(Complex const& a, Complex const& b) { 30 | return !(a == b); 31 | } 32 | 33 | Complex operator+(Complex const& a, Complex const& b) { 34 | return Complex{a.re + b.re, a.im + b.im}; 35 | } 36 | 37 | 38 | Complex operator*(Complex const& a, Complex const& b) { 39 | // (a + ib)*(c + id) == (a*c - b*d) + i(b*c + a*d) 40 | } 41 | 42 | Complex operator-(Complex const& a) { 43 | return Complex{-a.re, -a.im}; 44 | } 45 | -------------------------------------------------------------------------------- /exercises/2.2-complex/complex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_COMPLEX_COMPLEX_HPP 2 | #define CPPEX_COMPLEX_COMPLEX_HPP 3 | 4 | // Simple complex number class 5 | class Complex { 6 | public: 7 | // Default value is zero 8 | Complex() = default; 9 | // Construct purely real complex 10 | // Construct from real and imaginary parts 11 | 12 | // Access components 13 | double real() const; 14 | double imag() const; 15 | 16 | // Compute the complex conjugate 17 | Complex conj(); 18 | 19 | // Compute the magnitude and squared magnitude 20 | double norm() const; 21 | double norm2() const; 22 | 23 | // Declare comparisons 24 | friend bool operator==(Complex const& a, Complex const& b); 25 | friend bool operator!=(Complex const& a, Complex const& b); 26 | 27 | // Declare binary arithmetic operators 28 | friend Complex operator+(Complex const& a, Complex const& b); 29 | friend Complex operator-(Complex const& a, Complex const& b); 30 | friend Complex operator*(Complex const& a, Complex const& b); 31 | friend Complex operator/(Complex const& a, Complex const& b); 32 | // Question: how would you declare multiplication and division by a real number? 33 | 34 | // Unary negation 35 | friend Complex operator-(Complex const& a); 36 | 37 | private: 38 | double re = 0.0; 39 | double im = 0.0; 40 | }; 41 | 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /exercises/2.2-complex/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "complex.hpp" 7 | 8 | TEST_CASE("Complex numbers are constructed real/imag parts readable") { 9 | const Complex zero; 10 | REQUIRE(zero.real() == 0.0); 11 | REQUIRE(zero.imag() == 0.0); 12 | 13 | const Complex one{1.0}; 14 | REQUIRE(one.real() == 1.0); 15 | REQUIRE(one.imag() == 0.0); 16 | 17 | const Complex i{0, 1}; 18 | REQUIRE(i.real() == 0.0); 19 | REQUIRE(i.imag() == 1.0); 20 | 21 | const Complex z1{1, -83}; 22 | const Complex z2 = z1; 23 | REQUIRE(z1.real() == z2.real()); 24 | REQUIRE(z1.imag() == z2.imag()); 25 | REQUIRE(z2.real() == 1.0); 26 | REQUIRE(z2.imag() == -83.0); 27 | } 28 | 29 | TEST_CASE("Complex numbers can be compared") { 30 | const Complex zero; 31 | const Complex one{1.0}; 32 | const Complex i{0, 1}; 33 | REQUIRE(zero == zero); 34 | REQUIRE(one == one); 35 | REQUIRE(i == i); 36 | REQUIRE(zero != one); 37 | REQUIRE(zero != i); 38 | REQUIRE(one != i); 39 | } 40 | 41 | TEST_CASE("Complex numbers can have magnitude computed") { 42 | REQUIRE(Complex{}.norm2() == 0.0); 43 | REQUIRE(Complex{3,4}.norm2() == 25.0); 44 | } 45 | 46 | // Pure real => z == z* 47 | void CheckConjReal(double x) { 48 | Complex z{x}; 49 | REQUIRE(z == z.conj()); 50 | } 51 | // Pure imaginary => z* == -z 52 | void CheckConjImag(double y) { 53 | Complex z{0.0, y}; 54 | 55 | REQUIRE(z == -z.conj()); 56 | } 57 | 58 | TEST_CASE("Complex numbers be conjugated") { 59 | CheckConjReal(0); 60 | CheckConjReal(1); 61 | CheckConjReal(-3.14); 62 | CheckConjReal(1.876e6); 63 | 64 | CheckConjImag(0); 65 | CheckConjImag(1); 66 | CheckConjImag(-3.14); 67 | CheckConjImag(1.876e6); 68 | } 69 | 70 | void CheckZplusZeq2Z(const Complex& z) { 71 | REQUIRE(z + z == Complex{2*z.real(), 2*z.imag()}); 72 | } 73 | void CheckZminusZeq0(const Complex& z) { 74 | REQUIRE(z - z == Complex{}); 75 | } 76 | 77 | TEST_CASE("Complex number can be added and subtracted") { 78 | CheckZplusZeq2Z(1); 79 | CheckZplusZeq2Z(0); 80 | CheckZplusZeq2Z(-1); 81 | 82 | CheckZminusZeq0(1); 83 | CheckZminusZeq0(0); 84 | CheckZminusZeq0(-1); 85 | CheckZminusZeq0(Complex{1,2}); 86 | CheckZminusZeq0(Complex{-42, 1e-3}); 87 | } 88 | 89 | TEST_CASE("Complex numbers can be multiplied") { 90 | const Complex i{0, 1}; 91 | Complex z{1}; 92 | z = z*i; 93 | REQUIRE(z == i); 94 | z = z*i; 95 | REQUIRE(z == Complex{-1}); 96 | z = z*i; 97 | REQUIRE(z == -i); 98 | } 99 | -------------------------------------------------------------------------------- /exercises/3-containers/README.md: -------------------------------------------------------------------------------- 1 | # Containers exercise 2 | 3 | In your clone of this repository, find the `3-containers` exercise. It contains two sub-directories `part1` and `part2`. List 4 | the files in `part1`: 5 | 6 | ```bash 7 | $ cd archer2-cpp/exercises/3-containers/part1 8 | $ ls 9 | Makefile test.cpp vector_ex.cpp vector_ex.hpp 10 | ``` 11 | 12 | As before, `test.cpp` holds some basic unit tests and you can compile with `make`. 13 | 14 | ## Part 1 15 | `vector_ex.cpp`/`.hpp` hold some functions that work on `std::vector` - provide the implementations 16 | 17 | ## Part 2 18 | Copy `vector_ex.cpp`/`.hpp` from part 1 into the part 2 folder. 19 | 20 | Implement, in a new header/implementation pair of files (`map_ex.hpp`/`.cpp`), 21 | a function (`AddWord`) that adds a string to a `std::map` as the key, the value 22 | being the length of the string. 23 | 24 | You will want to find the documentatation for `map` on https://en.cppreference.com/ 25 | -------------------------------------------------------------------------------- /exercises/3-containers/part1/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 -I../../include 2 | 3 | test : vector_ex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | run : test 7 | ./test 8 | 9 | clean : 10 | rm -rf *.o test 11 | -------------------------------------------------------------------------------- /exercises/3-containers/part1/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "vector_ex.hpp" 7 | #include 8 | 9 | // type alias 10 | using IV = std::vector; 11 | 12 | IV IntRange(int n) { 13 | auto ans = IV(n); 14 | for (int i = 0; i < n; ++i) 15 | ans[i] = i; 16 | return ans; 17 | } 18 | 19 | TEST_CASE("Vector Even") { 20 | IV const empty; 21 | REQUIRE(empty == GetEven(empty)); 22 | 23 | auto const zero = IntRange(1); 24 | // Test the testing code! 25 | REQUIRE(zero.size() == 1); 26 | REQUIRE(zero[0] == 0); 27 | 28 | REQUIRE(zero == GetEven(zero)); 29 | 30 | auto const zero_one = IntRange(2); 31 | // Test the testing code! 32 | REQUIRE(zero_one.size() == 2); 33 | REQUIRE(zero_one[0] == 0); 34 | REQUIRE(zero_one[1] == 1); 35 | 36 | REQUIRE(zero == GetEven(zero_one)); 37 | 38 | // edited toddler random numbers 39 | auto const random = IV{8, 127356, 1, 29, 4376, 263478, 1537}; 40 | auto const random_even = IV{ 8, 127356, 4376, 263478}; 41 | REQUIRE(GetEven(random) == random_even); 42 | } 43 | 44 | std::string STR(IV const& v) { 45 | std::stringstream ss; 46 | PrintVectorOfInt(ss, v); 47 | return ss.str(); 48 | } 49 | 50 | TEST_CASE("Vector Print") { 51 | REQUIRE(STR(IV{}) == "[ ]"); 52 | REQUIRE(STR(IntRange(1)) == "[ 0]"); 53 | REQUIRE(STR(IntRange(2)) == "[ 0, 1]"); 54 | // edited toddler random numbers 55 | REQUIRE(STR(IV{8, 127356, 1, 29, 4376, 263478, 1537}) == "[ 8, 127356, 1, 29, 4376, 263478, 1537]"); 56 | } 57 | -------------------------------------------------------------------------------- /exercises/3-containers/part1/vector_ex.cpp: -------------------------------------------------------------------------------- 1 | #include "vector_ex.hpp" 2 | 3 | // std::vector documentation: 4 | // https://en.cppreference.com/w/cpp/container/vector 5 | 6 | std::vector GetEven(std::vector const& source) { 7 | } 8 | 9 | -------------------------------------------------------------------------------- /exercises/3-containers/part1/vector_ex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_VECTOR_EX_HPP 2 | #define CPPEX_VECTOR_EX_HPP 3 | 4 | #include 5 | #include 6 | 7 | // Here we declare two functions. 8 | // Provide implementations, in vector_ex.cpp 9 | 10 | // Given a vector of integers, return a new vector with only the even 11 | // elements from our input 12 | std::vector GetEven(std::vector const& source); 13 | 14 | // Given a vector of ints, print the data to the stream 15 | // [0, 1] 16 | void PrintVectorOfInt(std::ostream& output, std::vector const& data); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /exercises/3-containers/part2/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 -I../../include 2 | 3 | test : vector_ex.o map_ex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | run : test 7 | ./test 8 | 9 | clean : 10 | rm -rf *.o test 11 | -------------------------------------------------------------------------------- /exercises/3-containers/part2/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "vector_ex.hpp" 7 | #include "map_ex.hpp" 8 | #include 9 | 10 | // type alias 11 | using IV = std::vector; 12 | 13 | IV IntRange(int n) { 14 | auto ans = IV(n); 15 | for (int i = 0; i < n; ++i) 16 | ans[i] = i; 17 | return ans; 18 | } 19 | 20 | TEST_CASE("Vector Even") { 21 | IV const empty; 22 | REQUIRE(empty == GetEven(empty)); 23 | 24 | auto const zero = IntRange(1); 25 | // Test the testing code! 26 | REQUIRE(zero.size() == 1); 27 | REQUIRE(zero[0] == 0); 28 | 29 | REQUIRE(zero == GetEven(zero)); 30 | 31 | auto const zero_one = IntRange(2); 32 | // Test the testing code! 33 | REQUIRE(zero_one.size() == 2); 34 | REQUIRE(zero_one[0] == 0); 35 | REQUIRE(zero_one[1] == 1); 36 | 37 | REQUIRE(zero == GetEven(zero_one)); 38 | 39 | // edited toddler random numbers 40 | auto const random = IV{8, 127356, 1, 29, 4376, 263478, 1537}; 41 | auto const random_even = IV{ 8, 127356, 4376, 263478}; 42 | REQUIRE(GetEven(random) == random_even); 43 | } 44 | 45 | std::string STR(IV const& v) { 46 | std::stringstream ss; 47 | PrintVectorOfInt(ss, v); 48 | return ss.str(); 49 | } 50 | 51 | TEST_CASE("Vector Print") { 52 | REQUIRE(STR(IV{}) == "[ ]"); 53 | REQUIRE(STR(IntRange(1)) == "[ 0]"); 54 | REQUIRE(STR(IntRange(2)) == "[ 0, 1]"); 55 | // edited toddler random numbers 56 | REQUIRE(STR(IV{8, 127356, 1, 29, 4376, 263478, 1537}) == "[ 8, 127356, 1, 29, 4376, 263478, 1537]"); 57 | } 58 | 59 | using Word2Len = std::map; 60 | 61 | TEST_CASE("Map adding") { 62 | Word2Len wlens; 63 | 64 | SECTION( "Adding a word works") { 65 | bool did_insert = AddWord(wlens, "test"); 66 | REQUIRE(did_insert); 67 | REQUIRE(wlens.size() == 1); 68 | REQUIRE(wlens.find("test") != wlens.end()); 69 | 70 | // Second time must return false 71 | bool did_insert_second_time = AddWord(wlens, "test"); 72 | REQUIRE(!did_insert_second_time); 73 | REQUIRE(wlens.size() == 1); 74 | REQUIRE(wlens.find("test") != wlens.end()); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /exercises/3-containers/part2/vector_ex.cpp: -------------------------------------------------------------------------------- 1 | #include "vector_ex.hpp" 2 | 3 | // std::vector documentation: 4 | // https://en.cppreference.com/w/cpp/container/vector 5 | 6 | std::vector GetEven(std::vector const& source) { 7 | } 8 | 9 | -------------------------------------------------------------------------------- /exercises/3-containers/part2/vector_ex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_VECTOR_EX_HPP 2 | #define CPPEX_VECTOR_EX_HPP 3 | 4 | #include 5 | #include 6 | 7 | // Here we declare two functions. 8 | // Provide implementations, in vector_ex.cpp 9 | 10 | // Given a vector of integers, return a new vector with only the even 11 | // elements from our input 12 | std::vector GetEven(std::vector const& source); 13 | 14 | // Given a vector of ints, print the data to the stream 15 | // [0, 1] 16 | void PrintVectorOfInt(std::ostream& output, std::vector const& data); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /exercises/4-pointers/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 2 | 3 | pointers : pointers.o 4 | $(CXX) $^ -o $@ 5 | 6 | run : pointers 7 | ./pointers 8 | 9 | clean : 10 | rm -rf *.o pointers -------------------------------------------------------------------------------- /exercises/4-pointers/pointers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | // Print as you go 6 | // Print the value of x 7 | // Print the address of x 8 | // Print the pointer to x 9 | // Print the dereferenced pointer to x 10 | // Print the address of the pointer to x 11 | 12 | int main() { 13 | 14 | // TODO: Create an int x and set it to a value 15 | 16 | 17 | // TODO: Create a point to x called x_ptr 18 | 19 | 20 | std::cout << "----------" << std::endl; 21 | 22 | // TODO: Do all of the printing 23 | 24 | 25 | std::cout << "----------" << std::endl; 26 | 27 | // TODO: Dereference the pointer to x to increment x's value by one 28 | 29 | 30 | std::cout << "----------" << std::endl; 31 | 32 | // TODO: Do all of the printing 33 | 34 | 35 | std::cout << "----------" << std::endl; 36 | 37 | // TODO: Create a double array of size 4 and give it some values 38 | 39 | 40 | // TODO: Create a pointers to the 0th and 1st elements in y 41 | // Call them y0_ptr and y1_ptr 42 | 43 | 44 | std::cout << "----------" << std::endl; 45 | 46 | // TODO: Do all of the printing but for y 47 | 48 | 49 | std::cout << "----------" << std::endl; 50 | 51 | // TODO: Increment the y0_ptr 52 | 53 | 54 | std::cout << "----------" << std::endl; 55 | 56 | // TODO: Do all of the printing but for y 57 | 58 | std::cout << "----------" << std::endl; 59 | 60 | return 0; 61 | } -------------------------------------------------------------------------------- /exercises/5-templates/README.md: -------------------------------------------------------------------------------- 1 | # Templates exercise 2 | 3 | In your clone of this repository, find the `5-templates` exercise. It contains two sub-directories `part1` and `part2`. 4 | 5 | ## Part 1 6 | 7 | List the files in `part1`: 8 | 9 | ```bash 10 | $ cd archer2-cpp/exercises/5-templates/part1 11 | $ ls 12 | Makefile sum.cpp 13 | ``` 14 | 15 | 1. Have a look at `sum.cpp`. Do you think it will compile? If so, what will the output be? 16 | 2. Compile and run the provided code with `make && ./sum`. Is the result what you expected? Can you explain what is happening at line 12? 17 | ```C++ 18 | 11 sum(3, 4); 19 | 12 sum(3.2, 5.1); 20 | 13 // sum("Hello", "World!"); 21 | ``` 22 | 3. Uncomment line 13. What happens when you try to compile? 23 | 3. Change the `sum()` function to use type templating. How does this change the output? Hint: C++ will not automatically convert from a char array to std::string so you will need to be explicit. 24 | 25 | 26 | ## Part 2 27 | 28 | List the files in `part2`: 29 | 30 | ```bash 31 | $ cd archer2-cpp/exercises/5-templates/part2 32 | $ ls 33 | Makefile complex.cpp complex.hpp test.cpp 34 | ``` 35 | 36 | `complex.cpp` contains a working version of the complex number class. Change the class declaration and definitions to use type templating. 37 | -------------------------------------------------------------------------------- /exercises/5-templates/part1/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 -I../../include 2 | 3 | sum : sum.o 4 | $(CXX) $^ -o $@ 5 | 6 | run : sum 7 | ./sum 8 | 9 | clean : 10 | rm -rf *.o sum 11 | -------------------------------------------------------------------------------- /exercises/5-templates/part1/sum.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int sum(const int& a, const int& b) { 4 | int result = a + b; 5 | 6 | std::cout << a << " + " << b << " = " << result << std::endl; 7 | return result; 8 | } 9 | 10 | int main() { 11 | // 3 + 4 = 7 12 | sum(3, 4); 13 | 14 | // 3.2 + 5.1 = 8.3 15 | sum(3.2, 5.1); 16 | 17 | // sum("Hello", "World!"); 18 | } -------------------------------------------------------------------------------- /exercises/5-templates/part2/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 -I../../include 2 | 3 | test : complex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | run : test 7 | ./test 8 | 9 | clean : 10 | rm -rf *.o test 11 | -------------------------------------------------------------------------------- /exercises/5-templates/part2/complex.cpp: -------------------------------------------------------------------------------- 1 | #include "complex.hpp" 2 | #include 3 | 4 | // Hint: Templates should be defined in the .hpp file 5 | // Will any of these functions remain here? 6 | 7 | Complex::Complex(double real) : re(real) {} 8 | Complex::Complex(double real, double imag) : re(real), im(imag) {} 9 | 10 | double Complex::real() const { 11 | return re; 12 | } 13 | 14 | double Complex::imag() const { 15 | return im; 16 | } 17 | 18 | Complex Complex::conj() const { 19 | return Complex{re, -im}; 20 | } 21 | 22 | double Complex::norm() const { 23 | return std::sqrt(norm2()); 24 | } 25 | 26 | double Complex::norm2() const { 27 | return re*re + im*im; 28 | } 29 | 30 | bool operator==(Complex const& a, Complex const& b) { 31 | return (a.re == b.re) && (a.im == b.im); 32 | } 33 | bool operator!=(Complex const& a, Complex const& b) { 34 | return !(a == b); 35 | } 36 | 37 | Complex operator+(Complex const& a, Complex const& b) { 38 | return Complex{a.re + b.re, a.im + b.im}; 39 | } 40 | 41 | Complex operator-(Complex const& a, Complex const& b) { 42 | return Complex{a.re - b.re, a.im - b.im}; 43 | } 44 | 45 | Complex operator*(Complex const& a, Complex const& b) { 46 | return Complex{a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re}; 47 | } 48 | 49 | Complex operator-(Complex const& a) { 50 | return Complex{-a.re, -a.im}; 51 | } 52 | -------------------------------------------------------------------------------- /exercises/5-templates/part2/complex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_COMPLEX_COMPLEX_HPP 2 | #define CPPEX_COMPLEX_COMPLEX_HPP 3 | 4 | // Simple complex number class 5 | /* Add template typename definition here */ 6 | class Complex { 7 | public: 8 | // Default value is zero 9 | Complex() = default; 10 | Complex(double real); 11 | Complex(double real, double imag); 12 | 13 | // Access components 14 | double real() const; 15 | double imag() const; 16 | 17 | // Compute the complex conjugate 18 | Complex conj() const; 19 | 20 | // Compute the magnitude and squared magnitude 21 | double norm() const; 22 | double norm2() const; 23 | 24 | // Declare comparisons 25 | friend bool operator==(Complex const& a, Complex const& b); 26 | friend bool operator!=(Complex const& a, Complex const& b); 27 | 28 | // Declare binary arithmetic operators - Assume both complex numbers have the same template type. 29 | friend Complex operator+(Complex const& a, Complex const& b); 30 | friend Complex operator-(Complex const& a, Complex const& b); 31 | friend Complex operator*(Complex const& a, Complex const& b); 32 | 33 | /* 34 | Extension: What if I want to add a Complex to a Complex? 35 | What would be returned in this case? What should the return type 36 | be to make this flexible? 37 | Hint: Look up std::common_type from the type_traits header. 38 | 39 | If you complete this extension, uncomment the final test case in 40 | test.cpp to test your implementation. 41 | */ 42 | 43 | // Unary negation 44 | friend Complex operator-(Complex const& a); 45 | 46 | private: 47 | double re = 0.0; 48 | double im = 0.0; 49 | }; 50 | 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /exercises/5-templates/part2/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "complex.hpp" 7 | 8 | TEST_CASE("Complex numbers are constructed real/imag parts readable") { 9 | const Complex zero; 10 | REQUIRE(zero.real() == 0.0); 11 | REQUIRE(zero.imag() == 0.0); 12 | 13 | const Complex one{1}; 14 | REQUIRE(one.real() == 1.0); 15 | REQUIRE(one.imag() == 0.0); 16 | 17 | const Complex i{0, 1}; 18 | REQUIRE(i.real() == 0.0); 19 | REQUIRE(i.imag() == 1.0); 20 | 21 | const Complex z1{1, -83}; 22 | const Complex z2 = z1; 23 | REQUIRE(z1.real() == z2.real()); 24 | REQUIRE(z1.imag() == z2.imag()); 25 | REQUIRE(z2.real() == 1.0); 26 | REQUIRE(z2.imag() == -83.0); 27 | } 28 | 29 | TEST_CASE("Complex numbers can be compared") { 30 | const Complex zero; 31 | const Complex one{1}; 32 | const Complex i{0, 1}; 33 | REQUIRE(zero == zero); 34 | REQUIRE(one == one); 35 | REQUIRE(i == i); 36 | REQUIRE(zero != one); 37 | REQUIRE(zero != i); 38 | REQUIRE(one != i); 39 | } 40 | 41 | TEST_CASE("Complex numbers can have magnitude computed") { 42 | REQUIRE(Complex{}.norm2() == 0.0); 43 | REQUIRE(Complex{3,4}.norm2() == 25.0); 44 | } 45 | 46 | // Pure real => z == z* 47 | void CheckConjReal(double x) { 48 | Complex z{x}; 49 | REQUIRE(z == z.conj()); 50 | } 51 | // Pure imaginary => z* == -z 52 | void CheckConjImag(double y) { 53 | Complex z{0.0, y}; 54 | 55 | REQUIRE(z == -z.conj()); 56 | } 57 | 58 | TEST_CASE("Complex numbers be conjugated") { 59 | CheckConjReal(0); 60 | CheckConjReal(1); 61 | CheckConjReal(-3.14); 62 | CheckConjReal(1.876e6); 63 | 64 | CheckConjImag(0); 65 | CheckConjImag(1); 66 | CheckConjImag(-3.14); 67 | CheckConjImag(1.876e6); 68 | } 69 | 70 | void CheckZplusZeq2Z(const Complex& z) { 71 | REQUIRE(z + z == Complex{2*z.real(), 2*z.imag()}); 72 | } 73 | void CheckZminusZeq0(const Complex& z) { 74 | REQUIRE(z - z == Complex{}); 75 | } 76 | 77 | TEST_CASE("Complex number can be added and subtracted") { 78 | CheckZplusZeq2Z(1); 79 | CheckZplusZeq2Z(0); 80 | CheckZplusZeq2Z(-1); 81 | 82 | CheckZminusZeq0(1); 83 | CheckZminusZeq0(0); 84 | CheckZminusZeq0(-1); 85 | CheckZminusZeq0(Complex{1,2}); 86 | CheckZminusZeq0(Complex{-42, 1e-3}); 87 | } 88 | 89 | TEST_CASE("Complex numbers can be multiplied") { 90 | const Complex i{0, 1}; 91 | Complex z{1}; 92 | z = z*i; 93 | REQUIRE(z == i); 94 | z = z*i; 95 | REQUIRE(z == Complex{-1}); 96 | z = z*i; 97 | REQUIRE(z == -i); 98 | } 99 | 100 | TEST_CASE("Complex numbers can be templated") { 101 | const Complex z1{static_cast(3.4), static_cast(5.1)}; 102 | const Complex z2{3.4, 5.1}; 103 | REQUIRE(z1 != z2); 104 | REQUIRE(z1.real() == 3); 105 | REQUIRE(z1.imag() == 5); 106 | REQUIRE(z1.norm2() == 34); 107 | } 108 | 109 | /* 110 | Uncomment the code below to test the binary operators extension exercise 111 | 112 | TEST_CASE("Complex number binary operators can use two different template types") { 113 | const Complex z1{3, 5}; 114 | const Complex z2{3.4, 5.1}; 115 | REQUIRE(z1 + z2 == Complex{6.4, 10.1}); 116 | REQUIRE(std::is_same>::value); 117 | REQUIRE(std::is_same>::value); 118 | REQUIRE(std::is_same>::value); 119 | } 120 | */ 121 | -------------------------------------------------------------------------------- /exercises/6.1-my-array/part1/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 2 | 3 | my_array : my_array.o 4 | $(CXX) $^ -o $@ 5 | 6 | run : my_array 7 | ./my_array 8 | 9 | clean : 10 | rm -rf *.o my_array -------------------------------------------------------------------------------- /exercises/6.1-my-array/part1/my_array.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | // TODO: Implement constructor 11 | } 12 | 13 | // Destructor 14 | ~my_array() { 15 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | // TODO: Implement constructor 11 | } 12 | 13 | // Copy constructor 14 | my_array(my_array const& other) { 15 | std::cout << "Copy constructing: " << data << std::endl; 16 | // TODO: Implement copy constructor 17 | } 18 | 19 | // Copy assignment operator 20 | my_array& operator=(my_array const& other) { 21 | std::cout << "Destroying: " << data << std::endl; 22 | // TODO: Implement copy assignment operator 23 | std::cout << "Copy assigning: " << data << std::endl; 24 | } 25 | 26 | // Destructor 27 | ~my_array() { 28 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | // TODO: Implement constructor 11 | } 12 | 13 | // Copy constructor 14 | my_array(my_array const& other) { 15 | std::cout << "Copy constructing: " << data << std::endl; 16 | // TODO: Implement copy constructor 17 | } 18 | 19 | // Copy assignment operator 20 | my_array& operator=(my_array const& other) { 21 | std::cout << "Destroying: " << data << std::endl; 22 | // TODO: Implement copy assignment operator 23 | std::cout << "Copy assigning: " << data << std::endl; 24 | } 25 | 26 | // Move constructor 27 | my_array(my_array&& other) noexcept { 28 | std::cout << "Move construct: " << data << std::endl; 29 | // TODO: Implement move constructor 30 | } 31 | 32 | // Move assignment operator 33 | my_array& operator=(my_array&& other) noexcept { 34 | std::cout << "Move assign: " << data << std::endl; 35 | // TODO: Implement move assignment operator 36 | } 37 | 38 | // Destructor 39 | ~my_array() { 40 | std::cout << "Destroying: " < 2 | #include 3 | 4 | struct A { 5 | void printA() { std::cout << "A struct...." << std::endl; } 6 | }; 7 | 8 | int main() { 9 | 10 | // https://en.cppreference.com/w/cpp/memory/unique_ptr 11 | 12 | // TODO: Define a unique pointer of A called p1 13 | 14 | 15 | p1->printA(); 16 | 17 | // displays address of the containing pointer 18 | std::cout << p1.get() << std::endl; 19 | 20 | // TODO: Make a new unique pointer of A p2 21 | // Set it equal to p1 22 | // What happens when you compile this? 23 | 24 | // TODO: Use move to move p1 into p2 instead, what happens? 25 | 26 | p2->printA(); 27 | std::cout << p1.get() << std::endl; 28 | std::cout << p2.get() << std::endl; 29 | 30 | return 0; 31 | } -------------------------------------------------------------------------------- /exercises/6.2-special-pointers/part2/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 2 | 3 | pointers : shared.o 4 | $(CXX) $^ -o $@ 5 | 6 | run : shared 7 | ./shared 8 | 9 | clean : 10 | rm -rf *.o shared -------------------------------------------------------------------------------- /exercises/6.2-special-pointers/part2/shared.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct A { 5 | void printA() { std::cout << "A struct...." << std::endl; } 6 | }; 7 | 8 | int main() { 9 | 10 | // https://en.cppreference.com/w/cpp/memory/shared_ptr 11 | 12 | // TODO: Create a shared pointer of A called p1 13 | 14 | 15 | // Printing the address of the managed object 16 | std::cout << p1.get() << std::endl; 17 | p1->printA(); 18 | 19 | // Creating a new shared pointer p2 that shares ownership of p1 20 | 21 | p2->printA(); 22 | 23 | // Printing addresses of p1 and p2 24 | std::cout << p1.get() << std::endl; 25 | std::cout << p2.get() << std::endl; 26 | 27 | // TODO: Print the use count of p1 and p2 28 | 29 | // TODO: Look up how to relinquishes ownership of p1 30 | 31 | std::cout << p1.get() << std::endl; 32 | 33 | // TODO: Print the use count of p1 34 | 35 | std::cout << p2.get() << std::endl; 36 | 37 | return 0; 38 | } -------------------------------------------------------------------------------- /exercises/6.3-morton-order/Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | exes = test_bits 3 | 4 | all : $(exes) 5 | 6 | clean : 7 | -rm -f *.o $(exes) 8 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/README.md: -------------------------------------------------------------------------------- 1 | This document is available in multiple formats: 2 | * [PDF](instructions.pdf) 3 | * [Markdown](instructions.md) 4 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/bits.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MORTON_BITS_HPP 2 | #define MORTON_BITS_HPP 3 | #include 4 | 5 | namespace morton { 6 | // Go from bit pattern like 7 | // abcd 8 | // to: 9 | // 0a0b0c0d 10 | inline uint64_t split(const uint32_t a) { 11 | uint64_t x = a; 12 | x = (x | x << 16) & 0x0000ffff0000ffffUL; 13 | x = (x | x << 8) & 0x00ff00ff00ff00ffUL; 14 | x = (x | x << 4) & 0x0f0f0f0f0f0f0f0fUL; 15 | x = (x | x << 2) & 0x3333333333333333UL; 16 | x = (x | x << 1) & 0x5555555555555555UL; 17 | return x; 18 | } 19 | 20 | // Reverse the above 21 | inline uint32_t pack(const uint64_t z) { 22 | uint64_t x = z; 23 | x &= 0x5555555555555555UL; 24 | x = x >> 1 | x; 25 | x &= 0x3333333333333333UL; 26 | x = x >> 2 | x; 27 | x &= 0x0f0f0f0f0f0f0f0fUL; 28 | x = x >> 4 | x; 29 | x &= 0x00ff00ff00ff00ffUL; 30 | x = x >> 8 | x; 31 | x &= 0x0000ffff0000ffffUL; 32 | x = x >> 16| x; 33 | return x; 34 | } 35 | 36 | // Compute the 2d Morton code for a pair of indices 37 | inline uint64_t encode(const uint32_t x, const uint32_t y) { 38 | return split(x) | split(y) << 1; 39 | } 40 | 41 | // Compute the 2 indices from a Morton index 42 | inline void decode(const uint64_t z, uint32_t& x, uint32_t& y) { 43 | uint64_t i = z; 44 | x = pack(i); 45 | uint64_t j = z >> 1; 46 | y = pack(j); 47 | } 48 | 49 | const uint64_t odd_bit_mask = 0x5555555555555555UL; 50 | const uint64_t even_bit_mask = 0xaaaaaaaaaaaaaaaaUL; 51 | 52 | // Move from (i, j) -> (i - 1, j) 53 | inline uint64_t dec_x(const uint64_t z) { 54 | return (((z & odd_bit_mask) - 1) & odd_bit_mask) | (z & even_bit_mask); 55 | } 56 | // Move from (i, j) -> (i + 1, j) 57 | inline uint64_t inc_x(const uint64_t z) { 58 | return (((z | even_bit_mask) + 1) & odd_bit_mask) | (z & even_bit_mask); 59 | } 60 | 61 | // Move from (i, j) -> (i, j - 1) 62 | inline uint64_t dec_y(const uint64_t z) { 63 | return (z & odd_bit_mask) | (((z & even_bit_mask) - 1) & even_bit_mask); 64 | } 65 | // Move from (i, j) -> (i, j + 1) 66 | inline uint64_t inc_y(const uint64_t z) { 67 | return (z & odd_bit_mask) | (((z | odd_bit_mask) + 1) & even_bit_mask); 68 | } 69 | 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/config.mk: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -g --std=c++11 -I.. 2 | CC = $(CXX) 3 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/instructions.md: -------------------------------------------------------------------------------- 1 | 2 | # Morton order matrices in C++ 3 | ## James Richings 4 | ## j.richings@epcc.ed.ac.uk 5 | 6 | 7 | # Sample code 8 | 9 | Source for this can be obtained from Github. Get a new copy with: 10 | 11 | ``` 12 | git clone https://github.com/EPCCed/archer2-cpp 13 | ``` 14 | 15 | or update your existing one with 16 | 17 | ``` 18 | git pull 19 | ``` 20 | 21 | then you can 22 | 23 | ``` 24 | cd archer2-cpp/exercises/morton-order 25 | ``` 26 | 27 | # Morton ordering 28 | 29 | The Morton ordering (or z-ordering) of a matrix lays out the elements 30 | along a recursive z-shaped curve, as shown in the figure of four 31 | iterations of the Z-order curve (from 32 | [z ordered curve](https://en.wikipedia.org/wiki/Z-order_curve)). 33 | 34 | # Indices 35 | 36 | You can compute the Morton index `z` from the x- and y-indices (`i` 37 | and `j` respectively) by interleaving their bits. An example is shown 38 | in the table. 39 | 40 | | | 0 | 1 | 2 | 3 | 41 | |----|----|----|----|----| 42 | | 0 | 0| 1| 4| 5 | 43 | | 1 | 2| 3| 6| 7| 44 | | 2 | 8| 9| 12| 13| 45 | | 3 | 10| 11| 14| 15| 46 | 47 | Mapping between `x-y` indexes and Morton index for a 4 by 4 48 | matrix. Decimal on the left and binary on the right. 49 | 50 | | | 00 | 01 | 10 | 11 | 51 | |----|------|------|------|-----| 52 | | 00 | 0000 | 0001 | 0100 | 0101| 53 | | 01 | 0010 | 0011 | 0110 | 0111| 54 | | 10 | 1000 | 1001 | 1100 | 1101| 55 | | 11 | 1010 | 1011 | 1110 | 1111| 56 | 57 | Mapping between `x-y` indexes and Morton index for a matrix of size 58 | 4-by-4. Decimal on the left and binary on the right. 59 | 60 | 61 | 62 | The advantage of laying out data in this way is that it improves data 63 | locality (and hence cache use) without having to tune a block size or 64 | similar parameter. On a modern multilevel cache machine[^1], this 65 | means it can take good advantage of all the levels without tuning 66 | multiple parameters. 67 | 68 | (E.g. an ARCHER node has L1, L2, and L3 caches, and the RAM is divided 69 | into two NUMA regions. If using a PGAS approach one can view local RAM 70 | as a cache for the distributed memory - i.e. 6 levels!) 71 | 72 | This exercise will walk you through a simple implementation. 73 | 74 | I have included implementations of the functions that do the 75 | "bit-twiddling" for translating between a two-dimensional `x-y` index 76 | and the Morton index, in the file `bits.hpp`. These are reasonably fast, 77 | but can be beaten if you are interested to try! 78 | 79 | In what follows each section corresponds to a subdirectory with the same 80 | number. 81 | 82 | ## Implement the underlying data storage and element access 83 | 84 | Go to the step 1 directory: 85 | 86 | ```bash 87 | cd archer2-cpp/exercises/morton-order/step1 88 | ``` 89 | 90 | Using the partial implemenation in `matrix.hpp`, your task is to 91 | implement the allocation (and release!) of memory to store the data and 92 | to use the helper functions from `bits.hpp` to allow element access. You 93 | will need to implement a number of member functions (marked in the 94 | source with `\\ TODO`) and add whatever data members are needed (marked in 95 | the same way). 96 | 97 | There is a test program `test_matrix_basic.cpp` which runs a few sanity 98 | checks on your implementation (and similarly with `test_bits.cpp`). The 99 | supplied `Makefile` should work. 100 | 101 | ## Implement a basic iterator to traverse the matrix in order 102 | 103 | Go to the step 2 directory: 104 | 105 | ```bash 106 | cd archer2-cpp/exercises/morton-order/step2 107 | ``` 108 | 109 | I have a potential solution to part 1 here, but feel free to copy your 110 | implementation into this. 111 | 112 | The exercise here is to complete the `matrix_iterator` class template 113 | that I have started. I've provided most of the boilerplate to have 114 | this work as a "bidirectional iterator". See 115 | http://en.cppreference.com/w/cpp/concept/BidirectionalIterator for 116 | full details of what this means, but basically it's one that can move 117 | forward and backward through the data. 118 | 119 | Again, the things that need added are marked with `\\TODO`. The most 120 | important thing to think about is how you will refer to the current 121 | position and be able to traverse through it efficiently in Morton 122 | order - the performance should be identical to looping over a raw 123 | pointer! 124 | 125 | 126 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/exercises/6.3-morton-order/instructions.pdf -------------------------------------------------------------------------------- /exercises/6.3-morton-order/mortonorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/exercises/6.3-morton-order/mortonorder.png -------------------------------------------------------------------------------- /exercises/6.3-morton-order/range.hpp: -------------------------------------------------------------------------------- 1 | /* range.hpp 2 | * 3 | * Copyright (c) 2013 Alexander Duchene 4 | * 5 | * This piece of software was created as part of the Drosophila Population 6 | * Genomics Project opensource agreement. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | #ifndef RANGE_ITERATOR_HPP 28 | #define RANGE_ITERATOR_HPP 29 | #include 30 | 31 | namespace rangepp { 32 | 33 | template 34 | class range_impl{ 35 | private: 36 | value_t rbegin; 37 | value_t rend; 38 | value_t step; 39 | int step_end; 40 | public: 41 | range_impl(value_t begin, value_t end, value_t step=1): 42 | rbegin(begin),rend(end),step(step){ 43 | step_end=(rend-rbegin)/step; 44 | if(rbegin+step_end*step != rend){ 45 | step_end++; 46 | } 47 | } 48 | 49 | class iterator: 50 | public std::iterator 51 | { 52 | private: 53 | value_t current_value; 54 | int current_step; 55 | range_impl& parent; 56 | public: 57 | iterator(int start,range_impl& parent): current_step(start), parent(parent){current_value=parent.rbegin+current_step*parent.step;} 58 | value_t operator*() {return current_value;} 59 | const iterator* operator++(){ 60 | current_value+=parent.step; 61 | current_step++; 62 | return this; 63 | } 64 | const iterator* operator++(int){ 65 | current_value+=parent.step; 66 | current_step++; 67 | return this; 68 | } 69 | bool operator==(const iterator& other) { 70 | return current_step==other.current_step; 71 | } 72 | bool operator!=(const iterator& other) { 73 | return current_step!=other.current_step; 74 | } 75 | iterator operator+(int s) { 76 | iterator ret=*this; 77 | ret.current_step+=s; 78 | ret.current_value+=s*parent.step; 79 | return ret; 80 | } 81 | iterator operator-(int s){ 82 | iterator ret=*this; 83 | ret.current_step-=s; 84 | ret.current_value-=s*parent.step; 85 | return ret; 86 | } 87 | const iterator* operator--(){ 88 | current_value-=parent.step; 89 | current_step--; 90 | return this;} 91 | iterator operator--(int){ 92 | iterator old=*this; 93 | current_value-=parent.step; 94 | current_step--; 95 | return old; 96 | } 97 | }; 98 | 99 | iterator begin(){ 100 | return iterator(0,*this); 101 | } 102 | iterator end(){ 103 | return iterator(step_end,*this); 104 | } 105 | 106 | value_t operator[](int s){ 107 | return rbegin+s*step; 108 | } 109 | 110 | int size(){ 111 | return step_end; 112 | } 113 | }; 114 | } 115 | template 116 | auto range(other begin, other end, vt stepsize)->rangepp::range_impl 117 | { 118 | 119 | return rangepp::range_impl(begin,end,stepsize); 120 | } 121 | 122 | template 123 | auto range(b begin, e end) -> rangepp::range_impl 124 | { 125 | return rangepp::range_impl(begin,end,1); 126 | } 127 | 128 | template 129 | rangepp::range_impl range(e end){ 130 | return rangepp::range_impl(0,end,1); 131 | } 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/step1/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.mk 2 | 3 | exes = test_matrix_base 4 | 5 | all : $(exes) 6 | 7 | test_matrix_base : test_matrix_base.cpp matrix.hpp 8 | $(CXX) $(CXXFLAGS) $< -o $@ 9 | 10 | clean : 11 | -rm -f *.o $(exes) 12 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/step1/matrix.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MORTON_MATRIX_HPP 2 | #define MORTON_MATRIX_HPP 3 | 4 | #include 5 | #include "bits.hpp" 6 | 7 | namespace morton { 8 | 9 | // 2D square matrix that stores data in Morton order 10 | // 11 | // NB: 12 | // 13 | // - This simple implementation requires that the size be a power 14 | // of 2 (or zero indicating an empty matrix) 15 | // 16 | // - The matrix does not need to be resizeable 17 | // 18 | // - The matrix must not be implicitly copiable, must use explicit 19 | // duplicate member function 20 | template 21 | class matrix { 22 | public: 23 | // TODO - anything needed? 24 | matrix() : _rank(0) { 25 | } 26 | 27 | // TODO - allocate some memory 28 | matrix(uint32_t r) 29 | { 30 | // Check it's a power of 2. Could consider throwing an 31 | // exception, but these are not in the syllabus! 32 | assert((r & (r-1)) == 0); 33 | } 34 | 35 | // Implicit copying is not allowed 36 | matrix(const matrix& other) = delete; 37 | matrix& operator=(const matrix& other) = delete; 38 | 39 | // Moving is allowed 40 | // TODO - will the default implementations be OK? 41 | matrix(matrix&& other) noexcept; 42 | matrix& operator=(matrix&& other) noexcept; 43 | 44 | // Destructor 45 | // TODO - will the default implemenation be OK? 46 | ~matrix(); 47 | 48 | // Create a new matrix with contents copied from this one 49 | matrix duplicate() const { 50 | // TODO 51 | } 52 | 53 | // Get rank size 54 | uint32_t rank() const { 55 | return _rank; 56 | } 57 | 58 | // Get total size 59 | uint64_t size() const { 60 | return uint64_t(_rank) * uint64_t(_rank); 61 | } 62 | 63 | // TODO 64 | // Const element access 65 | const T& operator()(uint32_t i, uint32_t j) const; 66 | 67 | // TODO 68 | // Mutable element access 69 | T& operator()(uint32_t i, uint32_t j); 70 | 71 | // TODO 72 | // Raw data access (const and mutable versions) 73 | const T* data() const; 74 | T* data(); 75 | 76 | 77 | private: 78 | // rank of matrix 79 | uint32_t _rank; 80 | // Data storage 81 | // TODO - choose how to store data and manage that memory 82 | }; 83 | 84 | } 85 | #endif 86 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/step1/test_matrix_base.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "matrix.hpp" 4 | #include "test.hpp" 5 | #include "range.hpp" 6 | 7 | bool test_small() { 8 | const int N = 4; 9 | morton::matrix small(N); 10 | 11 | // Fill with standard C array layout 1D index 12 | for (auto i: range(N)) 13 | for (auto j: range(N)) 14 | small(i, j) = i*N + j; 15 | 16 | // Matrix contains: 17 | // 0 4 8 12 18 | // 1 5 9 13 19 | // 2 6 10 14 20 | // 3 7 11 15 21 | 22 | auto data = small.data(); 23 | const std::vector expected = { 24 | 0, 4, 1, 5, 8, 12, 9, 13, 25 | 2, 6, 3, 7,10, 14,11, 15 26 | }; 27 | for (auto z : range(N*N)) { 28 | TEST_ASSERT_EQUAL(expected[z], data[z]); 29 | } 30 | return true; 31 | } 32 | 33 | // Helper for making a matrix filled down the diagonal. 34 | // This touches plenty of the pages to ensure mem is actually allocated. 35 | morton::matrix make_diag(uint32_t rank) { 36 | auto mat = morton::matrix(rank); 37 | // fill diagonal 38 | for (auto i: range(rank)) 39 | mat(i,i) = i; 40 | return mat; 41 | } 42 | 43 | bool test_large() { 44 | const int logN = 10; 45 | const int N = 1 << logN; 46 | auto mat = make_diag(N); 47 | 48 | auto data = mat.data(); 49 | uint64_t z = 0; 50 | // pretty easy to convince yourself that the "last" index in each 51 | // successively bigger quad (starting at the origin) is (n^2 - 1) 52 | // where n is the linear size of that. 53 | 54 | // So in the below we're talking about the values 0, 3, 15 55 | 56 | // 0 1 4 5 57 | // 2 3 6 7 58 | // 8 9 12 13 59 | //10 11 14 15 60 | 61 | 62 | for (auto i: range(logN+1)) { 63 | auto n = 1 << i; 64 | auto z = n*n - 1; 65 | TEST_ASSERT_EQUAL(n - 1, data[z]); 66 | } 67 | 68 | return true; 69 | } 70 | 71 | bool test_move() { 72 | auto m1 = make_diag(4); 73 | auto m2 = make_diag(8); 74 | 75 | m2 = std::move(m1); 76 | // m1 is now moved-from: we can't do anything except destroy it or 77 | // assign a new value 78 | TEST_ASSERT_EQUAL(4, m2.rank()); 79 | 80 | // Test return of operator== 81 | const morton::matrix& matref = (m1 = std::move(m2)); 82 | // Also test the const element access version of operator() 83 | for (auto i: range(4)) 84 | for (auto j: range(4)) 85 | TEST_ASSERT_EQUAL(matref(i,j), m1(i,j)); 86 | 87 | 88 | return true; 89 | } 90 | 91 | // Try to ensure we really are deleting used memory 92 | bool test_free() { 93 | const int logN = 10; 94 | const int N = 1 << logN; 95 | for (auto j: range(10000)) 96 | auto mat = make_diag(N); 97 | 98 | return true; 99 | } 100 | 101 | 102 | int main() { 103 | static_assert(!std::is_copy_constructible>::value, 104 | "Require that morton matrix is not copyable"); 105 | static_assert(std::is_move_constructible>::value, 106 | "Require that morton matrix is moveable"); 107 | RUN_TEST(test_small); 108 | RUN_TEST(test_large); 109 | RUN_TEST(test_move); 110 | RUN_TEST(test_free); 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/step2/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.mk 2 | exes = test_matrix_base test_matrix_iter 3 | 4 | all : $(exes) 5 | 6 | test_matrix_base : test_matrix_base.cpp matrix.hpp 7 | $(CXX) $(CXXFLAGS) $< -o $@ 8 | 9 | test_matrix_iter : test_matrix_iter.cpp matrix.hpp 10 | $(CXX) $(CXXFLAGS) $< -o $@ 11 | 12 | clean : 13 | -rm -f *.o $(exes) 14 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/step2/matrix.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MORTON_MATRIX_HPP 2 | #define MORTON_MATRIX_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "bits.hpp" 9 | 10 | namespace morton { 11 | // Forward declare the iterator template 12 | template class matrix_iterator; 13 | 14 | // 2D square matrix that stores data in Morton order 15 | // 16 | // NB: 17 | // 18 | // - This simple implementation requires that the size be a power 19 | // of 2 (or zero indicating an empty matrix) 20 | // 21 | // - The matrix does not need to be resizeable 22 | // 23 | // - The matrix must not be implicitly copiable, must use explicit 24 | // duplicate member function 25 | template 26 | class matrix { 27 | public: 28 | using iterator = matrix_iterator; 29 | using const_iterator = matrix_iterator; 30 | 31 | matrix() : _rank(0) { 32 | } 33 | 34 | matrix(uint32_t r) : _rank(r), _data(new T[r*r]) { 35 | // Check it's a power of 2. Could consider throwing an 36 | // exception, but these are not in the syllabus! 37 | assert((r & (r-1)) == 0); 38 | } 39 | 40 | // Implicit copying is not allowed 41 | matrix(const matrix& other) = delete; 42 | matrix& operator=(const matrix& other) = delete; 43 | 44 | // Moving is allowed 45 | // Default is ok because of choice to use unique_ptr to manage data storage 46 | matrix(matrix&& other) noexcept = default; 47 | matrix& operator=(matrix&& other) noexcept = default; 48 | 49 | // Destructor 50 | // Default ok because of unique_ptr 51 | ~matrix() = default; 52 | 53 | // Create a new matrix with contents copied from this one 54 | matrix duplicate() const { 55 | matrix ans(_rank); 56 | std::copy(begin(), end(), ans.begin()); 57 | return ans; 58 | } 59 | 60 | // Get rank size 61 | uint32_t rank() const { 62 | return _rank; 63 | } 64 | 65 | // Get total size 66 | uint64_t size() const { 67 | return uint64_t(_rank) * uint64_t(_rank); 68 | } 69 | 70 | // Const element access 71 | const T& operator()(uint32_t i, uint32_t j) const { 72 | auto z = encode(i, j); 73 | return _data[z]; 74 | } 75 | 76 | // Mutable element access 77 | T& operator()(uint32_t i, uint32_t j) { 78 | auto z = encode(i, j); 79 | return _data[z]; 80 | } 81 | 82 | // Raw data access (const and mutable versions) 83 | const T* data() const { 84 | return _data.get(); 85 | } 86 | T* data() { 87 | return _data.get(); 88 | } 89 | 90 | // TODO: implement functions to get iterators to first and 91 | // just-past-the-last elements in the matrix 92 | // Mutable iterators 93 | iterator begin() { 94 | } 95 | iterator end() { 96 | } 97 | 98 | // TODO: as above, but const 99 | // Const iterators 100 | const_iterator begin() const { 101 | } 102 | const_iterator end() const { 103 | } 104 | 105 | private: 106 | // rank of matrix 107 | uint32_t _rank; 108 | // Data storage 109 | // Note using array version of unique_ptr 110 | std::unique_ptr _data; 111 | }; 112 | 113 | // Note we inherit from std::iterator. 114 | // This basically ensures our iterator has the right traits to work 115 | // efficiently with the standard library. 116 | // See http://en.cppreference.com/w/cpp/iterator/iterator 117 | 118 | // I've decided this should be a bidirectional iterator - i.e. you 119 | // can move back and forwards. You only need to implement a handful 120 | // of the methods, the rest follow from code I've done. 121 | 122 | // It could relatively easily be changed to a random access iterator 123 | // by adding a few more operations - see: 124 | // https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator 125 | template 126 | class matrix_iterator : 127 | public std::iterator { 129 | public: 130 | // TODO 131 | // Default constructor 132 | matrix_iterator(); 133 | 134 | // Note: must provide copy c'tor, copy assign 135 | // TODO: Decide if the default copy/move/destruct behaviour is 136 | // going to be OK. 137 | 138 | // TODO 139 | // Get the x/y coordinates of the current element 140 | uint32_t x() const { 141 | } 142 | uint32_t y() const { 143 | } 144 | 145 | // Comparison operators. Note these are inline non-member friend 146 | // functions. 147 | friend bool operator==(const matrix_iterator& a, const matrix_iterator& b) { 148 | // TODO 149 | } 150 | // Note this can be done in terms of the above 151 | friend bool operator!=(const matrix_iterator& a, const matrix_iterator& b) { 152 | return !(a == b); 153 | } 154 | 155 | // Dereference operator 156 | T& operator*() { 157 | // TODO 158 | } 159 | 160 | // Preincrement operator 161 | matrix_iterator& operator++() { 162 | // TODO 163 | } 164 | // TODO 165 | // Predecrement operator 166 | matrix_iterator& operator--() { 167 | // TODO 168 | } 169 | 170 | private: 171 | // TODO: declare and define appropriate constructor(s) to create 172 | // iterators pointing into a matrix's data. 173 | // matrix_iterator(...); 174 | 175 | // Other constructors should probably not be publicly visible, so 176 | // we need to allow matrix access. 177 | friend matrix::type>; 178 | 179 | // TODO: Define data members as needed 180 | 181 | }; 182 | 183 | } 184 | #endif 185 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/step2/test_matrix_base.cpp: -------------------------------------------------------------------------------- 1 | ../step1/test_matrix_base.cpp -------------------------------------------------------------------------------- /exercises/6.3-morton-order/step2/test_matrix_iter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "matrix.hpp" 4 | #include "test.hpp" 5 | #include "range.hpp" 6 | 7 | morton::matrix make_filled(int N) { 8 | morton::matrix mat(N); 9 | 10 | // Fill with standard C array layout 1D index 11 | for (auto i: range(N)) 12 | for (auto j: range(N)) 13 | mat(i, j) = i*N + j; 14 | return mat; 15 | } 16 | 17 | // M = matrix or const matrix 18 | template 19 | bool check_mat(M& mat) { 20 | // Matrix contains: 21 | // 0 4 8 12 22 | // 1 5 9 13 23 | // 2 6 10 14 24 | // 3 7 11 15 25 | 26 | // This will store the count of elements visited before the current 27 | // and so should be the Morton index of the element. 28 | int z = 0; 29 | auto it = mat.begin(); 30 | for (; it != mat.end(); ++it, ++z) { 31 | uint32_t i, j; 32 | morton::decode(z, i, j); 33 | int expect = i*N + j; 34 | TEST_ASSERT_EQUAL(expect, *it); 35 | 36 | TEST_ASSERT_EQUAL(i, it.x()); 37 | TEST_ASSERT_EQUAL(j, it.y()); 38 | } 39 | TEST_ASSERT_EQUAL(N*N, z); 40 | 41 | return true; 42 | } 43 | 44 | bool test_mut_iter() { 45 | constexpr int N = 4; 46 | auto mat = make_filled(N); 47 | return check_mat(mat); 48 | } 49 | 50 | // Do the same for the const iterator 51 | bool test_const_iter() { 52 | const int N = 4; 53 | const morton::matrix& mat = make_filled(N); 54 | return check_mat(mat); 55 | } 56 | 57 | bool test_rev_iter() { 58 | const int N = 4; 59 | const morton::matrix& mat = make_filled(N); 60 | 61 | int z = N*N; 62 | auto it = mat.end(); 63 | for (; it != mat.begin();) { 64 | // We want to run the below for it == mat.begin() and then finish 65 | // so move decrement inside the body. 66 | --it; 67 | --z; 68 | 69 | uint32_t i, j; 70 | morton::decode(z, i, j); 71 | 72 | int expect = i*N + j; 73 | TEST_ASSERT_EQUAL(expect, *it); 74 | 75 | TEST_ASSERT_EQUAL(i, it.x()); 76 | TEST_ASSERT_EQUAL(j, it.y()); 77 | } 78 | TEST_ASSERT_EQUAL(0, z); 79 | return true; 80 | } 81 | 82 | int main() { 83 | RUN_TEST(test_mut_iter); 84 | RUN_TEST(test_const_iter); 85 | RUN_TEST(test_rev_iter); 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/test.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MORTON_TEST_HPP 2 | #define MORTON_TEST_HPP 3 | 4 | #include 5 | 6 | // A few macros to help with basic unit testing 7 | 8 | // (Don't want to introduce any dependencies by using a framework) 9 | 10 | #define TEST_ASSERT_EQUAL(expected, actual) \ 11 | if (expected != actual) { \ 12 | std::cerr << "FAIL! Expected '" << expected \ 13 | << "' Got '" << actual << "'" << std::endl; \ 14 | return false; \ 15 | } 16 | 17 | // Runs a nullary predicate as a test 18 | #define RUN_TEST(tfunc) if (tfunc()) \ 19 | std::cerr << #tfunc << ": ok" << std::endl; \ 20 | else \ 21 | std::cerr << #tfunc << ": FAILED" << std::endl; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /exercises/6.3-morton-order/test_bits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "bits.hpp" 3 | #include "test.hpp" 4 | 5 | using namespace morton; 6 | 7 | using p32_64 = std::pair; 8 | const std::vector pdata = { 9 | {0x00000000U, 0x0000000000000000UL}, 10 | {0x00000001U, 0x0000000000000001UL}, 11 | {0x00000002U, 0x0000000000000004UL}, 12 | {0x00000004U, 0x0000000000000010UL}, 13 | {0x00000008U, 0x0000000000000040UL}, 14 | {0x00000010U, 0x0000000000000100UL}, 15 | 16 | {0x0000000fU, 0x0000000000000055UL}, 17 | 18 | {0xffffffffU, 0x5555555555555555UL}, 19 | }; 20 | 21 | 22 | bool test_split() { 23 | for(auto& item: pdata) { 24 | auto res = split(item.first); 25 | // All odd bits must be zero 26 | // 0xa == 0b1010 27 | auto mask = 0xaaaaaaaaaaaaaaaaUL; 28 | if (mask & res) { 29 | std::cerr << "FAIL! Have a non-zero odd bit in " << res << std::endl; 30 | return false; 31 | } 32 | 33 | TEST_ASSERT_EQUAL(item.second, res); 34 | 35 | } 36 | return true; 37 | } 38 | 39 | 40 | bool test_pack() { 41 | 42 | for(auto& item: pdata) { 43 | auto res = pack(item.second); 44 | TEST_ASSERT_EQUAL(item.first, res); 45 | } 46 | return true; 47 | } 48 | 49 | 50 | const std::vector> enc_data = { 51 | {0, 0, 0}, 52 | {1, 0, 1}, 53 | {0, 1, 2}, 54 | {1, 1, 3}, 55 | 56 | {42, 7, 1134}, 57 | {0x45812369U, 0xa7112504U, 0x983b42030c271461UL}, 58 | {0xffffffffU, 0xffffffffU, 0xffffffffffffffffUL} 59 | }; 60 | 61 | bool test_encode() { 62 | for (auto& item: enc_data) { 63 | auto& x = std::get<0>(item); 64 | auto& y = std::get<1>(item); 65 | auto& z = std::get<2>(item); 66 | 67 | auto res = encode(x, y); 68 | TEST_ASSERT_EQUAL(z, res); 69 | 70 | uint32_t rx, ry; 71 | decode(z, rx, ry); 72 | 73 | TEST_ASSERT_EQUAL(x, rx); 74 | TEST_ASSERT_EQUAL(y, ry); 75 | } 76 | return true; 77 | } 78 | 79 | bool test_shift() { 80 | uint64_t start = 0; 81 | auto res = dec_y(dec_x(inc_y(inc_x(start)))); 82 | TEST_ASSERT_EQUAL(start, res); 83 | return true; 84 | 85 | } 86 | int main() { 87 | RUN_TEST(test_split); 88 | RUN_TEST(test_pack); 89 | RUN_TEST(test_encode); 90 | RUN_TEST(test_shift); 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /exercises/7-inheritance/part1/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++14 -I../include 2 | 3 | test : complex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | test.o : catch.hpp 7 | 8 | catch.hpp : 9 | wget https://github.com/catchorg/Catch2/releases/download/v2.13.6/catch.hpp 10 | 11 | run : test 12 | ./test 13 | 14 | clean : 15 | rm -rf *.o test 16 | -------------------------------------------------------------------------------- /exercises/7-inheritance/part1/complex.cpp: -------------------------------------------------------------------------------- 1 | #include "complex.hpp" 2 | #include 3 | 4 | Complex::Complex(double real) : re(real) { 5 | } 6 | 7 | Complex::Complex(double real, double imag) : re(real), im(imag) { 8 | } 9 | 10 | double Complex::real() { 11 | // Return real component 12 | } 13 | 14 | /* Add definition of a member function to access the imaginary component */ 15 | 16 | Complex Complex::conj() { 17 | // Return complex conjugate 18 | } 19 | 20 | /* Add definition of 'norm' member function. Hint: Look up std::sqrt from the 21 | cmath header to help calculate the magnitude of a complex number */ 22 | 23 | /* Add definition of 'add' member function */ 24 | 25 | bool Complex::equals(Complex other_complex) { 26 | // Return true if the real and imaginary parts of the complex numbers are 27 | // equal. False otherwise. 28 | } -------------------------------------------------------------------------------- /exercises/7-inheritance/part1/complex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_COMPLEX_COMPLEX_HPP 2 | #define CPPEX_COMPLEX_COMPLEX_HPP 3 | 4 | // Simple complex number class 5 | class Complex { 6 | public: 7 | /* Add declarations to create: 8 | - A default constructor 9 | - A constructor using just a real component 10 | - A constructor using real and imaginary components 11 | */ 12 | 13 | // Access components 14 | double real(); 15 | /* Add declaration to access the imaginary component */ 16 | 17 | // Compute the complex conjugate 18 | Complex conj(); 19 | 20 | /* Add declaration for member function 'norm' that takes no arguments and 21 | returns the magnitude of the complex number. 22 | */ 23 | 24 | /* Add declaration for an 'add' member function as so: z = i.add(j) 25 | I.e. For complex numbers i and j, z is the result of i + j. 26 | */ 27 | 28 | // Check if two complex numbers are equal 29 | bool equals(Complex other_complex); 30 | 31 | /* Add private member variables to store the real and imaginary components of 32 | the complex number. These should have type 'double' and a suitable default 33 | value. 34 | */ 35 | }; 36 | 37 | #endif -------------------------------------------------------------------------------- /exercises/7-inheritance/part1/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "complex.hpp" 7 | 8 | TEST_CASE("Complex numbers are constructed real/imag parts readable") { 9 | Complex zero; 10 | REQUIRE(zero.real() == 0.0); 11 | REQUIRE(zero.imag() == 0.0); 12 | 13 | Complex one{1.0}; 14 | REQUIRE(one.real() == 1.0); 15 | REQUIRE(one.imag() == 0.0); 16 | 17 | Complex i{0, 1}; 18 | REQUIRE(i.real() == 0.0); 19 | REQUIRE(i.imag() == 1.0); 20 | 21 | Complex z1{1, -83}; 22 | Complex z2 = z1; 23 | REQUIRE(z1.real() == z2.real()); 24 | REQUIRE(z1.imag() == z2.imag()); 25 | REQUIRE(z2.real() == 1.0); 26 | REQUIRE(z2.imag() == -83.0); 27 | } 28 | 29 | TEST_CASE("Complex numbers can have magnitude computed") { 30 | REQUIRE(Complex{}.norm() == 0.0); 31 | REQUIRE(Complex{3,4}.norm() == 5.0); 32 | } 33 | 34 | // Pure real => z == z* 35 | void CheckConjReal(double x) { 36 | Complex z{x}; 37 | Complex z_conj = z.conj(); 38 | REQUIRE(z_conj.equals(z)); 39 | } 40 | // Pure imaginary => z* == -z 41 | void CheckConjImag(double y) { 42 | Complex z{0.0, y}; 43 | Complex expected{0.0, -y}; 44 | Complex z_conj = z.conj(); 45 | REQUIRE(z_conj.equals(expected)); 46 | } 47 | 48 | TEST_CASE("Complex numbers be conjugated") { 49 | CheckConjReal(0); 50 | CheckConjReal(1); 51 | CheckConjReal(-3.14); 52 | CheckConjReal(1.876e6); 53 | 54 | CheckConjImag(0); 55 | CheckConjImag(1); 56 | CheckConjImag(-3.14); 57 | CheckConjImag(1.876e6); 58 | } 59 | 60 | TEST_CASE("Complex numbers can be compared") { 61 | Complex zero; 62 | Complex one{1.0}; 63 | Complex i{0, 1}; 64 | REQUIRE(zero.equals(zero)); 65 | REQUIRE(one.equals(one)); 66 | REQUIRE(i.equals(i)); 67 | REQUIRE(!zero.equals(one)); 68 | REQUIRE(!zero.equals(i)); 69 | REQUIRE(!one.equals(i)); 70 | } 71 | 72 | void CheckZplusZeq2Z(Complex z) { 73 | Complex expected = Complex{2*z.real(), 2*z.imag()}; 74 | Complex result = z.add(z); 75 | REQUIRE(result.equals(expected)); 76 | } 77 | void CheckZminusZeq0(Complex z) { 78 | Complex expected = Complex{}; 79 | Complex z_minus = Complex{-z.real(), -z.imag()}; 80 | Complex result = z.add(z_minus); 81 | REQUIRE(result.equals(expected)); 82 | } 83 | 84 | TEST_CASE("Complex number can be added") { 85 | CheckZplusZeq2Z(1); 86 | CheckZplusZeq2Z(0); 87 | CheckZplusZeq2Z(-1); 88 | CheckZplusZeq2Z(Complex{1, 2}); 89 | CheckZplusZeq2Z(Complex{-42, 1e-3}); 90 | 91 | CheckZminusZeq0(1); 92 | CheckZminusZeq0(0); 93 | CheckZminusZeq0(-1); 94 | CheckZminusZeq0(Complex{1, 2}); 95 | CheckZminusZeq0(Complex{-42, 1e-3}); 96 | } -------------------------------------------------------------------------------- /exercises/7-inheritance/part2/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++14 -I../include 2 | 3 | test : complex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | test.o : catch.hpp 7 | 8 | catch.hpp : 9 | wget https://github.com/catchorg/Catch2/releases/download/v2.13.6/catch.hpp 10 | 11 | run : test 12 | ./test 13 | 14 | clean : 15 | rm -rf *.o test 16 | -------------------------------------------------------------------------------- /exercises/7-inheritance/part2/complex.cpp: -------------------------------------------------------------------------------- 1 | #include "complex.hpp" 2 | #include 3 | 4 | Complex::Complex(double real) : re(real) { 5 | } 6 | 7 | Complex::Complex(double real, double imag) : re(real), im(imag) { 8 | } 9 | 10 | double Complex::real() { 11 | // Return real component 12 | } 13 | 14 | /* Add definition of a member function to access the imaginary component */ 15 | 16 | Complex Complex::conj() { 17 | // Return complex conjugate 18 | } 19 | 20 | /* Add definition of 'norm' member function. Hint: Look up std::sqrt from the 21 | cmath header to help calculate the magnitude of a complex number */ 22 | 23 | /* Add definition of 'add' member function */ 24 | 25 | bool Complex::equals(Complex other_complex) { 26 | // Return true if the real and imaginary parts of the complex numbers are 27 | // equal. False otherwise. 28 | } -------------------------------------------------------------------------------- /exercises/7-inheritance/part2/complex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_COMPLEX_COMPLEX_HPP 2 | #define CPPEX_COMPLEX_COMPLEX_HPP 3 | 4 | // Simple complex number class 5 | class Complex { 6 | public: 7 | /* Add declarations to create: 8 | - A default constructor 9 | - A constructor using just a real component 10 | - A constructor using real and imaginary components 11 | */ 12 | 13 | // Access components 14 | double real(); 15 | /* Add declaration to access the imaginary component */ 16 | 17 | // Compute the complex conjugate 18 | Complex conj(); 19 | 20 | /* Add declaration for member function 'norm' that takes no arguments and 21 | returns the magnitude of the complex number. 22 | */ 23 | 24 | /* Add declaration for an 'add' member function as so: z = i.add(j) 25 | I.e. For complex numbers i and j, z is the result of i + j. 26 | */ 27 | 28 | // Check if two complex numbers are equal 29 | bool equals(Complex other_complex); 30 | 31 | /* Add private member variables to store the real and imaginary components of 32 | the complex number. These should have type 'double' and a suitable default 33 | value. 34 | */ 35 | }; 36 | 37 | #endif -------------------------------------------------------------------------------- /exercises/7-inheritance/part2/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "complex.hpp" 7 | 8 | TEST_CASE("Complex numbers are constructed real/imag parts readable") { 9 | Complex zero; 10 | REQUIRE(zero.real() == 0.0); 11 | REQUIRE(zero.imag() == 0.0); 12 | 13 | Complex one{1.0}; 14 | REQUIRE(one.real() == 1.0); 15 | REQUIRE(one.imag() == 0.0); 16 | 17 | Complex i{0, 1}; 18 | REQUIRE(i.real() == 0.0); 19 | REQUIRE(i.imag() == 1.0); 20 | 21 | Complex z1{1, -83}; 22 | Complex z2 = z1; 23 | REQUIRE(z1.real() == z2.real()); 24 | REQUIRE(z1.imag() == z2.imag()); 25 | REQUIRE(z2.real() == 1.0); 26 | REQUIRE(z2.imag() == -83.0); 27 | } 28 | 29 | TEST_CASE("Complex numbers can have magnitude computed") { 30 | REQUIRE(Complex{}.norm() == 0.0); 31 | REQUIRE(Complex{3,4}.norm() == 5.0); 32 | } 33 | 34 | // Pure real => z == z* 35 | void CheckConjReal(double x) { 36 | Complex z{x}; 37 | Complex z_conj = z.conj(); 38 | REQUIRE(z_conj.equals(z)); 39 | } 40 | // Pure imaginary => z* == -z 41 | void CheckConjImag(double y) { 42 | Complex z{0.0, y}; 43 | Complex expected{0.0, -y}; 44 | Complex z_conj = z.conj(); 45 | REQUIRE(z_conj.equals(expected)); 46 | } 47 | 48 | TEST_CASE("Complex numbers be conjugated") { 49 | CheckConjReal(0); 50 | CheckConjReal(1); 51 | CheckConjReal(-3.14); 52 | CheckConjReal(1.876e6); 53 | 54 | CheckConjImag(0); 55 | CheckConjImag(1); 56 | CheckConjImag(-3.14); 57 | CheckConjImag(1.876e6); 58 | } 59 | 60 | TEST_CASE("Complex numbers can be compared") { 61 | Complex zero; 62 | Complex one{1.0}; 63 | Complex i{0, 1}; 64 | REQUIRE(zero.equals(zero)); 65 | REQUIRE(one.equals(one)); 66 | REQUIRE(i.equals(i)); 67 | REQUIRE(!zero.equals(one)); 68 | REQUIRE(!zero.equals(i)); 69 | REQUIRE(!one.equals(i)); 70 | } 71 | 72 | void CheckZplusZeq2Z(Complex z) { 73 | Complex expected = Complex{2*z.real(), 2*z.imag()}; 74 | Complex result = z.add(z); 75 | REQUIRE(result.equals(expected)); 76 | } 77 | void CheckZminusZeq0(Complex z) { 78 | Complex expected = Complex{}; 79 | Complex z_minus = Complex{-z.real(), -z.imag()}; 80 | Complex result = z.add(z_minus); 81 | REQUIRE(result.equals(expected)); 82 | } 83 | 84 | TEST_CASE("Complex number can be added") { 85 | CheckZplusZeq2Z(1); 86 | CheckZplusZeq2Z(0); 87 | CheckZplusZeq2Z(-1); 88 | CheckZplusZeq2Z(Complex{1, 2}); 89 | CheckZplusZeq2Z(Complex{-42, 1e-3}); 90 | 91 | CheckZminusZeq0(1); 92 | CheckZminusZeq0(0); 93 | CheckZminusZeq0(-1); 94 | CheckZminusZeq0(Complex{1, 2}); 95 | CheckZminusZeq0(Complex{-42, 1e-3}); 96 | } -------------------------------------------------------------------------------- /exercises/7-inheritance/part3/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++14 -I../include 2 | 3 | test : complex.o test.o 4 | $(CXX) $^ -o $@ 5 | 6 | test.o : catch.hpp 7 | 8 | catch.hpp : 9 | wget https://github.com/catchorg/Catch2/releases/download/v2.13.6/catch.hpp 10 | 11 | run : test 12 | ./test 13 | 14 | clean : 15 | rm -rf *.o test 16 | -------------------------------------------------------------------------------- /exercises/7-inheritance/part3/complex.cpp: -------------------------------------------------------------------------------- 1 | #include "complex.hpp" 2 | #include 3 | 4 | Complex::Complex(double real) : re(real) { 5 | } 6 | 7 | Complex::Complex(double real, double imag) : re(real), im(imag) { 8 | } 9 | 10 | double Complex::real() { 11 | // Return real component 12 | } 13 | 14 | /* Add definition of a member function to access the imaginary component */ 15 | 16 | Complex Complex::conj() { 17 | // Return complex conjugate 18 | } 19 | 20 | /* Add definition of 'norm' member function. Hint: Look up std::sqrt from the 21 | cmath header to help calculate the magnitude of a complex number */ 22 | 23 | /* Add definition of 'add' member function */ 24 | 25 | bool Complex::equals(Complex other_complex) { 26 | // Return true if the real and imaginary parts of the complex numbers are 27 | // equal. False otherwise. 28 | } -------------------------------------------------------------------------------- /exercises/7-inheritance/part3/complex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPEX_COMPLEX_COMPLEX_HPP 2 | #define CPPEX_COMPLEX_COMPLEX_HPP 3 | 4 | // Simple complex number class 5 | class Complex { 6 | public: 7 | /* Add declarations to create: 8 | - A default constructor 9 | - A constructor using just a real component 10 | - A constructor using real and imaginary components 11 | */ 12 | 13 | // Access components 14 | double real(); 15 | /* Add declaration to access the imaginary component */ 16 | 17 | // Compute the complex conjugate 18 | Complex conj(); 19 | 20 | /* Add declaration for member function 'norm' that takes no arguments and 21 | returns the magnitude of the complex number. 22 | */ 23 | 24 | /* Add declaration for an 'add' member function as so: z = i.add(j) 25 | I.e. For complex numbers i and j, z is the result of i + j. 26 | */ 27 | 28 | // Check if two complex numbers are equal 29 | bool equals(Complex other_complex); 30 | 31 | /* Add private member variables to store the real and imaginary components of 32 | the complex number. These should have type 'double' and a suitable default 33 | value. 34 | */ 35 | }; 36 | 37 | #endif -------------------------------------------------------------------------------- /exercises/7-inheritance/part3/test.cpp: -------------------------------------------------------------------------------- 1 | // Catch2 is a unit testing library 2 | // Here we let it create a main() function for us 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | 6 | #include "complex.hpp" 7 | 8 | TEST_CASE("Complex numbers are constructed real/imag parts readable") { 9 | Complex zero; 10 | REQUIRE(zero.real() == 0.0); 11 | REQUIRE(zero.imag() == 0.0); 12 | 13 | Complex one{1.0}; 14 | REQUIRE(one.real() == 1.0); 15 | REQUIRE(one.imag() == 0.0); 16 | 17 | Complex i{0, 1}; 18 | REQUIRE(i.real() == 0.0); 19 | REQUIRE(i.imag() == 1.0); 20 | 21 | Complex z1{1, -83}; 22 | Complex z2 = z1; 23 | REQUIRE(z1.real() == z2.real()); 24 | REQUIRE(z1.imag() == z2.imag()); 25 | REQUIRE(z2.real() == 1.0); 26 | REQUIRE(z2.imag() == -83.0); 27 | } 28 | 29 | TEST_CASE("Complex numbers can have magnitude computed") { 30 | REQUIRE(Complex{}.norm() == 0.0); 31 | REQUIRE(Complex{3,4}.norm() == 5.0); 32 | } 33 | 34 | // Pure real => z == z* 35 | void CheckConjReal(double x) { 36 | Complex z{x}; 37 | Complex z_conj = z.conj(); 38 | REQUIRE(z_conj.equals(z)); 39 | } 40 | // Pure imaginary => z* == -z 41 | void CheckConjImag(double y) { 42 | Complex z{0.0, y}; 43 | Complex expected{0.0, -y}; 44 | Complex z_conj = z.conj(); 45 | REQUIRE(z_conj.equals(expected)); 46 | } 47 | 48 | TEST_CASE("Complex numbers be conjugated") { 49 | CheckConjReal(0); 50 | CheckConjReal(1); 51 | CheckConjReal(-3.14); 52 | CheckConjReal(1.876e6); 53 | 54 | CheckConjImag(0); 55 | CheckConjImag(1); 56 | CheckConjImag(-3.14); 57 | CheckConjImag(1.876e6); 58 | } 59 | 60 | TEST_CASE("Complex numbers can be compared") { 61 | Complex zero; 62 | Complex one{1.0}; 63 | Complex i{0, 1}; 64 | REQUIRE(zero.equals(zero)); 65 | REQUIRE(one.equals(one)); 66 | REQUIRE(i.equals(i)); 67 | REQUIRE(!zero.equals(one)); 68 | REQUIRE(!zero.equals(i)); 69 | REQUIRE(!one.equals(i)); 70 | } 71 | 72 | void CheckZplusZeq2Z(Complex z) { 73 | Complex expected = Complex{2*z.real(), 2*z.imag()}; 74 | Complex result = z.add(z); 75 | REQUIRE(result.equals(expected)); 76 | } 77 | void CheckZminusZeq0(Complex z) { 78 | Complex expected = Complex{}; 79 | Complex z_minus = Complex{-z.real(), -z.imag()}; 80 | Complex result = z.add(z_minus); 81 | REQUIRE(result.equals(expected)); 82 | } 83 | 84 | TEST_CASE("Complex number can be added") { 85 | CheckZplusZeq2Z(1); 86 | CheckZplusZeq2Z(0); 87 | CheckZplusZeq2Z(-1); 88 | CheckZplusZeq2Z(Complex{1, 2}); 89 | CheckZplusZeq2Z(Complex{-42, 1e-3}); 90 | 91 | CheckZminusZeq0(1); 92 | CheckZminusZeq0(0); 93 | CheckZminusZeq0(-1); 94 | CheckZminusZeq0(Complex{1, 2}); 95 | CheckZminusZeq0(Complex{-42, 1e-3}); 96 | } -------------------------------------------------------------------------------- /exercises/7-inheritance/part4/main.cpp: -------------------------------------------------------------------------------- 1 | #include "poly.cpp" 2 | 3 | 4 | int main() 5 | { 6 | child x; 7 | 8 | x.predict(); 9 | } 10 | -------------------------------------------------------------------------------- /exercises/7-inheritance/part4/poly.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "poly.hpp" 3 | 4 | 5 | void base::forcast(){ 6 | std::cout << "The forcast is Rain" << std::endl; 7 | } 8 | 9 | void child::forcast(){ 10 | std::cout << "The forcast is Sunny" << std::endl; 11 | } 12 | 13 | void child::predict(){ 14 | base::forcast(); 15 | }; 16 | 17 | 18 | -------------------------------------------------------------------------------- /exercises/7-inheritance/part4/poly.hpp: -------------------------------------------------------------------------------- 1 | 2 | class base 3 | { 4 | 5 | public: 6 | 7 | void forcast(); 8 | 9 | }; 10 | 11 | 12 | class child : base 13 | { 14 | 15 | private: 16 | 17 | void forcast(); 18 | 19 | public: 20 | 21 | void predict(); 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /exercises/8-algorithm/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 2 | CC = $(CXX) 3 | 4 | ex : ex.o 5 | -------------------------------------------------------------------------------- /exercises/8-algorithm/README.md: -------------------------------------------------------------------------------- 1 | # Algorithm use exercise 2 | 3 | In the file `ex.cpp` there is an incomplete program which, by 4 | following the instructions in the comments, you can finish. 5 | 6 | You will likely want to refer to the documentation of the standard 7 | library algorithms, which, for reasons, are split across two headers: 8 | 9 | - 10 | 11 | - 12 | 13 | -------------------------------------------------------------------------------- /exercises/8-algorithm/ex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void print_vec(std::vector &vec) { 9 | for (auto &el : vec) { 10 | std::cout << el << " "; 11 | } 12 | std::cout << std::endl << std::endl; 13 | } 14 | 15 | int main(int argc, char* argv[]) { 16 | // First, a warmup of basic algorithm usage 17 | auto nums = std::vector(50); 18 | 19 | // let's initalize our vector with some random numbers 20 | for (int i = 0; i < 50; ++i) { 21 | nums[i] = std::rand() % 100; 22 | } 23 | 24 | // Bonus: can we do this using the algorithms library? Hint - std::generate 25 | // and use the following lambda 26 | auto gen = []() { return std::rand() % 100; }; 27 | 28 | // Your code here.... 29 | 30 | // Now, sort nums. 31 | 32 | // Your code here.... 33 | 34 | std::cout << "Sorted nums: "; 35 | print_vec(nums); 36 | // Reverse sort nums, using (a) sort on its own and (b) using sort and another 37 | // algorithm function 38 | 39 | // Your code here.... 40 | 41 | std::cout << "Reverse sorted nums (a): "; 42 | print_vec(nums); 43 | 44 | // Your code here.... 45 | 46 | std::cout << "Reverse sorted nums (b): "; 47 | print_vec(nums); 48 | 49 | // Now, lets look at a more involved example. We'll be working through Project 50 | // Euler No.2 (https://projecteuler.net/problem=2) "By considering the terms in 51 | // the Fibonacci sequence whose values do not exceed four million, find the sum 52 | // of the even-valued terms" 53 | 54 | // First lets get the fist 47 fibonacci numbers 55 | // BONUS: use std::transform 56 | 57 | auto fibs = std::vector(47); 58 | 59 | // Your code here.... 60 | 61 | 62 | print_vec(fibs); 63 | 64 | // Next, get all that are less than or equal to 4 million, and store them in 65 | // fibs_less HINT: use std::copy_if and std::back_inserter 66 | 67 | auto fibs_less = std::vector(); 68 | 69 | // Your code here.... 70 | 71 | std::cout << "fibs <= 4000000: "; 72 | print_vec(fibs_less); 73 | 74 | // Now, get the evens. Use the same approach as above 75 | auto evens = std::vector(); 76 | 77 | // Your code here.... 78 | 79 | 80 | std::cout << "Evens: "; 81 | print_vec(evens); 82 | 83 | // Finally, let's sum them (hint: std::accumulate) 84 | 85 | int sum = 0; 86 | 87 | std::cout << "Sum of even fibonacci numbers not greater than 4 million: " 88 | << sum << std::endl; 89 | 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /exercises/9-eigen/Makefile: -------------------------------------------------------------------------------- 1 | CXX=clang++ 2 | CXX=icpc 3 | CXX=g++ 4 | CPPFLAGS=-O2 -std=c++11 5 | 6 | all: explicit implicit sparse 7 | 8 | explicit: explicit.o 9 | $(CXX) $(CPPFLAGS) -o $@ $< 10 | 11 | implicit: implicit.o 12 | $(CXX) $(CPPFLAGS) -o $@ $< 13 | 14 | sparse: sparse.o 15 | $(CXX) $(CPPFLAGS) -o $@ $< 16 | -------------------------------------------------------------------------------- /exercises/9-eigen/README.md: -------------------------------------------------------------------------------- 1 | # Eigen Demos 2 | 3 | Some simple demos using the Eigen C++ library to solve 1D diffusion 4 | problems. 5 | 6 | See Eigen lecture for details 7 | -------------------------------------------------------------------------------- /exercises/9-eigen/explicit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main() 8 | { 9 | int n = 20; 10 | int steps = 200; 11 | std::vector Avec(n * n); 12 | 13 | 14 | // Set up matrix A 15 | Eigen::Map A(Avec.data(), n, n); 16 | A = Eigen::MatrixXd::Identity(n, n); 17 | double delta = 0.4; 18 | for (int i = 0; i < n - 1; ++i) 19 | { 20 | A(i + 1, i) += delta; 21 | A(i + 1, i + 1) += -delta; 22 | 23 | A(i, i) += -delta; 24 | A(i, i + 1) += +delta; 25 | } 26 | 27 | std::cout << "A = \n" << A << std::endl 28 | << std::endl; 29 | 30 | 31 | // T_n 32 | Eigen::VectorXd b(n); 33 | b.setZero(); 34 | b.head(n / 2).array() = 1.0; 35 | 36 | std::ofstream f; 37 | f.open("explicit_sim.txt"); 38 | for (int i = 0; i < steps; ++i) 39 | { 40 | f << b.transpose() << std::endl; 41 | // update time-step T_{n+1} = A.T_{n} 42 | b = A * b; 43 | } 44 | 45 | std::cout << b.transpose() << std::endl; 46 | 47 | return 0; 48 | } -------------------------------------------------------------------------------- /exercises/9-eigen/implicit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | int n = 20; 9 | int steps = 100; 10 | 11 | // Set up matrix A 12 | Eigen::MatrixXd A(n, n); 13 | A = Eigen::MatrixXd::Identity(n, n); 14 | 15 | double delta = -0.4; 16 | 17 | for (int i = 0; i < n - 1; ++i) 18 | { 19 | A(i + 1, i) += delta; 20 | A(i + 1, i + 1) += -delta; 21 | 22 | A(i, i) += -delta; 23 | A(i, i + 1) += +delta; 24 | } 25 | 26 | std::cout << "A = \n" 27 | << A << std::endl 28 | << std::endl; 29 | 30 | Eigen::VectorXd b(n); 31 | b.setZero(); 32 | 33 | b.head(n / 2).array() = 1.0; 34 | 35 | std::ofstream f; 36 | f.open("implicit_sim.txt"); 37 | for (int i = 0; i < 200; ++i) 38 | { 39 | // Solve A.T_{n+1} = T_{n} for T_{n+1} 40 | f << b.transpose() << std::endl; 41 | b = A.colPivHouseholderQr().solve(b); 42 | } 43 | 44 | std::cout << b.transpose() << std::endl; 45 | return 0; 46 | } -------------------------------------------------------------------------------- /exercises/9-eigen/modules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | module load PrgEnv-gnu 3 | module load cray-python 4 | module load matplotlib 5 | module load eigen/3.4.0 6 | -------------------------------------------------------------------------------- /exercises/9-eigen/movie.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from matplotlib.animation import FuncAnimation 5 | 6 | def plot(simulation): 7 | n = 20 8 | steps = 200 9 | 10 | fig, ax = plt.subplots() 11 | xdata = np.arange(0, n) 12 | ydata = [] 13 | ln, = ax.plot([], [], 'ro') 14 | 15 | data = np.loadtxt(f"{simulation}_sim.txt") 16 | 17 | def init(): 18 | ax.set_xlim(0,n) 19 | ax.set_ylim(0,1.1) 20 | return ln, 21 | 22 | def update(frame): 23 | ydata = data[frame] 24 | ln.set_data(xdata, ydata) 25 | return ln, 26 | 27 | ani = FuncAnimation(fig, update, frames=np.arange(0, 200), 28 | init_func=init, blit=True) 29 | 30 | ani.save(f"{simulation}.gif", writer="pillow") 31 | plt.show() 32 | 33 | 34 | if __name__ == "__main__": 35 | parser = argparse.ArgumentParser(prog='movie.py', description='Create animation from simulation output.') 36 | parser.add_argument('simulation', default='implicit', const='implicit', nargs='?', choices=['implicit', 'explicit', 'sparse']) 37 | 38 | args = parser.parse_args() 39 | plot(args.simulation) 40 | -------------------------------------------------------------------------------- /exercises/9-eigen/sparse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | int main() 9 | { 10 | int n = 20; 11 | int steps = 200; 12 | 13 | Eigen::SparseMatrix A; 14 | A.resize(n, n); 15 | 16 | double delta = 0.4; 17 | 18 | std::vector> fill; 19 | fill.reserve(n * 4); 20 | 21 | for (int i = 0; i < n - 1; ++i) 22 | { 23 | fill.push_back(Eigen::Triplet(i + 1, i, delta)); 24 | fill.push_back(Eigen::Triplet(i + 1, i + 1, -delta)); 25 | fill.push_back(Eigen::Triplet(i, i, 1.0 - delta)); 26 | fill.push_back(Eigen::Triplet(i, i + 1, delta)); 27 | } 28 | fill.push_back(Eigen::Triplet(n - 1, n - 1, 1.0)); 29 | A.setFromTriplets(fill.begin(), fill.end()); 30 | 31 | std::cout << A << std::endl; 32 | 33 | Eigen::VectorXd b(n); 34 | b.head(n / 2).array() = 1.0; 35 | b.tail(n / 2).array() = 0.0; 36 | 37 | std::cout << b.transpose() << std::endl; 38 | 39 | std::ofstream f; 40 | f.open("sparse_sim.txt"); 41 | for (int i = 0; i < steps; ++i) 42 | { 43 | f << b.transpose() << std::endl; 44 | b = A * b; 45 | } 46 | 47 | std::cout << b.transpose() << std::endl; 48 | 49 | return 0; 50 | } -------------------------------------------------------------------------------- /exercises/README.md: -------------------------------------------------------------------------------- 1 | # C++ exercise 2 | 3 | Practical exercises for C++ course 4 | 5 | See each subdirectory for further instructions 6 | 7 | * [2.1 Class types](2.1-class-types/) 8 | * [2.2 Complex numbers](2.2-complex/) 9 | * [3 Containers](3-containers/) 10 | * [4 My array](4-my-array/) 11 | * [5 Templates](5-templates/) 12 | * [6.1 Pointers](6.1-pointers/) 13 | * [6.2 Special pointers](6.2-special-pointers/) 14 | * [6.3 Morton-order matrix class template](6.3-morton-order/) 15 | * [7 Inheritance](7-inheritance/) 16 | * [8 Using algorithms](8-algorithm/) 17 | * [9 Eigen](9-eigen/) 18 | * [10 Simple use of threads](10-threads/) 19 | -------------------------------------------------------------------------------- /lectures/0-course-intro-2-days/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | 3 | # Course Introduction 4 | ## Nathan Mannall, EPCC 5 | ## n.mannall@epcc.ed.ac.uk 6 | 7 | --- 8 | template: titleslide 9 | # Timetable 10 | 11 | --- 12 | # Day 1 13 | 14 | - 9:30 - 9:45 : Introduction to this course 15 | 16 | - 9:45 – 10:45 : Introduction to C++ 17 | 18 | - 10:45 – 11:00 : Coffee break 19 | 20 | - 11:00 – 12:30 : Class types 21 | 22 | - 12:30 – 1:30 : Lunch 23 | 24 | - 1:30 – 2:30 : loops, containers, and iterators 25 | 26 | - 2:30 - 3:00 : Managing resources 27 | 28 | - 3:00 – 3:15 : Coffee 29 | 30 | - 3:15 - 4:00 : RAII 31 | 32 | --- 33 | # Day 2 34 | 35 | - 9:30 - 9:45 : Recap previous day 36 | 37 | - 9:45 - 10:45 : Templates & traits for generic programming 38 | 39 | - 10:45 – 11:00 : Coffee 40 | 41 | - 11:00 – 12:30 : RAII continued 42 | 43 | - 12:30 - 1:30 : Lunch 44 | 45 | - 1:30 – 2:30 : Algorithms and lambdas 46 | 47 | - 2:30 - 3:00 : Linear algebra with Eigen 48 | 49 | - 3:00 - 3:15 : Coffee 50 | 51 | - 3:15 - 3:45 : Time to work on exercises from the course & ask questions. 52 | 53 | - 3:45 - 4:00 : Wrap-up 54 | 55 | --- 56 | # Access to ARCHER2 57 | 58 | - SAFE: https://safe.epcc.ed.ac.uk/ 59 | 60 | - SAFE Docs: https://epcced.github.io/safe-docs/ 61 | 62 | - ARCHER2 Docs: https://docs.archer2.ac.uk/user-guide/connecting/ 63 | 64 | - Project: ta199 65 | 66 | ---- 67 | 68 | Complete setup of machine account: 69 | - Login to SAFE 70 | - Set multi-factor token: 71 | - `Login accounts -> user@archer2 -> Set MFA-Token` 72 | - Connect via terminal: 73 | - `ssh user@login.archer2.ac.uk` 74 | - `ssh -i [path-to-ssh-key] user@login.archer2.ac.uk` 75 | - Get LDAP password from SAFE (first login only): 76 | - `Login accounts -> user@archer2 -> View Login Account Password` 77 | - Set password (used for recovery only) 78 | 79 | -------------------------------------------------------------------------------- /lectures/0-course-intro-2-days/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Course Introduction 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/0-course-intro-3-days/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | 3 | # Course Introduction 4 | ## James Richings, EPCC 5 | ## j.richings@epcc.ed.ac.uk 6 | 7 | --- 8 | template: titleslide 9 | # Timetable 10 | 11 | --- 12 | # Day 1 13 | 14 | - 9:30 - 9:45 : Introduction to this course 15 | 16 | - 9:45 – 10:45 : Introduction to C++ 17 | 18 | - 10:45 – 11:00 : Coffee break 19 | 20 | - 11:00 – 12:30 : Class types 21 | 22 | - 12:30 – 1:30 : Lunch 23 | 24 | - 1:30 – 2:30 : loops, containers, and iterators 25 | 26 | - 2:30 - 3:00 : Managing resources 27 | 28 | - 3:00 – 3:15 : Coffee 29 | 30 | - 3:15 - 4:00 : Time to work on exercises from the day & ask questions 31 | 32 | --- 33 | # Day 2 34 | 35 | - 9:30 - 9:45 : Recap previous day 36 | 37 | - 9:45 – 10:45 : Managing resources & RAII 38 | 39 | - 10:45 – 11:00 : Coffee 40 | 41 | - 11:00 – 12:30 : Templates for generic programming 42 | 43 | - 12:30 - 1:30 : Lunch 44 | 45 | - 1:30 - 3:00 : RAII continued 46 | 47 | - 3:00 - 3:15 : Coffee 48 | 49 | - 3:15 - 4:00 : Time to work on exercises from the day & ask questions 50 | 51 | --- 52 | # Day 3 53 | 54 | - 9:30-9:45: Recap the previous day 55 | 56 | - 9:45 - 11:00 : Combining classes 57 | 58 | - 11:00 - 11:15 : coffee 59 | 60 | - 11:15 – 12:30 : Algorithms, lambdas, and traits 61 | 62 | - 12:30 - 1:30 : Lunch 63 | 64 | - 1:30 – 2:15 : Linear algebra with Eigen 65 | 66 | - 2:15 - 2:30 : Coffee 67 | 68 | - 2:30 – 3:00 : Threads with C++ 69 | 70 | - 3:05 - 4:00 : Time to work on exercises from the day & ask questions 71 | 72 | 73 | --- 74 | # Access to ARCHER2 75 | 76 | - SAFE: https://safe.epcc.ed.ac.uk/ 77 | 78 | - SAFE Docs: https://epcced.github.io/safe-docs/ 79 | 80 | - ARCHER2 Docs: https://docs.archer2.ac.uk/user-guide/connecting/ 81 | 82 | - Project: ta198 83 | 84 | ---- 85 | 86 | Complete setup of machine account: 87 | - Login to SAFE 88 | - Set multi-factor token: 89 | - `Login accounts -> user@archer2 -> Set MFA-Token` 90 | - Connect via terminal: 91 | - `ssh user@login.archer2.ac.uk` 92 | - `ssh -i [path-to-ssh-key] user@login.archer2.ac.uk` 93 | - Get LDAP password from SAFE (first login only): 94 | - `Login accounts -> user@archer2 -> View Login Account Password` 95 | - Set password (used for recovery only) 96 | 97 | -------------------------------------------------------------------------------- /lectures/0-course-intro-3-days/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Course Introduction 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/1-cpp-intro/auto/auto.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Needs C++20 4 | auto add(auto x, auto y) { 5 | return x+y; 6 | } 7 | 8 | int main(void) { 9 | auto num = 42; 10 | auto real = 42.0; 11 | 12 | std::cout << add(num,10) << std::endl; 13 | std::cout << std::fixed << add(real,10.0) << std::endl; 14 | 15 | return 0; 16 | } -------------------------------------------------------------------------------- /lectures/1-cpp-intro/frank_mon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/1-cpp-intro/frank_mon.jpg -------------------------------------------------------------------------------- /lectures/1-cpp-intro/hello/hello.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char* argv[]) { 4 | std::cout << "Hello, world!" << std::endl; 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /lectures/1-cpp-intro/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A brief introduction to C++ 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/1-cpp-intro/octodog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/1-cpp-intro/octodog.jpg -------------------------------------------------------------------------------- /lectures/1-cpp-intro/sak.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/1-cpp-intro/sak.jpg -------------------------------------------------------------------------------- /lectures/1-cpp-intro/sum/sum.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int sum(int a, int b) { 4 | return a + b; 5 | } 6 | 7 | double sum(double a, double b) { 8 | return a + b; 9 | } 10 | 11 | int i1 = 1; 12 | int i2 = 2; 13 | double d1 = 1.0; 14 | double d2 = 2.0; 15 | unsigned u42 = 42; 16 | std::string name = "Alice"; 17 | std::string file = "data.csv"; 18 | 19 | 20 | int main(void) { 21 | std::cout << sum(i1, i2) << std::endl; 22 | std::cout << sum(3, 72) << std::endl; 23 | std::cout << sum(i1, u42) << std::endl; 24 | std::cout << sum(d2, d1) << std::endl; 25 | std::cout << sum(d2, 1e6) << std::endl; 26 | std::cout << sum(d2, i1) << std::endl; 27 | std::cout << sum(name, file) << std::endl; 28 | 29 | return 0; 30 | } -------------------------------------------------------------------------------- /lectures/10.1-threads/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | 3 | # C++ Threads - Basics 4 | ## James Richings, EPCC 5 | ## j.richings@epcc.ed.ac.uk 6 | 7 | --- 8 | 9 | # Overview 10 | 11 | - Introduction 12 | 13 | - Creating and joining threads 14 | 15 | - Passing arguments to threads 16 | 17 | - Synchronisation 18 | 19 | --- 20 | # C++11 threads 21 | 22 | - API for multithreaded programming built in to C++11 standard 23 | - Similar functionality to POSIX threads 24 | - but with a proper OO interface 25 | - based quite heavily on Boost threads library 26 | - Portable 27 | - depends on C++11 support, available in most compilers today 28 | - Threads are C++ objects 29 | - call a constructor to create a thread 30 | - Synchronisation 31 | - mutex locks 32 | - condition variables 33 | - C++11 atomics 34 | - Tasks 35 | - via async/futures/promises 36 | 37 | --- 38 | # Creating threads 39 | 40 | - Threads are objects of the `std::thread` class 41 | - Threads are created by calling the constructor for this class 42 | - Pass as an argument what we want the thread to execute. This can be: 43 | - A function pointer 44 | - A function object / functor 45 | - A lambda expression 46 | 47 | - Note: you cannot copy a thread 48 | 49 | --- 50 | # Joining threads 51 | 52 | The `join()` member function on a `std::thread` object causes the calling thread to wait for the thread object to finish executing its function/functor/lambda. 53 | 54 | --- 55 | # Hello world – function pointer 56 | 57 | ```C++ 58 | #include 59 | #include 60 | #include 61 | 62 | void hello() { 63 | std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; 64 | } 65 | 66 | int main() { 67 | std::vector threads; 68 | for (int i = 0; i < 5; ++i) { 69 | threads.push_back(std::thread(hello)); 70 | } 71 | 72 | for (auto& thread: threads) { 73 | thread.join(); 74 | } 75 | } 76 | ``` 77 | 78 | --- 79 | # Hello world – lambda function 80 | 81 | ```C++ 82 | #include 83 | #include 84 | #include 85 | 86 | int main() { 87 | std::vector threads; 88 | 89 | for(int i = 0; i < 5; ++i){ 90 | threads.push_back(std::thread([] { 91 | std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; 92 | })); 93 | } 94 | 95 | for(auto& thread : threads){ 96 | thread.join(); 97 | } 98 | } 99 | ``` 100 | 101 | --- 102 | # Thread IDs 103 | 104 | - Can call `get_id()`on a thread 105 | - Use `std::this_thread::get_id()` to call it on the executing thread 106 | - Returns an arbitrary identifier 107 | - Not much use! 108 | - If we want sequentially numbered threads, need to pass the number as an argument to the thread constructor. 109 | 110 | --- 111 | # Passing arguments to threads 112 | 113 | Arguments to the thread function are moved or copied by value 114 | 115 | Passing simple arguments to threads is straightforward: 116 | ```C++ 117 | void hello(int x, int y) { 118 | std::cout << "Hello " << x << " " << y << std::endl; 119 | } 120 | 121 | int main() { 122 | int a = 1; 123 | int b = 27; 124 | std::thread mythread(hello, a, b); 125 | mythread.join(); 126 | } 127 | ``` 128 | 129 | --- 130 | # Passing references to threads 131 | 132 | Need to use a reference wrapper to avoid the argument to the thread constructor making a copy 133 | ```C++ 134 | void hello(int& x) { 135 | x++; 136 | } 137 | 138 | int main() { 139 | int x = 9; 140 | std::thread mythread(hello, std::ref(x)); 141 | mythread.join(); 142 | std::cout << "x = " << x << std::endl; // x is 10 here 143 | } 144 | ``` 145 | 146 | --- 147 | # Synchronisation 148 | 149 | ```C++ 150 | class Wallet 151 | { 152 | int mMoney = 0; 153 | public: 154 | Wallet() {} 155 | 156 | void addMoney(int money) { 157 | mMoney += money; 158 | } 159 | }; 160 | ``` 161 | 162 | If two threads call `addMoney()` on the same `Wallet` object, then we have a race condition. 163 | 164 | --- 165 | # Mutex locks 166 | 167 | - Can use a mutex lock to protect updates to shared variables 168 | - natural to declare a mutex inside the object whose data needs protecting 169 | 170 | ```C++ 171 | #include 172 | class Wallet 173 | { 174 | int mMoney = 0; 175 | std::mutex mutex; 176 | public: 177 | Wallet() {} 178 | 179 | void addMoney(int money) { 180 | mutex.lock(); 181 | mMoney += money; 182 | mutex.unlock(); 183 | } 184 | }; 185 | ``` 186 | 187 | --- 188 | # Guard objects 189 | 190 | - Need to make sure a mutex is always unlocked 191 | - Can be tricky in cases with complex control flow, or with exception handling. 192 | - The `std::lock_guard` class implements the RAII (resource allocation is initialization) pattern for mutexes 193 | - Its constructor takes as an argument a mutex, which it then locks 194 | - Its destructor unlocks the mutex 195 | 196 | --- 197 | # Guard objects 198 | 199 | ```C++ 200 | #include 201 | 202 | class Wallet 203 | { 204 | int mMoney = 0; 205 | std::mutex mutex; 206 | public: 207 | Wallet() {} 208 | 209 | void addMoney(int money) { 210 | std::lock_guard lockGuard(mutex); 211 | mMoney += money; 212 | } // mutex unlocked when lockGuard goes out of scope 213 | }; 214 | ``` 215 | 216 | --- 217 | # Atomics 218 | 219 | - C++ provides an atomic template class `std::atomic` 220 | 221 | - Efficient, lock-free operations supported for specialization to basic integer, boolean and character types 222 | 223 | - Floating point support in C++20 standard only 224 | 225 | --- 226 | # Atomics 227 | 228 | ```C++ 229 | #include 230 | 231 | class Wallet 232 | { 233 | std::atomic mMoney = 0; 234 | public: 235 | Wallet() {} 236 | void addMoney(int money) { 237 | mMoney += money; //atomic increment 238 | } 239 | }; 240 | ``` 241 | 242 | --- 243 | # Thread safe class design 244 | 245 | - Possible to add a mutex data member to the class and make every member function that accesses any mutable state acquire and release the mutex (use a `lock_guard`) 246 | 247 | - Good design, in the sense that multithreaded code can use the class without worrying about the synchronisation 248 | 249 | - Can result in unacceptable overheads – lots of lock/unlocks, and synchronization when it’s not needed. 250 | 251 | - Need to think carefully about use cases in a given application. 252 | 253 | --- 254 | # Reusing this material 255 | 256 | .center[![CC-BY-NC-SA](https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc-sa.eu.png)] 257 | 258 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 259 | 260 | https://creativecommons.org/licenses/by-nc-sa/4.0/ 261 | 262 | .smaller[ 263 | This means you are free to copy and redistribute the material and adapt and build on the material under the following terms: You must give appropriate credit, provide a link to the license and indicate if changes were made. If you adapt or build on the material you must distribute your work under the same license as the original. 264 | 265 | Acknowledge EPCC as follows: “© EPCC, The University of Edinburgh, www.epcc.ed.ac.uk” 266 | 267 | Note that this presentation may contain images owned by others. Please seek their permission before reusing these images. 268 | ] 269 | -------------------------------------------------------------------------------- /lectures/10.1-threads/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | C++ Threads 1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lectures/10.2-threads-cont/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | # C++ Threads – Further topics 3 | 4 | --- 5 | # Overview 6 | 7 | - `std::async` and futures 8 | - Parallel STL 9 | - C++ threads or OpenMP? 10 | 11 | --- 12 | # async and futures 13 | 14 | - With `std::thread` there is no direct way to get a return value from the function/lambda that a thread executes 15 | 16 | - Could alter function to use an output reference argument, but this is problematic 17 | 18 | - Can get round this with `std::async` and futures 19 | 20 | - Also allows exception handling to work properly if a thread throws an exception 21 | 22 | - async returns a future object, and calling the `get()` function on the future blocks until the value is available 23 | 24 | --- 25 | # async and future 26 | 27 | ```C++ 28 | int add(int x, int y) { 29 | return x+y; 30 | } 31 | 32 | int main() { 33 | int a = 1; 34 | int b = 27; 35 | std::future fut = std::async(add, a, b); 36 | cout << "sum = " << fut.get() << std::endl; 37 | } 38 | ``` 39 | 40 | --- 41 | # async and future 42 | 43 | ```C++ 44 | int add(int x, int y) { 45 | return x+y; 46 | } 47 | 48 | int main() { 49 | int a = 1; 50 | int b = 27; 51 | auto fut = std::async(add, a, b); 52 | cout << "sum = " << fut.get() << std::endl; 53 | } 54 | ``` 55 | 56 | --- 57 | # Problems! 58 | 59 | - By default, the runtime can decide to not create a new thread to run 60 | the callable, and simply execute it when `fut.get()` is called. 61 | 62 | - Can cause deadlock if you are not careful! 63 | 64 | - Can force asynchronous execution via an optional policy argument: 65 | ```C++ 66 | auto fut = std::async(std::launch::async, add, a, b); 67 | ``` 68 | 69 | - Implementations can choose to use a thread pool to execute asyncs, 70 | but most don’t can end up with way too many threads 71 | 72 | --- 73 | # Parallel STL 74 | 75 | - Version of the STL that incorporates parallel versions of many STL algorithms 76 | 77 | - Part of C++17 standard, so not yet available in most standard 78 | library implementations, but coming soon. 79 | 80 | - Most STL algorithms take a addition execution policy argument: 81 | * Sequential 82 | * Parallel 83 | * Parallel and vectorized 84 | * (vectorized but not parallel added in C++20) 85 | 86 | --- 87 | # Example 88 | 89 | ```C++ 90 | std::vector v = genLargeVector(); 91 | 92 | // standard sequential sort 93 | std::sort(v.begin(), v.end()); 94 | 95 | // explicitly sequential 96 | sort std::sort(std::execution::seq, v.begin(), v.end()); 97 | 98 | // permitting parallel execution 99 | std::sort(std::execution::par, v.begin(), v.end()); 100 | 101 | // permitting vectorization as well 102 | std::sort(std::execution::par_unseq, v.begin(), v.end()); 103 | ``` 104 | 105 | --- 106 | # Easy to use, but... 107 | 108 | - For algorithms that take a function object argument (e.g. 109 | `for_each`), that operates on each element it is up to the 110 | programmer to ensure that parallel/vectorized execution of this is 111 | correct. 112 | 113 | - Interface gives almost no control to the programmer 114 | * how many threads to use? 115 | * which threads handle which elements? 116 | * whether to use a static of dynamic assignment of work to threads? 117 | 118 | - Might be difficult to have any control over data affinity/locality 119 | 120 | - Implementations can support additional execution policies 121 | 122 | - Scope for adding more to the standard in the future 123 | 124 | --- 125 | # C++ threads or OpenMP? 126 | 127 | - Since OpenMP supports C++ as a base language, what are the pros and cons of OpenMP vs C++ threads? 128 | 129 | - Adding OpenMP support for new C++ features takes time (several years 130 | in practice) 131 | 132 | - OpenMP 4.5 only supports C++98 133 | 134 | - OpenMP 5.0 supports most of C++11/14 135 | 136 | * some exceptions – including C++ threads and atomics 137 | * implementations will take some time to catch up with this 138 | 139 | - If you want to use the latest and greatest features in C++, then 140 | OpenMP might not work. 141 | 142 | --- 143 | # C++ threads or OpenMP? 144 | 145 | OpenMP has a lot of useful features that are not available in the C++ standard: 146 | 147 | - Thread barriers 148 | * will likely be added in C++20 149 | 150 | - Thread pools and tasks 151 | * may be added in C++23 (depends on "executors") 152 | 153 | - Parallel for loops 154 | * some functionality with `std::for_each` in Parallel STL 155 | 156 | - SIMD directives 157 | * probably will work OK with C++ threads 158 | 159 | --- 160 | # But I can build my own! 161 | 162 | - C++ threads has all the required building blocks to allow you to 163 | implement many of these features for yourself 164 | 165 | * or borrow someone else’s implementation 166 | 167 | - Can make use of C++ functionality to make them look syntactically neat 168 | 169 | - May not be correct 170 | * low level threaded programming is hard! 171 | 172 | - May not be well documented 173 | * unusual to find documentation that is as good as language standards 174 | 175 | - Unlikely to be both portable and efficient 176 | maximum efficiency typically requires architecture-specific coding 177 | 178 | --- 179 | # What about GPUs? 180 | 181 | - OpenMP has support for offloading code to GPUs 182 | 183 | * still a bit immature, not many good implementations yet 184 | 185 | - C++ may support this in the future sometime?? 186 | 187 | - There are a number of C++ frameworks for doing this 188 | 189 | * Kokkos 190 | * Raja 191 | * SYCL 192 | 193 | - Various degrees of support and robustness 194 | 195 | --- 196 | # More differences 197 | 198 | - OpenMP has some restrictions which annoy C++ programmers 199 | 200 | * e.g. can’t use data members of objects in private or reduction clauses 201 | 202 | - Support for reductions is in both, but handled differently 203 | 204 | * OpenMP allows you to define your own reduction operators 205 | * C++ has `std::reduce` in the Parallel STL 206 | 207 | - OpenMP does not allow arbitrary forking/joining of threads 208 | * You can argue whether this is a good thing or not in an HPC context! 209 | 210 | - C++ has no standard way of binding threads to sockets/cores 211 | * need to call OS-specific functions 212 | 213 | --- 214 | # C++ threads or OpenMP? 215 | .center[ 216 | ![:scale_img 80%](omp-v-thread.png) 217 | ] 218 | 219 | --- 220 | # Reusing this material 221 | 222 | .center[![CC-BY-NC-SA](https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc-sa.eu.png)] 223 | 224 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 225 | 226 | https://creativecommons.org/licenses/by-nc-sa/4.0/ 227 | 228 | .smaller[ 229 | This means you are free to copy and redistribute the material and adapt and build on the material under the following terms: You must give appropriate credit, provide a link to the license and indicate if changes were made. If you adapt or build on the material you must distribute your work under the same license as the original. 230 | 231 | Acknowledge EPCC as follows: “© EPCC, The University of Edinburgh, www.epcc.ed.ac.uk” 232 | 233 | Note that this presentation may contain images owned by others. Please seek their permission before reusing these images. 234 | ] 235 | -------------------------------------------------------------------------------- /lectures/10.2-threads-cont/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | C++ Threads 2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lectures/10.2-threads-cont/omp-v-thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/10.2-threads-cont/omp-v-thread.png -------------------------------------------------------------------------------- /lectures/2-classes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Classes in C++ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lectures/3-loops-containers/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | # Containers, loops, and iterators 3 | ## Nathan Mannall 4 | ## n.mannall@epcc.ed.ac.uk 5 | 6 | ??? 7 | 8 | We've pretty much only talked about working on single things at once 9 | 10 | Simulation/analysis usually require us to deal with many things: many 11 | data points, many atoms in your MD simulation, many images to 12 | recognise objects in etc. 13 | 14 | --- 15 | # Containers 16 | 17 | So we have some intuition about what this means 18 | 19 | In C++ a container: 20 | 21 | - Holds objects of a uniform type 22 | 23 | - Owns the objects that it contains 24 | 25 | - Provides access to its elements 26 | 27 | The means of access and the performance of these operations depends on 28 | the container. 29 | 30 | --- 31 | # Standard library containers 32 | 33 | The standard library has 13 container template classes, but we'll only touch on a few. 34 | 35 | - `vector` - a dynamically sized contiguous array 36 | 37 | - `array` - a statically sized contiguous array 38 | 39 | - `list`/`forward_list` - a doubly/singly linked list 40 | 41 | - (`unordered_`)`set` / (`unordered_`)`map` 42 | 43 | --- 44 | # vector 45 | 46 | The size of the vector is determined at run time (and hence memory is 47 | allocated then) 48 | 49 | The elements are *contiguous in memory*, so it is fast to jump to any 50 | element by index and to iterate though them. 51 | 52 | ```C++ 53 | #include 54 | #include 55 | 56 | void ShowData() { 57 | std::vector data = GetData(); 58 | for (int x: data) { 59 | std::cout << x << "\n"; 60 | } 61 | } 62 | ``` 63 | 64 | ![:thumb](Use by default - data locality often wins over algorithmic complexity) 65 | 66 | ??? 67 | 68 | Poorly named, but we're stuck: `std::vector` is not a vector in either 69 | the 3D spatial sense nor the vector space sense... 70 | 71 | We put the type of the elements inside the angle brackets 72 | 73 | Here we show a for loop that will iterate through every element of the vector 74 | 75 | Why does contiguous memory => fast? Because main memory is slow, chips 76 | have caches which bring lines of memory into small high speed bits of 77 | memory on chip. They also notice when you are running through a chunk 78 | of memory and pre-fetch (or the compiler does this) 79 | 80 | --- 81 | # vector 82 | 83 | Supports: 84 | 85 | - copy and move (if element type is `noexcept` moveable) 86 | 87 | - random element access by index 88 | 89 | - resize (and pre-reserving memory) 90 | 91 | - element insertion 92 | 93 | - element deletion 94 | 95 | Note that it *owns* its elements: when it reaches the end of its 96 | lifetime, contained elements will also be destroyed. 97 | 98 | Be aware that resizing operations may force reallocation and copying! 99 | 100 | --- 101 | # vector building 102 | 103 | ```C++ 104 | std::vector first_n_primes(unsigned n) { 105 | std::vector ans; 106 | 107 | unsigned maybe_prime = 2; 108 | 109 | while (ans.size() < n) { 110 | if (is_prime(maybe_prime)) { 111 | ans.push_back(maybe_prime); 112 | } 113 | maybe_prime += 1; 114 | } 115 | return ans; 116 | } 117 | ``` 118 | ??? 119 | Default constructor creates an empty vector 120 | 121 | `push_back` adds a new value to the end of the vector 122 | 123 | This might require a re-allocation of memory and copying the contents - 124 | 125 | Discuss capacity. 126 | 127 | --- 128 | # array 129 | 130 | - Contiguous in memory but the size is fixed at compile time. 131 | 132 | - Almost like a vector, but you can't change the size. 133 | 134 | - Only difference is construction - list the values inside the braces 135 | 136 | ```C++ 137 | #include 138 | using GridPoint = std::array; 139 | 140 | auto p1 = GridPoint{1,2,3}; 141 | std::cout << p1.size() << std::endl; 142 | // Prints 3 143 | ``` 144 | 145 | --- 146 | # list (and forward_list) 147 | 148 | - Almost always implemented as a doubly (singly) linked list. 149 | 150 | - Elements are allocated one by one on the heap at run time. 151 | 152 | - Traversal requires following pointers from one end 153 | 154 | - Fast element insertion and deletion (**if** you don't have to look for 155 | the element!) 156 | 157 | ![:thumb](Use when you will be adding and removing from the ends a lot 158 | and the contained objects are expensive to copy/move. 159 | 160 | Consider converting to `vector` if you have a build/access pattern.) 161 | 162 | --- 163 | # set and map 164 | 165 | - These are associative containers implemented as sorted data 166 | structures for rapid search (typically red-black trees). 167 | 168 | - `set` is just a set of keys, `map` is a set of key/value pairs 169 | (types can differ). 170 | 171 | - You must have defined a comparison function for the key type. 172 | 173 | - You may want to use the `unordered` versions which use a hash 174 | table (requires a hash function) 175 | 176 | ![:thumb](Use if you either 177 | 178 | - have a large key space that mostly lacks value, or 179 | 180 | - will be looking up unpredictable values a lot or frequently 181 | adding/removing values. 182 | ) 183 | 184 | --- 185 | # Map example 186 | 187 | In some structural simulation each parallel processs might own a part of the domain. 188 | 189 | .center[ 190 | ![:scale_img 60%](domain_decomp.png) 191 | ] 192 | 193 | --- 194 | # Map example 195 | 196 | So after starting all the processes and reading the domain we might 197 | have code like this: 198 | 199 | ```C++ 200 | auto rank2comms = std::map{}; 201 | for (auto p = 0; p < MPI_COMM_SIZE; ++p) { 202 | if (ShareBoundaryWithRank(p)) { 203 | rank2comms[p] = BoundaryComm(my_rank, p); 204 | } 205 | } 206 | // later 207 | for (auto [rank, bc]: rank2comm) { 208 | bc->SendData(local_data); 209 | } 210 | ``` 211 | ??? 212 | 213 | Map takes two type parameters in the angle brackets: the key type and 214 | value type 215 | 216 | What's with the square brackets? It's a structured binding similar to 217 | python's tuple unpacking 218 | 219 | --- 220 | # Guidelines 221 | 222 | > Each container has its traits 223 | > That define the places where they are great 224 | > Particularly vector 225 | > You don't need a lecture 226 | > Just use vector 227 | 228 | > Where choosing a container, remember vector is best 229 | > Leave a comment to explain if you choose from the rest 230 | 231 | Credit - [Tony van Eerd](https://twitter.com/tvaneerd) @ [CppCon 2017](https://youtu.be/QTLn3goa3A8?t=332) 232 | 233 | --- 234 | template: titleslide 235 | # Loops 236 | 237 | --- 238 | # Loops 239 | 240 | Three types: 241 | 242 | - `while` 243 | 244 | - range-based `for` loop 245 | 246 | - C-style `for` loop 247 | 248 | --- 249 | # While 250 | 251 | ```C++ 252 | while (boolean expression) { 253 | // Code 254 | } 255 | ``` 256 | 257 | ??? 258 | 259 | The expression inside the parens can be anything that is convertable to `bool` 260 | 261 | `int` - zero => false, anything else => true 262 | 263 | Consider computing some prime numbers 264 | 265 | --- 266 | # While 267 | 268 | ```C++ 269 | std::vector first_n_primes(unsigned n) { 270 | std::vector ans; 271 | 272 | unsigned maybe_prime = 2; 273 | 274 | while (ans.size() < n) { 275 | if (is_prime(maybe_prime)) { 276 | ans.push_back(maybe_prime); 277 | } 278 | maybe_prime += 1; 279 | } 280 | return ans; 281 | } 282 | ``` 283 | 284 | ??? 285 | 286 | condition can be a variable or a more complex condtion 287 | 288 | --- 289 | # Range based for loop 290 | 291 | ```C++ 292 | #include 293 | #include 294 | 295 | void ShowData(std::vector& data) { 296 | for (int x: data) { 297 | std::cout << x << "\n"; 298 | } 299 | } 300 | ``` 301 | 302 | ??? 303 | 304 | Syntax 305 | 306 | Read it as "for x in data" 307 | 308 | Compare to python 309 | 310 | Point out we are copying each element into a local variable `x` 311 | 312 | --- 313 | # Range based for loop 314 | 315 | ```C++ 316 | #include 317 | 318 | void DoubleInPlace(std::vector& data) { 319 | for (int& x: data) { 320 | x *= 2; 321 | } 322 | } 323 | ``` 324 | 325 | ??? 326 | We are making `x` a reference now 327 | 328 | Changes made through it will update the value inside the container 329 | 330 | --- 331 | # Range based for loop 332 | 333 | What can you iterate over here? 334 | 335 | - Any type with `begin()` and `end()` member functions 336 | 337 | - All the STL containers have this 338 | 339 | - Any type where you have overloads of the free functions `begin` and 340 | `end` accepting your object 341 | 342 | --- 343 | # C-style for loop 344 | 345 | Familiar to C programmers 346 | 347 | Fortran programmers will know this as a `DO` loop 348 | 349 | ```C++ 350 | void DoubleInPlace(std::vector& data) { 351 | for (int i = 0; i < data.size(); ++i) { 352 | data[i] *= 2; 353 | } 354 | } 355 | ``` 356 | 357 | ??? 358 | 359 | This is the canonical form for a counting loop, but you can implement 360 | all sort of things 361 | 362 | Remind people that C counts from zero (because it's how many elements 363 | to advance past the first one) up to but not including the end 364 | 365 | Note pre-increment - do not post increment without good reason 366 | 367 | Sometime you really need this - but should usually have a range for 368 | loop for the simple cases. "What you say when you say nothing" 369 | 370 | --- 371 | template: titleslide 372 | # Iterators 373 | 374 | --- 375 | 376 | # What is an iterator? 377 | 378 | Let's think about a C-style for loop: 379 | 380 | ```C++ 381 | void DoubleInPlace(std::vector& data) { 382 | for (int i = 0; i < data.size(); ++i) { 383 | data[i] *= 2; 384 | } 385 | } 386 | ``` 387 | 388 | ??? 389 | 390 | In this function we are mixing things up 391 | - The way in which we iterate through `data` 392 | - The operation that we perform on it 393 | - The way in which we access an element 394 | 395 | We are also requiring that `data` supports random access (cos 396 | `operator[]`). Not all containers do 397 | 398 | --- 399 | # Example using iterators 400 | 401 | ```C++ 402 | void DoubleInPlace(std::vector& data) { 403 | for (std::vector::iterator it = data.begin(); 404 | it != data.end(); ++it) { 405 | *it *= 2; 406 | } 407 | } 408 | ``` 409 | 410 | This is the "old fashioned C++" way 411 | 412 | --- 413 | # Example using iterators 414 | 415 | ```C++ 416 | void DoubleInPlace(std::vector& data) { 417 | for (auto it = data.begin(); it != data.end(); ++it) { 418 | *it *= 2; 419 | } 420 | } 421 | ``` 422 | 423 | This uses `auto` to let the compiler deduce the type for you 424 | 425 | ??? 426 | 427 | Discuss pre-increment (optimiser is not perfect) 428 | 429 | Discuss `operator*` 430 | 431 | Discuss `operator==`. While `operator<` is prefered for numeric comparisons 432 | in loop conditions, it is conventional to use `operator!=` to test if an 433 | iterator has reached the end element. This is because some iterator types are 434 | not relationally comparable, but `operator!=` works with all iterator types 435 | 436 | --- 437 | # Implementing your own iterator 438 | 439 | To define your own iterator, you need to create a type with several 440 | operator overloads (exactly which ones depends on the category of 441 | iterator you need). Basics are: 442 | 443 | - dereference operator (`*it`) - you have to be able to get a value 444 | (either to read or write) 445 | 446 | - pre-increment (`++it`) - you have to be able to go to the next 447 | one 448 | ![:fn](Why not post-increment? Because this has to return the 449 | value of `it` from *before* it was incremented. This usually means 450 | a copy.) 451 | 452 | - assigment - you need to be able to bind it to name 453 | 454 | - inequality comparison (`it != end`) - you need to know when you are 455 | done 456 | 457 | ![:fn_show]( ) 458 | 459 | --- 460 | template: titleslide 461 | # Exercise 462 | 463 | --- 464 | # Containers exercise 465 | 466 | In your clone of this repository, find the `3-containers` exercise. It contains two sub-directories `part1` and `part2`. List 467 | the files in `part1`: 468 | 469 | ```bash 470 | $ cd archer2-cpp/exercises/3-containers/part1 471 | $ ls 472 | Makefile test.cpp vector_ex.cpp vector_ex.hpp 473 | ``` 474 | 475 | As before, `test.cpp` holds some basic unit tests and you can compile with `make`. 476 | 477 | **Part 1** 478 | 479 | `vector_ex.cpp`/`.hpp` hold some functions that work on `std::vector` - provide 480 | the implementations. 481 | 482 | 483 | **Part 2** 484 | 485 | Implement, in a new header/implementation pair of files (`map_ex.hpp`/`.cpp`), 486 | a function (`AddWord`) that adds a string to a `std::map` as the key, the value 487 | being the length of the string. Note: Copy your completed `vector_ex.cpp`/`.hpp` files from part 1. 488 | 489 | You can find documentatation for `map` here: https://en.cppreference.com/ 490 | -------------------------------------------------------------------------------- /lectures/3-loops-containers/domain_decomp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/3-loops-containers/domain_decomp.png -------------------------------------------------------------------------------- /lectures/3-loops-containers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Containers, loops, and iterators 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lectures/4-resources/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | 3 | # Resource management 4 | ## Luca Parisi, EPCC 5 | ## l.parisi@epcc.ed.ac.uk 6 | 7 | --- 8 | # Resources 9 | 10 | In a program you often need to manage things like: 11 | 12 | - memory 13 | 14 | - open files 15 | 16 | - GPUs 17 | 18 | - network sockets 19 | 20 | ??? 21 | 22 | Let's talk a bit about memory first 23 | 24 | --- 25 | # Memory: overview 26 | 27 | .columns[ 28 | .col[ 29 | Modern OSes given each process a flat address space 30 | 31 | Each byte can be accessed by its address, which is just a number. 32 | 33 | For most purposes this can be considered in two parts: 34 | - stack 35 | - heap 36 | ] 37 | .col[ 38 | .center[ 39 | ![:scale_img 50%](mem_layout.jpg) 40 | ] 41 | ] 42 | ] 43 | 44 | --- 45 | # Memory: stack 46 | 47 | In C++ (and C and many other compiled languages) local variables are 48 | stored in the **stack**. 49 | 50 | As your program runs, the local variables that are defined, get space 51 | allocated by the compiler relative to the current stack frame. 52 | 53 | Each time you call a function, you start a new stack frame by 54 | incrementing the stack pointer. 55 | 56 | Variables are implemented as offsets from this, so allocating a local 57 | variable has no run-time cost. 58 | 59 | When you return from a function, the stack pointer is updated and 60 | deallocation also has no cost 61 | 62 | These allocations are called *static* because they have to be prepared 63 | at compile time 64 | 65 | --- 66 | # Memory: heap 67 | 68 | The language and OS also make available some memory for dynamic 69 | allocation: the *heap* 70 | 71 | You need to request some memory to store an object and then give it 72 | back when you are finished 73 | 74 | In C++ this uses the `new` and `delete` keywords 75 | 76 | ```C++ 77 | int main() 78 | { 79 | // This memory for 3 integers 80 | // is allocated on heap. 81 | int *ptr = new int[3]{1,2,3}; 82 | 83 | // To keep things tidy we need to manually call delete 84 | delete[] ptr; 85 | } 86 | ``` 87 | 88 | ??? 89 | 90 | Fortran has allocatable arrays and somewhat restricted pointers 91 | 92 | C programmers will be familiar with malloc and free, which is also 93 | present in C++, but should never be used (I can't recall ever having 94 | done so) 95 | 96 | --- 97 | # Pointers in C++ 98 | 99 | In C++ you can get a pointer to an object using `&` (the *address of* operator) 100 | 101 | You can read or write the value-that-is-pointed-to with `*` (the *dereference* operator) 102 | 103 | ```C++ 104 | int main() { 105 | int i = 99; 106 | int* i_ptr = &i; 107 | 108 | *i_ptr += 1; 109 | std::cout << i << std::endl; 110 | } 111 | ``` 112 | 113 | --- 114 | # Pointers vs references 115 | 116 | Pointers are a lot like references, but they are not guaranteed to be 117 | initialised to a valid value! 118 | 119 | It is valid to assign an arbitrary value to a pointer, but actually 120 | using it is **undefined behaviour** - typically a crash but could be 121 | silent corruption of data. 122 | 123 | ```C++ 124 | int main() { 125 | int* i_ptr = 0xdeadbeef; 126 | 127 | *i_ptr += 1; 128 | std::cout << i << std::endl; 129 | } 130 | ``` 131 | Use pointers with great caution! 132 | 133 | --- 134 | # Pointers vs references 135 | 136 | Pointers are a lot like references, but they are not guaranteed to be 137 | initialised to a valid value! 138 | 139 | It is valid to assign an arbitrary value to a pointer, but actually 140 | using it is **undefined behaviour** - typically a crash but could be 141 | silent corruption of data. 142 | 143 | ```C++ 144 | int* make_bad_pointer() { 145 | int i = 42; 146 | return &i; 147 | } 148 | ``` 149 | 150 | Returning a pointer to a local variable is a recipe for problems 151 | 152 | ??? 153 | 154 | Because once that function returns the lifetime of the target object 155 | has ended and accessing it is UB 156 | 157 | --- 158 | # Pointers to dynamically allocated memory 159 | 160 | In C++ you can manually create instances of objects dynamically with 161 | `new` and try to remember to destroy them with `delete` 162 | 163 | ```C++ 164 | int main() 165 | { 166 | // Memory for an integer is allocated on heap. 167 | int *ptr = new int{}; 168 | *ptr = 7; // Put a value in that memory location 169 | 170 | // Initialize a dynamic array 171 | int* array_ptr = new int[5]{ 9, 7, 5, 3, 1 }; 172 | 173 | // To keep things tidy we need to manually call delete 174 | delete ptr; 175 | delete[] array_ptr; 176 | } 177 | ``` 178 | 179 | But doing so is a recipe for problems! 180 | 181 | ??? 182 | 183 | Using new and delete throughout your code is generally going to cause 184 | you problems, even with extensive use of tools like AddressSanitizer (ASan) 185 | and Valgrind 186 | 187 | But C++ has a design pattern that can tame this - first another feature of the language 188 | 189 | --- 190 | # Pointers exercise 191 | 192 | Write a new C++ code to test your understanding of pointers. 193 | 194 | Start with an int x and give it a value. 195 | 196 | Create a pointer to x and show how to dereference the pointer and increment the value of x. 197 | 198 | Make sure your print the location of the pointer and x in memory so you convince yourself no data movement has occurred. 199 | 200 | Second create an double array y of 4 elements and give each element a value. 201 | 202 | Create two pointers one for each of the 0th and 1st members of the array. 203 | 204 | Incrementing the pointer to the 0th element of the array and show the pointer now points to the 1st element of the array. 205 | 206 | Make sure to print the location of the pointer and array elements in memory to convince yourself no data movement has occurred. 207 | -------------------------------------------------------------------------------- /lectures/4-resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Resource Management 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/4-resources/mem_layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/4-resources/mem_layout.jpg -------------------------------------------------------------------------------- /lectures/4-resources/sample/.gitignore: -------------------------------------------------------------------------------- 1 | arr1 2 | arr2 3 | arr3 4 | shared 5 | -------------------------------------------------------------------------------- /lectures/4-resources/sample/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 2 | CC = $(CXX) 3 | 4 | exes = arr1 arr2 arr3 shared 5 | all : $(exes) 6 | 7 | clean : 8 | rm -rf *.o $(exes) 9 | arr1 : arr1.o 10 | arr2 : arr2.o 11 | arr3 : arr3.o 12 | shared : shared.o 13 | -------------------------------------------------------------------------------- /lectures/4-resources/sample/arr1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Destructor 13 | ~my_array() { 14 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Copy constructor 13 | my_array(my_array const& other) : size(other.size), data(new double[size]) { 14 | std::cout << "Copy constructing: " << data << std::endl; 15 | std::copy(other.data, other.data + size, data); 16 | } 17 | 18 | // Copy assignment operator 19 | my_array& operator=(my_array const& other) { 20 | std::cout << "Destroying: " << data << std::endl; 21 | delete[] data; 22 | size = other.size; 23 | data = new double[size]; 24 | std::cout << "Copy assigning: " << data << std::endl; 25 | std::copy(other.data, other.data + size, data); 26 | return *this; 27 | } 28 | 29 | // Destructor 30 | ~my_array() { 31 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Copy constructor 13 | my_array(my_array const& other) : size(other.size), data(new double[size]) { 14 | std::cout << "Copy constructing: " << data << std::endl; 15 | std::copy(other.data, other.data + size, data); 16 | } 17 | 18 | // Copy assignment operator 19 | my_array& operator=(my_array const& other) { 20 | std::cout << "Destroying: " << data << std::endl; 21 | delete[] data; 22 | size = other.size; 23 | data = new double[size]; 24 | std::cout << "Copy assigning: " << data << std::endl; 25 | std::copy(other.data, other.data + size, data); 26 | return *this; 27 | } 28 | 29 | // Move constructor 30 | my_array(my_array&& other) noexcept : size(other.size), data(other.data) { 31 | std::cout << "Move construct: " << data << std::endl; 32 | other.size = 0; 33 | other.data = nullptr; 34 | } 35 | 36 | // Move assignment operator 37 | my_array& operator=(my_array&& other) noexcept { 38 | std::swap(size, other.size); 39 | std::swap(data, other.data); 40 | std::cout << "Move assign: " << data << std::endl; 41 | return *this; 42 | } 43 | 44 | // Destructor 45 | ~my_array() { 46 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | ~dyn_array() { 12 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | dyn_array(dyn_array const& other) : size(other.size), data(new double[size]) { 12 | std::cout << "Copy constructing: " << data << std::endl; 13 | std::copy(other.data, other.data + size, data); 14 | } 15 | dyn_array& operator=(dyn_array const& other) { 16 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | dyn_array(dyn_array const& other) : size(other.size), data(new double[size]) { 12 | std::cout << "Copy constructing: " << data << std::endl; 13 | std::copy(other.data, other.data + size, data); 14 | } 15 | dyn_array& operator=(dyn_array const& other) { 16 | std::cout << "Destroying: " < 2 | #include 3 | #include 4 | 5 | class Cowboy { 6 | using ptr = std::shared_ptr; 7 | std::string name; 8 | std::weak_ptr partner; 9 | public: 10 | 11 | Cowboy(std::string const& n) : name(n) { 12 | std::cout << name << std::endl; 13 | } 14 | ~Cowboy() { std::cout << "Delete " << name << std::endl; } 15 | friend void partner_up(ptr a, ptr b) { 16 | std::cout << "BFFs: " << a->name << " & " << b->name << std::endl; 17 | a->partner = b; b->partner = a; 18 | } 19 | }; 20 | 21 | int main() { 22 | auto good = std::make_shared("Alice"); 23 | auto bad = std::make_shared("Bob"); 24 | //ugly 25 | partner_up(good, bad); 26 | } 27 | -------------------------------------------------------------------------------- /lectures/5-templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Templates and Traits 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/6.1-RAII/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | 3 | # RAII 4 | ## Nathan Mannall, EPCC 5 | ## n.mannall@epcc.ed.ac.uk 6 | 7 | --- 8 | template: titleslide 9 | # Reminder Classes and Constructors 10 | 11 | --- 12 | # Classes 13 | 14 | - User defined types. 15 | 16 | - can be defined with either the `class` or `struct` keyword. 17 | 18 | ```C++ 19 | struct Complex { 20 | double re; 21 | double im; 22 | }; 23 | ``` 24 | 25 | - Creating trivial types - give the class name then list the values to be assigned to the 26 | members, _in order_, inside braces: 27 | 28 | ```C++ 29 | Complex mk_imaginary_unit() { 30 | return Complex{0, 1}; 31 | } 32 | ``` 33 | This is called aggregate initialisation. 34 | 35 | --- 36 | # Constructors 37 | 38 | Often you want to control the creation of instances of your classes. 39 | 40 | You do this with _constructors_ - these are special member "functions" 41 | with the same name as the type. 42 | 43 | ```C++ 44 | struct Complex { 45 | Complex() = default; 46 | Complex(double re); 47 | Complex(double re, double im); 48 | double re = 0.0; 49 | double im = 0.0; 50 | }; 51 | ``` 52 | 53 | Note we declare three: 54 | - one that initialises with a purely real value 55 | - one that initialises with a real and imaginary value 56 | - a *default constructor* which needs no arguments (that we tell the 57 | compiler to generate for us as before with `= default` ) 58 | 59 | ??? 60 | 61 | Control in more detail than just starting from a default value or 62 | having to provide *all* the of member values. 63 | 64 | Constructors are not strictly functions in C++ but very nearly (next slide) 65 | 66 | Why do you have to "explictly default the default constructor"? 67 | 68 | Because the language rules say if the user provides any constructors, 69 | the compiler must not create one unless asked to... 70 | 71 | --- 72 | # Constructors 73 | 74 | - Constructors are not directy callable 75 | - Constructors do not return a value 76 | - Constructors can do initialisation of member variables before the body begins execution 77 | 78 | 79 | Let's define the ones we declared just now: 80 | 81 | ```C++ 82 | Complex::Complex(double real) : re{real} { 83 | } 84 | 85 | Complex::Complex(double real, double imag) : re{real}, im{imag} { 86 | } 87 | ``` 88 | 89 | --- 90 | # Destructors 91 | 92 | You can also control what happens when your objects reach the end of 93 | their lifetime. 94 | 95 | When this happens is deterministic: 96 | - when a local variable goes out of scope 97 | - when an object that contains them is destroyed 98 | - when the programmer `delete`s them 99 | 100 | For a class `Name` they are declared like: 101 | 102 | ```C++ 103 | struct Name { 104 | ~Name(); 105 | }; 106 | ``` 107 | 108 | It's important to note that you should **never call this directly** - 109 | the compiler will call it for you when your objects are deallocated. 110 | 111 | ??? 112 | 113 | Note the tilde syntax is the logical negation of the class. Cf 114 | annihilation operators for any physicists. 115 | 116 | 117 | --- 118 | # Resource allocation is instantiation 119 | 120 | A very important pattern in C++ is **RAII**: resource allocation is 121 | instantiation. 122 | 123 | Also known as constructor acquires, destructor releases (CADRe). 124 | 125 | This odd name is trying to communicate that any resource you have 126 | should be tied to the lifetime of an object. 127 | 128 | So the when the compiler destroys your object it will release the 129 | resource (e.g. memory). 130 | 131 | ??? 132 | 133 | Saying that in some philosophical sense allocating a resource is the 134 | creation of something, which implies its destruction later. 135 | 136 | --- 137 | # RAII example 138 | 139 | A very simple copy of `std::vector`: 140 | 141 | ```C++ 142 | class my_array { 143 | unsigned size = 0; 144 | double* data = nullptr; 145 | public: 146 | my_array() = default; 147 | explicit my_array(unsigned n) : size(n), data(new double[size]) {} 148 | ~my_array() { 149 | delete[] data; 150 | } 151 | double& operator[](unsigned i) { 152 | return data[i]; 153 | } 154 | }; 155 | ``` 156 | 157 | ??? 158 | 159 | This class allocates some memory to store `n` doubles when constructed 160 | 161 | When it reaches the end of its life the destructor returns the memory 162 | to the OS 163 | 164 | It allows users to access elements (with no bounds checking) 165 | 166 | --- 167 | # What happens when we compile and run? 168 | 169 | Add a few annotations to print in the contructor/destructor 170 | 171 | ??? 172 | 173 | Open sample/arr1.cpp 174 | Compile and run 175 | 176 | What happens if we copy x? 177 | 178 | Add `auto x_cp = x;` (same as `auto x_cp = my_array{x};`) 179 | 180 | --- 181 | # Copying 182 | 183 | When you value assign an object in C++ this will only be valid if 184 | there is a *copy constructor* or *copy assignment operator* 185 | 186 | -- 187 | 188 | Copy constructor - when you create a new object as the destination: 189 | 190 | ```C++ 191 | my_array x{10}; // Direct initialisation 192 | my_array y{x}; // Direct initialisation 193 | my_array z = x; // Copy initialization 194 | ``` 195 | 196 | -- 197 | Copy assignment - when you assign a new value to an existing object 198 | 199 | ```C++ 200 | my_array x{10}; 201 | x = my_array{2000}; 202 | ``` 203 | 204 | ??? 205 | 206 | What's the diff? 207 | 208 | In the last case, you have to deal with releasing any resources held 209 | by the target object 210 | 211 | --- 212 | # Implicit copy 213 | 214 | The compiler will automatically generate these operations for us if 215 | all the data members of you class are copyable. 216 | 217 | So what went wrong with the example shown? 218 | 219 | -- 220 | A pointer is just a number and so it can be copied implicitly - hence the double delete 221 | 222 | If we want to copy our array then we need to either: 223 | - copy the data (aka deep copy) 224 | - share the data and somehow keep track of when the last reference to it is destroyed (aka shallow copy) 225 | 226 | ??? 227 | 228 | Deep copies are more expensive at run time but somewhat safer 229 | 230 | Shallow copies can be faster but harder to implement correctly and can have thread safety issues 231 | 232 | Do we want to copy? 233 | 234 | --- 235 | # User-defined copy 236 | 237 | Of course, you can control how your objects are copied 238 | 239 | 240 | ```C++ 241 | class my_array { 242 | unsigned size = 0; 243 | double* data = nullptr; 244 | public: 245 | my_array() = default; 246 | explicit my_array(unsigned n) : size(n) data(new double[size]) {} 247 | my_array(my_array const& other) : size(other.size), data(new double[size]) { 248 | // Copy data 249 | } 250 | my_array& operator=(my_array const& other) { 251 | delete[] data; 252 | size = other.size; 253 | data = new double[size]; 254 | // Copy data 255 | return *this; 256 | } 257 | ~my_array() { 258 | delete[] data; 259 | } 260 | }; 261 | ``` 262 | ??? 263 | 264 | Open arr2.cpp 265 | 266 | Note the signature 267 | 268 | --- 269 | # Returning a value looks a lot like copying 270 | 271 | When a function returns a value, you might think that will copy it to the target: 272 | 273 | ```C++ 274 | std::vector ReadData() { 275 | std::vector answer; 276 | // Read it from somewhere 277 | return answer; 278 | } 279 | 280 | int main() { 281 | auto data = ReadData(); 282 | } 283 | ``` 284 | 285 | ??? 286 | 287 | Thinking about std::vector examples we've seen and that you might have implemented 288 | 289 | Have previously said that you should use bare auto when you want a 290 | copy - by that what we really mean is you want to *own* the object and 291 | control its lifetime. 292 | 293 | Copying a vector of billions of elements is going to get expensive and 294 | would be counter to C++'s zero overhead abstractions principle 295 | 296 | --- 297 | # Move instead 298 | 299 | Since C++11, the language has supported the concept of *moving* from 300 | objects which the compiler knows (or the programmer asserts) will not 301 | be used any more. 302 | 303 | Examples are: 304 | - temporaries (i.e. the result of a function call/constructor expression) 305 | - automatic variables that are going out of scope 306 | - the result of calling `std::move` on an object 307 | 308 | The destination object "steals" the contained resources from the 309 | source object and sets the source to a valid but undefined state - 310 | typically the only operations you can perform on a moved-from object 311 | are destruction and assignment. 312 | 313 | --- 314 | # Move implementation 315 | 316 | Going back to our simple array: 317 | ```C++ 318 | class my_array { 319 | unsigned size = 0; 320 | double* data = nullptr; 321 | public: 322 | // c'tors, copy assignment, d'tor 323 | my_array(my_array&& other) noexcept : size(other.size), data(other.data) { 324 | other.size = 0; 325 | other.data = nullptr; 326 | } 327 | my_array& operator=(my_array&& other) noexcept { 328 | std::swap(size, other.size); 329 | std::swap(data, other.data); 330 | } 331 | }; 332 | ``` 333 | 334 | ??? 335 | 336 | Comment on `noexcept` - this is for STL compatibility. The containers 337 | will copy if your move operations are not noexcept. These ones cannot 338 | throw exceptions so this is safe. 339 | 340 | 341 | Look at arr3.cpp 342 | 343 | --- 344 | # The Rule of Five 345 | 346 | This says that if you define or delete one of the following: 347 | - copy constructor 348 | - copy assignment operator 349 | - move constructor 350 | - move assignment operator 351 | - destructor 352 | 353 | then you should probably do so for all five. 354 | 355 | ??? 356 | This can be quite a lot of work! 357 | 358 | --- 359 | # The Rule of Zero 360 | 361 | This says that unless your class is solely deals with ownership, then 362 | it should define none of the five special functions. 363 | 364 | This is really a corollary of the general software engineering 365 | "principle of single responsibility". 366 | 367 | You should split your code into a resource manager with all five 368 | functions and a user class that has none, but uses the resource 369 | manager as one or more data members. 370 | 371 | ??? 372 | 373 | If it does deal with ownership then rule of 5 applies :( 374 | 375 | --- 376 | 377 | # my_array Exercise 378 | 379 | Time to try this out for yourself your own class. 380 | 381 | **Part 1** 382 | 383 | - Implement the constructor 384 | - Implement the destructor 385 | 386 | **Part 2** 387 | 388 | - Implement the copy constructor 389 | - Implement the copy assignment operator 390 | 391 | **Part 3** 392 | 393 | - Implement the move constructor 394 | - Implement the movement move assignment operator 395 | 396 | 397 | 398 | N.B. Add print statements to each function call in my_vector so you can see when each of the class members are called. 399 | 400 | -------------------------------------------------------------------------------- /lectures/6.1-RAII/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RAII 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/6.1-RAII/mem_layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/6.1-RAII/mem_layout.jpg -------------------------------------------------------------------------------- /lectures/6.1-RAII/sample/.gitignore: -------------------------------------------------------------------------------- 1 | arr1 2 | arr2 3 | arr3 4 | shared 5 | -------------------------------------------------------------------------------- /lectures/6.1-RAII/sample/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 2 | CC = $(CXX) 3 | 4 | exes = arr1 arr2 arr3 shared 5 | all : $(exes) 6 | 7 | clean : 8 | rm -rf *.o $(exes) 9 | arr1 : arr1.o 10 | arr2 : arr2.o 11 | arr3 : arr3.o 12 | shared : shared.o 13 | -------------------------------------------------------------------------------- /lectures/6.1-RAII/sample/arr1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Destructor 13 | ~my_array() { 14 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Copy constructor 13 | my_array(my_array const& other) : size(other.size), data(new double[size]) { 14 | std::cout << "Copy constructing: " << data << std::endl; 15 | std::copy(other.data, other.data + size, data); 16 | } 17 | 18 | // Copy assignment operator 19 | my_array& operator=(my_array const& other) { 20 | std::cout << "Destroying: " << data << std::endl; 21 | delete[] data; 22 | size = other.size; 23 | data = new double[size]; 24 | std::cout << "Copy assigning: " << data << std::endl; 25 | std::copy(other.data, other.data + size, data); 26 | return *this; 27 | } 28 | 29 | // Destructor 30 | ~my_array() { 31 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Copy constructor 13 | my_array(my_array const& other) : size(other.size), data(new double[size]) { 14 | std::cout << "Copy constructing: " << data << std::endl; 15 | std::copy(other.data, other.data + size, data); 16 | } 17 | 18 | // Copy assignment operator 19 | my_array& operator=(my_array const& other) { 20 | std::cout << "Destroying: " << data << std::endl; 21 | delete[] data; 22 | size = other.size; 23 | data = new double[size]; 24 | std::cout << "Copy assigning: " << data << std::endl; 25 | std::copy(other.data, other.data + size, data); 26 | return *this; 27 | } 28 | 29 | // Move constructor 30 | my_array(my_array&& other) noexcept : size(other.size), data(other.data) { 31 | std::cout << "Move construct: " << data << std::endl; 32 | other.size = 0; 33 | other.data = nullptr; 34 | } 35 | 36 | // Move assignment operator 37 | my_array& operator=(my_array&& other) noexcept { 38 | std::swap(size, other.size); 39 | std::swap(data, other.data); 40 | std::cout << "Move assign: " << data << std::endl; 41 | return *this; 42 | } 43 | 44 | // Destructor 45 | ~my_array() { 46 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | ~dyn_array() { 12 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | dyn_array(dyn_array const& other) : size(other.size), data(new double[size]) { 12 | std::cout << "Copy constructing: " << data << std::endl; 13 | std::copy(other.data, other.data + size, data); 14 | } 15 | dyn_array& operator=(dyn_array const& other) { 16 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | dyn_array(dyn_array const& other) : size(other.size), data(new double[size]) { 12 | std::cout << "Copy constructing: " << data << std::endl; 13 | std::copy(other.data, other.data + size, data); 14 | } 15 | dyn_array& operator=(dyn_array const& other) { 16 | std::cout << "Destroying: " < 2 | #include 3 | #include 4 | 5 | class Cowboy { 6 | using ptr = std::shared_ptr; 7 | std::string name; 8 | std::weak_ptr partner; 9 | public: 10 | 11 | Cowboy(std::string const& n) : name(n) { 12 | std::cout << name << std::endl; 13 | } 14 | ~Cowboy() { std::cout << "Delete " << name << std::endl; } 15 | friend void partner_up(ptr a, ptr b) { 16 | std::cout << "BFFs: " << a->name << " & " << b->name << std::endl; 17 | a->partner = b; b->partner = a; 18 | } 19 | }; 20 | 21 | int main() { 22 | auto good = std::make_shared("Alice"); 23 | auto bad = std::make_shared("Bob"); 24 | //ugly 25 | partner_up(good, bad); 26 | } 27 | -------------------------------------------------------------------------------- /lectures/6.2-RAII-smart-pointers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RAII - Smart Pointers 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/6.2-RAII-smart-pointers/mem_layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/6.2-RAII-smart-pointers/mem_layout.jpg -------------------------------------------------------------------------------- /lectures/6.2-RAII-smart-pointers/sample/.gitignore: -------------------------------------------------------------------------------- 1 | arr1 2 | arr2 3 | arr3 4 | shared 5 | -------------------------------------------------------------------------------- /lectures/6.2-RAII-smart-pointers/sample/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = --std=c++17 2 | CC = $(CXX) 3 | 4 | exes = arr1 arr2 arr3 shared 5 | all : $(exes) 6 | 7 | clean : 8 | rm -rf *.o $(exes) 9 | arr1 : arr1.o 10 | arr2 : arr2.o 11 | arr3 : arr3.o 12 | shared : shared.o 13 | -------------------------------------------------------------------------------- /lectures/6.2-RAII-smart-pointers/sample/arr1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Destructor 13 | ~my_array() { 14 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Copy constructor 13 | my_array(my_array const& other) : size(other.size), data(new double[size]) { 14 | std::cout << "Copy constructing: " << data << std::endl; 15 | std::copy(other.data, other.data + size, data); 16 | } 17 | 18 | // Copy assignment operator 19 | my_array& operator=(my_array const& other) { 20 | std::cout << "Destroying: " << data << std::endl; 21 | delete[] data; 22 | size = other.size; 23 | data = new double[size]; 24 | std::cout << "Copy assigning: " << data << std::endl; 25 | std::copy(other.data, other.data + size, data); 26 | return *this; 27 | } 28 | 29 | // Destructor 30 | ~my_array() { 31 | std::cout << "Destroying: " < 2 | 3 | class my_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | my_array() = default; 8 | my_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | 12 | // Copy constructor 13 | my_array(my_array const& other) : size(other.size), data(new double[size]) { 14 | std::cout << "Copy constructing: " << data << std::endl; 15 | std::copy(other.data, other.data + size, data); 16 | } 17 | 18 | // Copy assignment operator 19 | my_array& operator=(my_array const& other) { 20 | std::cout << "Destroying: " << data << std::endl; 21 | delete[] data; 22 | size = other.size; 23 | data = new double[size]; 24 | std::cout << "Copy assigning: " << data << std::endl; 25 | std::copy(other.data, other.data + size, data); 26 | return *this; 27 | } 28 | 29 | // Move constructor 30 | my_array(my_array&& other) noexcept : size(other.size), data(other.data) { 31 | std::cout << "Move construct: " << data << std::endl; 32 | other.size = 0; 33 | other.data = nullptr; 34 | } 35 | 36 | // Move assignment operator 37 | my_array& operator=(my_array&& other) noexcept { 38 | std::swap(size, other.size); 39 | std::swap(data, other.data); 40 | std::cout << "Move assign: " << data << std::endl; 41 | return *this; 42 | } 43 | 44 | // Destructor 45 | ~my_array() { 46 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | ~dyn_array() { 12 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | dyn_array(dyn_array const& other) : size(other.size), data(new double[size]) { 12 | std::cout << "Copy constructing: " << data << std::endl; 13 | std::copy(other.data, other.data + size, data); 14 | } 15 | dyn_array& operator=(dyn_array const& other) { 16 | std::cout << "Destroying: " < 2 | 3 | class dyn_array { 4 | unsigned size = 0; 5 | double* data = nullptr; 6 | public: 7 | dyn_array() = default; 8 | dyn_array(unsigned n) : size(n), data(new double[n]) { 9 | std::cout << "Constructing: " << data << std::endl; 10 | } 11 | dyn_array(dyn_array const& other) : size(other.size), data(new double[size]) { 12 | std::cout << "Copy constructing: " << data << std::endl; 13 | std::copy(other.data, other.data + size, data); 14 | } 15 | dyn_array& operator=(dyn_array const& other) { 16 | std::cout << "Destroying: " < 2 | #include 3 | #include 4 | 5 | class Cowboy { 6 | using ptr = std::shared_ptr; 7 | std::string name; 8 | std::weak_ptr partner; 9 | public: 10 | 11 | Cowboy(std::string const& n) : name(n) { 12 | std::cout << name << std::endl; 13 | } 14 | ~Cowboy() { std::cout << "Delete " << name << std::endl; } 15 | friend void partner_up(ptr a, ptr b) { 16 | std::cout << "BFFs: " << a->name << " & " << b->name << std::endl; 17 | a->partner = b; b->partner = a; 18 | } 19 | }; 20 | 21 | int main() { 22 | auto good = std::make_shared("Alice"); 23 | auto bad = std::make_shared("Bob"); 24 | //ugly 25 | partner_up(good, bad); 26 | } 27 | -------------------------------------------------------------------------------- /lectures/7-combining-classes/Polymorphism-in-CPP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/7-combining-classes/Polymorphism-in-CPP.png -------------------------------------------------------------------------------- /lectures/7-combining-classes/diamond-problem-in-cpp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/7-combining-classes/diamond-problem-in-cpp.webp -------------------------------------------------------------------------------- /lectures/7-combining-classes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Combining Classes 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lectures/8-algorithms-lambdas/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | 3 | # Algorithms and lambdas 4 | ## Luca Parisi, EPCC 5 | ## l.parisi@epcc.ed.ac.uk 6 | 7 | --- 8 | 9 | template: titleslide 10 | # Recap 11 | --- 12 | 13 | # Iterators 14 | ```C++ 15 | std::vector data = GetData(n); 16 | 17 | // C style iteration - fully explicit 18 | for (auto i=0; i != n; ++i) { 19 | data[i] *= 2; 20 | } 21 | // Old C++ style - hides some details 22 | for (auto iter = data.begin(); iter != data.end(); ++iter) { 23 | *iter *= 2; 24 | } 25 | // New range-based for 26 | for (auto& item : data) { 27 | item *= 2; 28 | } 29 | ``` 30 | 31 | --- 32 | template: titleslide 33 | # Standard algorithms library 34 | --- 35 | 36 | # Standard algorithms library 37 | 38 | The library includes many (around 100) function templates that 39 | implement algorithms, like "count the elements that match a criteria", 40 | or "divide the elements based on a condition". 41 | 42 | These all use *iterators* to specify the data they will work on, e.g., 43 | `count` might use a vector's `begin()` and `end()` iterators. 44 | 45 | ```C++ 46 | #include 47 | std::vector data = Read(); 48 | int nzeros = std::count(data.begin(), data.end(), 49 | 0); 50 | 51 | bool is_prime(int x); 52 | int nprimes = std::count_if(data.begin(), data.end(), 53 | is_prime); 54 | ``` 55 | --- 56 | # Standard algorithms library 57 | 58 | Possible implementation: 59 | ```C++ 60 | template 61 | intptr_t count_if(InputIt first, InputIt last, 62 | UnaryPredicate p) { 63 | intptr_t ans = 0; 64 | for (; first != last; ++first) { 65 | if (p(*first)) { 66 | ans++; 67 | } 68 | } 69 | return ans; 70 | } 71 | ``` 72 | 73 | (Unary `\(\implies\)` one argument, a predicate is a Boolean-valued 74 | function.) 75 | 76 | --- 77 | 78 | # Key algorithms 79 | 80 | |Algorithm | Description | 81 | |--|---| 82 | | `for_each` | Apply function to each element in the range. | 83 | | `count`/`count_if`| Return number of elements matching. 84 | | `find`/`find_if` | Return iterator to first element matching or end if no match. | 85 | | `copy`/`copy_if` | Copy input range (if predicate true) to destination | 86 | | `transform` | Apply the function to input elements storing in destination (has overload work on two inputs at once) | 87 | | `swap` | Swap two values - used widely! You may wish to provide a way to make your types swappable (see cpprefence.com) | 88 | | `sort` | Sort elements in ascending order using either `operator<` or the binary predicate provided | 89 | | `lower_bound`/ `upper_bound` | Given a *sorted* range, do a binary search for value. | 90 | 91 | --- 92 | 93 | # `for_each` 94 | 95 | One of the simplest algorithms: apply a function to every element in a 96 | range. 97 | ```C++ 98 | template< class InputIt, class UnaryFunction > 99 | UnaryFunction for_each(InputIt first, 100 | InputIt last, 101 | UnaryFunction f); 102 | ``` 103 | 104 | Why bother? 105 | 106 | - Clearly states your intent 107 | 108 | - Cannot get an off-by-one errors / skip elements 109 | 110 | - Works well with other range-based algorithms 111 | 112 | - Concise if your operation is already defined in a function 113 | 114 | However often a range-based for loop is better! 115 | 116 | --- 117 | # `transform` 118 | 119 | A very powerful function with two variants: one takes a single range, 120 | applies a function to each element, and stores the result in an output 121 | iterator. 122 | ```C++ 123 | template 125 | OutputIt transform(InputIt first1, InputIt last1, 126 | OutputIt d_first, 127 | UnaryOperation unary_op ); 128 | ``` 129 | 130 | This is basically the equivalent of `map` from functional programming or MapReduce. 131 | 132 | ```C++ 133 | std::vector data = GetData(); 134 | std::transform(data.begin(), data.end(), 135 | data.begin(), double_in_place); 136 | ``` 137 | 138 | You can use your input as the output. 139 | 140 | --- 141 | 142 | # Motivation 143 | 144 | Implementations have been written *and tested* by your compiler 145 | authors. 146 | 147 | The library may be able to do platform-specific optimizations that you 148 | probably don't want to maintain. 149 | 150 | They form a language to communicate with other programmers what your 151 | code is really doing. 152 | 153 | It's nice code that you don't have to write. 154 | 155 | ```C++ 156 | for (auto it = images.begin(); 157 | it != images.end(); ++it) { 158 | if (ContainsCat(*it)) { 159 | catpics.push_back(*it); 160 | } 161 | } 162 | ``` 163 | vs 164 | ```C++ 165 | std::copy_if(images.begin(), images.end(), 166 | ContainsCat, std::back_inserter(catpics)); 167 | ``` 168 | 169 | --- 170 | template:titleslide 171 | 172 | # Lambda functions 173 | --- 174 | 175 | # Algorithms need functions 176 | 177 | Very many of the algorithms just discussed need you to provide a 178 | function-like object as an argument for them to use. 179 | 180 | If you have to declare a new function for a one-off use in an algorithm 181 | call that is inconvenient and moves the code away from its use site. 182 | 183 | Worse would be to have to create a custom function object class each 184 | time! 185 | 186 | --- 187 | # A verbose example 188 | 189 | ```C++ 190 | struct SquareAndAddConstF { 191 | float c; 192 | SquareAndAddConstF(float c_) : c(c_) {} 193 | 194 | float operator()(float x) { 195 | return x*x + c; 196 | } 197 | }; 198 | 199 | std::vector SquareAndAddConst(const std::vector& x, float c) { 200 | std::vector ans; 201 | ans.resize(x.size()); 202 | 203 | std::transform(x.begin(), x.end(), ans.begin(), 204 | SquareAndAddConstF(c)); 205 | return ans; 206 | } 207 | ``` 208 | --- 209 | 210 | # Lambdas to the rescue 211 | 212 | - A lambda function a.k.a. a closure is a function object that does 213 | not have a name like the functions you have seen so far. 214 | 215 | - You can define one inside a function body 216 | 217 | - You can bind them to a variable, call them and pass them to other 218 | functions. 219 | 220 | - They can capture local variables (either by value or reference). 221 | 222 | - They have a unique, unknown type so you may have to use `auto` or 223 | pass them straight to a template argument. 224 | 225 | --- 226 | 227 | # A less verbose example 228 | ```C++ 229 | std::vector SquareAndAddConst(const std::vector& x, float c) { 230 | std::vector ans; 231 | ans.resize(x.size()); 232 | auto func = [c] (double z) { return z*z + c; }; 233 | std::transform(x.begin(), x.end(), ans.begin(), 234 | func); 235 | return ans; 236 | } 237 | ``` 238 | 239 | --- 240 | 241 | # A less less verbose example 242 | ```C++ 243 | std::vector SquareAndAddConst(const std::vector& x, float c) { 244 | std::vector ans; 245 | ans.resize(x.size()); 246 | std::transform(x.begin(), x.end(), ans.begin(), 247 | [c] (double z) { return z*z + c; }); 248 | return ans; 249 | } 250 | ``` 251 | 252 | --- 253 | # Anatomy of a lambda 254 | ``` 255 | [captures](arg-list) -> ret-type {function-body} 256 | ``` 257 | | | | 258 | |---|----| 259 | | `[ ... ]` | new syntax that indicates this is a lambda expression | 260 | | `arg-list` | exactly as normal | 261 | | `function-body` | zero or more statements as normal | 262 | | `-> ret-type` | new C++11 syntax to specify the return type of a function - can be skipped if return type is void or function body is only a single `return` statement. | 263 | | `captures` | zero or more comma separated captures | 264 | 265 | 266 | You can capture a value by copy (just put its name: `local`) or by reference 267 | (put an ampersand before its name: `&local`). 268 | 269 | --- 270 | # Anatomy of a lambda 271 | 272 | ``` 273 | [captures](arg-list) -> ret-type {function-body} 274 | ``` 275 | 276 | This creates a function object of a unique unnamed type (you 277 | must use `auto` to store it in a local variable). 278 | 279 | You can call this like any object that overloads `operator()`: 280 | ```C++ 281 | std::cout << "3^2 +c = " << func(3) << std::endl; 282 | ``` 283 | 284 | Note that because it does not have a name, it cannot take part in 285 | overload resolution. 286 | 287 | --- 288 | 289 | # Quick Quiz 290 | 291 | What does the following do? 292 | ```C++ 293 | [](){}(); 294 | ``` 295 | -- 296 | 297 | Nothing 298 | 299 | --- 300 | 301 | # Uses 302 | 303 | - STL algorithms library (or similar) - pass small one-off pieces of 304 | code in freely 305 | 306 | ```C++ 307 | std::sort(molecules.begin(), molecules.end(), 308 | [](const Mol& a, const Mol& b) { 309 | return a.charge < b.charge; 310 | }); 311 | ``` 312 | 313 | - To do complex initialisation on something, especially if it should 314 | be `const`. 315 | 316 | ```C++ 317 | const auto rands = [&size] () -> std::vector { 318 | std::vector ans(size); 319 | for (auto& el: ans) { 320 | el = GetRandomNumber(); 321 | } 322 | return ans; 323 | }(); // Note parens to call! 324 | ``` 325 | 326 | --- 327 | # Rules of thumb 328 | 329 | Be careful with what you capture! 330 | 331 | ![:thumb](If your lambda is used locally - including passed to 332 | algorithms - capture by reference. This ensures there are no copies 333 | and ensures any changes made propagate.) 334 | 335 | ![:thumb](If you use the lambda elsewhere, especially if you return 336 | it, capture by value. Recall references to locals are invalid after 337 | the function returns! References to other objects may still be valid 338 | but hard to keep track of.) 339 | 340 | ![:thumb](Keep lambdas short - more than 10 lines you should think 341 | about moving it to a function/functor instead.) 342 | 343 | 344 | --- 345 | template: titleslide 346 | # Performance 347 | 348 | ??? 349 | 350 | Many people are a bit concerned that using iterators/lambdas etc 351 | incurs some overhead - after all you're calling a bunch of 352 | functions. So let's benchmark them 353 | 354 | --- 355 | 356 | # Many ways to iterate 357 | ```C++ 358 | // C style iteration - fully explicit 359 | for (auto i=0; i != n; ++i) { 360 | data[i] *= 2; 361 | } 362 | 363 | // Old C++ style - hides some details 364 | for (auto iter = data.begin(); iter != data.end(); ++iter) { 365 | *iter *= 2; 366 | } 367 | 368 | // New range-based for 369 | for (auto& item : data) { 370 | item *= 2; 371 | } 372 | 373 | // Algorithms library 374 | std::for_each(data.begin(), data.end(), 375 | double_in_place); 376 | ``` 377 | 378 | --- 379 | # Is there any overhead? 380 | 381 | Going to quickly compare four implementations to scale by 0.5: 382 | 383 | - C-style array indexing 384 | 385 | - Standard vector with iterator 386 | 387 | - Standard vector with range based for-loop 388 | 389 | - Standard vector with std::for_each 390 | 391 | ```C++ 392 | int main(int argc, char** argv) { 393 | int size = std::atoi(argv[1]); 394 | std::vector data(size); 395 | for (auto& el: data) 396 | el = rand(1000); 397 | Timer t; 398 | scale(data.data(), data.size(), 0.5); 399 | std::cout << size << ", " 400 | << t.GetSeconds() << std::endl; 401 | } 402 | ``` 403 | 404 | --- 405 | # Results 406 | .center[![-O0](looptests/opt0.svg)] 407 | 408 | --- 409 | # Results 410 | .center[![-O2](looptests/opt2.svg)] 411 | 412 | --- 413 | # Algorithms Exercise 414 | 415 | In your clone of this repository, find the `algorithm` exercise and list 416 | the files 417 | 418 | ``` 419 | $ cd archer2-cpp/exercises/algorithm 420 | $ ls 421 | Makefile ex.cpp README.md 422 | ``` 423 | 424 | In the file `ex.cpp` there is an incomplete program which, by 425 | following the instructions in the comments, you can finish. 426 | 427 | You will likely want to refer to the documentation of the standard 428 | library algorithms, which, for reasons, are split across two headers: 429 | 430 | - 431 | 432 | - 433 | -------------------------------------------------------------------------------- /lectures/8-algorithms-lambdas/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Algorithms and lambdas 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lectures/9-eigen/README.md: -------------------------------------------------------------------------------- 1 | template: titleslide 2 | # Linear Algebra for C++ (using Eigen) 3 | ## Nathan Mannall, EPCC 4 | ## n.mannall@epcc.ed.ac.uk 5 | 6 | --- 7 | # Source 8 | 9 | Original: 10 | - Chris Richardson (chris@bpi.cam.ac.uk) 11 | - Rupert Nash (r.nash@epcc.ed.ac.uk) 12 | - Joseph Lee (j.lee@epcc.ed.ac.uk) 13 | --- 14 | 15 | # Rundown 16 | 17 | - C++ Linear Algebra libraries: what and why? 18 | 19 | - Eigen3: 20 | 21 | - Getting started 22 | 23 | - Matrices: Dense, Sparse, Geometry 24 | 25 | - Solvers: Dense, Sparse, Iterative 26 | 27 | - General notes 28 | 29 | - Diffusion equation demo + exercise 30 | 31 | 32 | --- 33 | 34 | # Linear Algebra: What do we need? 35 | 36 | __What for?__ 37 | 38 | - Scientific computing, PDE, simulations, finance, graphics, ML and more 39 | 40 | __What we expect?__ 41 | 42 | Object types: 43 | 44 | - Vectors 45 | 46 | - Matrix: (Dense / Sparse / Diagonal / Symmetric / Column/Row major) 47 | 48 | - Operations: Matrix (Multiplication/transpose/geometric rotation) 49 | 50 | - Solvers (`\(Ax = b\)`, Eigenvalues) : Direct (LU/QR factorization) / Indirect (CG) 51 | 52 | Numeric types: 53 | 54 | - `complex` `complex` 55 | --- 56 | 57 | # Why use a library? 58 | 59 | - Simple & easy to use : clean API 60 | 61 | - Fast Performance 62 | 63 | - Correctness 64 | 65 | - Expressiveness 66 | 67 | - Don’t reinvent the wheel 68 | 69 | --- 70 | 71 | # Linear Algebra on C++ 72 | 73 | - Simple: **Eigen** 74 | 75 | - Big & popular: **PETSc** (C) 76 | 77 | - Standard: **LAPACK/LAPACK++/ScaLAPACK** 78 | 79 | - GPU: Nvidia/AMD **(cu/roc)(BLAS/SPARSE/SOLVER)** 80 | 81 | - CPU: **Intel MKL**, **OpenBLAS** 82 | 83 | - Future: C++26 std library? 84 | 85 | - vs: Python Numpy/Scipy 86 | 87 | - 88 | - 89 | --- 90 | 91 | # Eigen3 92 | 93 | ## Numpy, but C++ 94 | 95 | “Header only” no library to link 96 | - `g++ -I /path/to/eigen/ my_program.cpp -o my_program` 97 | 98 | Contains optimisation for ARM & Intel processors (Vectorization - SIMD instructions) 99 | 100 | Easy to use interface 101 | 102 | Support for dense and sparse matrices, vectors, and “arrays” (coefficient-wise ops) 103 | 104 | Support for some solvers 105 | 106 | Download from or e.g. apt install libeigen3-dev 107 | 108 | --- 109 | 110 | # Matrix: Basic 111 | 112 | ```C++ 113 | #include 114 | 115 | int main() 116 | { 117 | Eigen::Matrix A; 118 | A.setZero(); 119 | A(9, 0) = 1.234; 120 | std::cout << A << std::endl; 121 | return 0; 122 | } 123 | ``` 124 | 125 | This is pretty similar to `double A[10][10];` 126 | 127 | --- 128 | # Dynamic size 129 | 130 | ```C++ 131 | int n = 64; 132 | int m = 65; 133 | Eigen::Matrix A(n, m); 134 | 135 | A.resize(20, 20); 136 | 137 | std::cout << “Size is ”; 138 | std::cout << A.rows() << “ x ” << A.cols() << std::endl; 139 | ``` 140 | 141 | So this is more like a 2D version of `std::vector` 142 | 143 | --- 144 | # Convenience typedefs 145 | ```C++ 146 | MatrixNt = Matrix 147 | MatrixXNt = Matrix 148 | MatrixNXt = Matrix 149 | VectorNt = Matrix 150 | RowVectorNt = Matrix 151 | ``` 152 | ``` 153 | N = {2, 3, 4, X = dynamic} 154 | t = {i = int, f = float, d = double, cf = complex, cd = complex} 155 | ``` 156 | e.g.: 157 | - `Matrix3d` = 3x3 double matrix 158 | - `Matrix3i` = 3x3 int matrix 159 | - `MatrixXd` = (Dynamic)x(Dynamic) double matrix 160 | - `VectorXd` = (Dynamic) double vector 161 | 162 | --- 163 | # Matrix arithmetics 164 | 165 | ```C++ 166 | Eigen::MatrixXd A(5, 10); 167 | Eigen::MatrixXd B(10, 2); 168 | Eigen::VectorXd vec(10); 169 | 170 | Eigen::MatrixXd C = A * B; 171 | Eigen::VectorXd w = A * vec; 172 | ``` 173 | 174 | More: 175 | 176 | Dot product: `v.dot(w)` 177 | 178 | Cross product: `v.cross(w)` 179 | 180 | Transpose: `A.transpose()` 181 | 182 | Set constant: 183 | `A.setZeros(rows, cols)`, `A.setOnes(rows, cols)`, `A.setConstant(rows, cols, value)`, `A.setRandom(rows, cols)` 184 | 185 | --- 186 | # Element-wise ops with `Array`s 187 | 188 | For example: 189 | ```C++ 190 | Eigen::Matrix3d A, B; 191 | // set all values to 2.0 192 | A.array() = 2.0; 193 | 194 | // set each element of A to sin() of the same element in B 195 | A.array() = B.array().sin(); 196 | 197 | Eigen::Array3d W; 198 | W = W * A; // Error - cannot mix Array with Matrix 199 | ``` 200 | 201 | --- 202 | # View other data as a `Matrix` or `Array` 203 | 204 | Mix and match with `std::vector` or any contiguous layout 205 | 206 | It is easy to “overlay” existing memory with an Eigen `Array` or `Matrix`: 207 | 208 | ```C++ 209 | std::vector a(1000); 210 | 211 | Eigen::Map> a_eigen(a.data()); 212 | 213 | a_eigen(10, 0) = 1.0; 214 | 215 | Eigen::Map a2_eigen(a.data(), 10, 100); 216 | ``` 217 | --- 218 | # Sparse matrix: Basic 219 | 220 | When dealing with very large matrices with many zeros (e.g. from Differential Equations), store as sparse matrices 221 | 222 | ```C++ 223 | #include 224 | 225 | SparseMatrix > mat(1000,2000); 226 | // declares a 1000x2000 column-major compressed sparse matrix of complex 227 | 228 | SparseMatrix mat(1000,2000); 229 | // declares a 1000x2000 row-major compressed sparse matrix of double 230 | 231 | SparseVector > vec(1000); 232 | // declares a column sparse vector of complex of size 1000 233 | 234 | SparseVector vec(1000); 235 | // declares a row sparse vector of double of size 1000 236 | ``` 237 | 238 | --- 239 | # Sparse matrix 240 | Simplest way to create a sparse matrix is to build a triplet: 241 | ```C++ 242 | typedef Triplet T; 243 | std::vector tripletList; 244 | tripletList.reserve(estimation_of_entries); 245 | for(...) 246 | { 247 | // ... 248 | tripletList.push_back(T(i,j,v_ij)); 249 | } 250 | SparseMatrixType m(rows,cols); 251 | m.setFromTriplets(tripletList.begin(), tripletList.end()); 252 | // m is ready to go! 253 | ``` 254 | Operators: 255 | - Sparse: `SM.transpose()`, `SM.adjoint()`… 256 | - Sparse-Sparse: `SM1 + SM2`, `SM1 *SM2` … 257 | - Sparse-Dense: `DM2 = DM1 + SM1` , `DM2 = SM1* DM2` 258 | 259 | --- 260 | # Matrix: Geometry 261 | 262 | ```C++ 263 | #include 264 | ``` 265 | - 2D Rotations: 266 | ```C++ 267 | Rotation2D rot2(angle_in_radian) 268 | ``` 269 | - 3D Rotaions: 270 | ```C++ 271 | AngleAxis AA(angle_in_radian, Vector3f(ax,ay,az)) 272 | ``` 273 | - Quarternion, ND-Transformations… 274 | 275 | --- 276 | # Solvers 277 | 278 | Simple example: `\(Ax = b\)` 279 | ```C++ 280 | #include 281 | #include 282 | 283 | int main() 284 | { 285 | Eigen::Matrix3f A; 286 | Eigen::Vector3f b; 287 | A << 1,2,3, 4,5,6, 7,8,10; 288 | b << 3, 3, 4; 289 | std::cout << "Here is the matrix A:\n" << A << std::endl; 290 | std::cout << "Here is the vector b:\n" << b << std::endl; 291 | Eigen::Vector3f x = A.colPivHouseholderQr().solve(b); #HERE 292 | std::cout << "The solution is:\n" << x << std::endl; 293 | } 294 | ``` 295 | 2 steps: 296 | - Decompose 297 | - Solve 298 | 299 | --- 300 | # Decompositions: 301 | 302 | E.g. 303 | - PartialPivLU 304 | 305 | - FullPivLU 306 | 307 | - HouseholderQR 308 | 309 | - ColPivHousehoulderQR 310 | 311 | Varying matrix requirements, speed, reliability, accuracy, optimization 312 | 313 | 314 | 315 | 316 | --- 317 | # Other solvers 318 | ### Singular values 319 | E.g. JacobiSVD, BDCSVD 320 | 321 | ### Eigenvalues/vectors 322 | E.g. SelfAdjointEigenSolver, ComplexEigenSolver 323 | 324 | ### Sparse Solver 325 | E.g. SparseLU, SparseQR, SimplicialLLT, SimplicialLDLT 326 | 327 | --- 328 | 329 | # Iterative Solvers 330 | 331 | ```C++ 332 | #include 333 | ``` 334 | Useful for solving `\(Ax = b\)` where `\(A\)` is large and sparse 335 | 336 | E.g. 337 | - ConjugateGradient 338 | 339 | - LeastSquaresConjugateGradient 340 | 341 | - BICGSTAB 342 | 343 | Use with preconditioner 344 | 345 | --- 346 | # General Notes: 347 | Eigen does a lot of checks in debug mode: 348 | 349 | - Turn optimisation on: `-O2` etc 350 | 351 | - Turn off debug: `-DNDEBUG` 352 | 353 | Can use external BLAS/LAPACK library (e.g. MKL, OpenBLAS) by enabling macro 354 | 355 | - e.g. `EIGEN_USE_BLAS` 356 | --- 357 | # Demo: diffusion equation 358 | 359 | Solve diffusion equation 360 | 361 | $$\frac{\partial T}{\partial t} = k \frac{\partial^2 T}{\partial x^2}$$ 362 | 363 | in 1D using an explicit method 364 | 365 | Each timestep can be iterated by using `\(T_{n+1} = A T_n\)` 366 | 367 | 1. Create an initial vector for `\(T\)` 368 | 369 | 2. Create a dense matrix for `\(A\)` 370 | 371 | 3. Matrix multiply `\(A\)` several times 372 | 373 | 374 | See `exercises/eigen/explicit.cpp` 375 | 376 | --- 377 | # Diffusion equation (explicit) 378 | 379 | Subscript means time, square brackets position 380 | 381 | `$$T_{n+1}[i] = T_n[i] + \frac{k \Delta t}{\Delta x^2}(T_n[i-1] - 2T_n[i] + T_n[i+1])$$` 382 | 383 | Our matrix equation is now: 384 | 385 | `$$T_{n+1} = A.T_{n}$$` 386 | 387 | Left-hand side is unknown (next time step) 388 | 389 | Let: `\(\delta = k\Delta t/ \Delta x^2\)` 390 | 391 | ``` 392 | A = [ 1-ẟ ẟ 0 0 0 … ] 393 | [ ẟ 1-2ẟ ẟ 0 0 … ] 394 | [ 0 ẟ 1-2ẟ ẟ 0 … ] 395 | [ 0 0 ẟ 1-2ẟ ẟ 0 0 …] 396 | [ 0 0 0 ẟ 1-2ẟ ẟ 0 …] 397 | 398 | ``` 399 | --- 400 | # Code: 401 | 402 | ```C++ 403 | int n = 20; 404 | int steps = 200; 405 | std::vector Avec(n * n); 406 | ``` 407 | ```C++ 408 | // Set up matrix A 409 | Eigen::Map A(Avec.data(), n, n); 410 | A = Eigen::MatrixXd::Identity(n, n); 411 | double delta = 0.4; 412 | for (int i = 0; i < n - 1; ++i) 413 | { 414 | A(i + 1, i) += delta; 415 | A(i + 1, i + 1) += -delta; 416 | 417 | A(i, i) += -delta; 418 | A(i, i + 1) += +delta; 419 | } 420 | ``` 421 | 422 | ```C++ 423 | // T_n 424 | Eigen::VectorXd b(n); 425 | b.setZero(); 426 | b.head(n / 2).array() = 1.0; 427 | ``` 428 | --- 429 | # Diffusion equation (implicit) 430 | More stable: 431 | 432 | Subscript means time, square brackets position 433 | 434 | `$$T_{n+1}[i] - \frac{k \Delta t}{\Delta x^2} (T_{n+1}[i-1] - 2*T_{n+1}[i] + T_{n+1}[i+1]) = T[i]$$` 435 | 436 | Left-hand side is unknown (next time step) 437 | `$$A.T_{n+1} = T_n$$` 438 | 439 | ``` 440 | A = [ 1+ẟ -ẟ 0 0 0 …] 441 | [ -ẟ 1+2ẟ -ẟ 0 0 …] 442 | [ 0 -ẟ 1+2ẟ -ẟ 0 …] 443 | [ 0 0 -ẟ 1+2ẟ -ẟ …] 444 | [ 0 0 0 -ẟ 1+2ẟ …] 445 | 446 | ``` 447 | The matrix A is very similar - just flip the sign of the delta terms 448 | 449 | --- 450 | # Exercise: Diffusion equation (sparse) 451 | 452 | There is also a way to implement this example using the sparse matrix interface in eigen. 453 | 454 | Some changes to look out for in the `sparse.cpp` example: 455 | ```C++ 456 | #include 457 | ``` 458 | ```C++ 459 | std::vector> fill; 460 | fill.reserve(...); 461 | ``` 462 | ```C++ 463 | for (int i = 0; i < n - 1; ++i) 464 | { 465 | fill.push_back(Eigen::Triplet(...); 466 | ... 467 | } 468 | ``` 469 | ```C++ 470 | A.setFromTriplets(fill.begin(), fill.end()); 471 | ``` 472 | 473 | See `exercises/eigen/sparse.cpp` 474 | 475 | --- 476 | # Exercise: Diffusion equation 3 ways 477 | 478 | - Use `modules.sh` to load correct environment on ARCHER2 479 | 480 | - Compile the examples using `make` 481 | 482 | - Run each of the three examples explicit, implicit and sparse 483 | 484 | - Generate the movie using the provided python script 485 | 486 | To view the movie you will need to either: 487 | - Download the data and generate it locally 488 | 489 | or 490 | 491 | - Set up a python virtual environment on ARCHER2 with matplotlib 492 | - User `ssh -X` to view graphics -------------------------------------------------------------------------------- /lectures/9-eigen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Linear Algebra for C++ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lectures/README.md: -------------------------------------------------------------------------------- 1 | # Lectures 2 | 3 | * [Course introduction (2 days)](0-course-intro-2-days) 4 | * [Course introduction (3 days)](0-course-intro-3-days) 5 | * [A brief introduction to C++](1-cpp-intro) 6 | * [Class types](2-classes) 7 | * [Loops, containers, and iterators](3-loops-containers) 8 | * [Managing resources](4-resources) 9 | * [RAII](6.1-RAII) 10 | * [Templates & traits for generic programming](5-templates) 11 | * [RAII - Smart pointers](6.2-RAII-smart-pointers) 12 | * [Combining classes](7-combining-classes) 13 | * [Algorithms and lambdas](8-algorithms-lambdas) 14 | * [Linear algebra with Eigen](9-eigen) 15 | * [Threads with C++](10.1-threads) and [Further topics with threads](10.2-threads-cont) 16 | -------------------------------------------------------------------------------- /lectures/template/cpptheme.js: -------------------------------------------------------------------------------- 1 | import {epcc, Theme} from "https://EPCCed.github.io/remark_theme/latest.js"; 2 | 3 | var footer = new URL(import.meta.url).searchParams.get("footer"); 4 | 5 | if (!footer) { 6 | footer = "© Rupert Nash, The University of Edinburgh, CC-BY"; 7 | } 8 | epcc.footer_text = footer; 9 | 10 | var cpptheme = new Theme( 11 | (str => str.substring(0, str.lastIndexOf("/")))(import.meta.url), 12 | '$BASEURL/style.css', 13 | { 14 | thumb: function () { 15 | return '.thumb[\n.thumbtxt[\n' + this +'\n]\n]'; 16 | } 17 | } 18 | ); 19 | 20 | epcc.install(); 21 | cpptheme.install(); 22 | globalThis.slideshow = remark.create({sourceUrl: 'README.md'}); 23 | -------------------------------------------------------------------------------- /lectures/template/mathjax-setup.js: -------------------------------------------------------------------------------- 1 | // Setup MathJax 2 | MathJax.Hub.Config({ 3 | tex2jax: { 4 | skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'] 5 | } 6 | }); 7 | 8 | MathJax.Hub.Configured(); -------------------------------------------------------------------------------- /lectures/template/style.css: -------------------------------------------------------------------------------- 1 | div.thumb { 2 | padding: 0.5em; 3 | margin: 0.25em; 4 | border-style: solid; 5 | border-color: rgb(0,50,95); 6 | display: flex; 7 | align-items: center; 8 | } 9 | 10 | div.thumb::before { 11 | content: url(thumbs_up.png); 12 | } 13 | 14 | blockquote { 15 | padding: 0.5em; 16 | background-color: #F0F0F0; 17 | } 18 | 19 | .smaller { 20 | font-size: smaller; 21 | } 22 | -------------------------------------------------------------------------------- /lectures/template/thumbs_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPCCed/archer2-cpp/4328427811eb74a1b315b9f758b955c39b3151d3/lectures/template/thumbs_up.png --------------------------------------------------------------------------------