diff --git a/notebooks/02_Pointers+References.ipynb b/notebooks/02_Pointers+References.ipynb
index 4a045bf79217548ae0cfc9249793af3fa7fe72a9..03f67b6dd245afa44aa6993ef87e23eebd052392 100644
--- a/notebooks/02_Pointers+References.ipynb
+++ b/notebooks/02_Pointers+References.ipynb
@@ -1 +1 @@
-{"metadata":{"orig_nbformat":4,"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"17"},"kernelspec":{"name":"xcpp17","display_name":"C++17","language":"C++17"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# References","metadata":{},"id":"eb6a84a9-1411-4a9b-8cbb-c7ed29ecb9c0"},{"cell_type":"code","source":"#include <iostream>\n#include <memory>","metadata":{"trusted":true},"execution_count":1,"outputs":[],"id":"40de00aa-5b48-420d-8743-099cef5d316a"},{"cell_type":"code","source":"void check() {\n    int iVal = 1;\n    int* ptr2int = &iVal;\n    int** ptr2ptr = &ptr2int;\n    int& ref2int = iVal;\n    // int&& ref2ref = ref2int; // this will not work\n    \n    std::cout << \"iVal stores a value of ................. \" << iVal << '\\n';\n    std::cout << \"iVal resides in memory at address ...... \" << std::addressof( iVal ) << \"\\n\\n\";\n\n    std::cout << \"ptr2int stores a value of .............. << \" << ptr2int << '\\n';\n    std::cout << \"ptr2int resides in memory at address ... << \" << &ptr2int << \"\\n\\n\";\n\n    std::cout << \"ptr2ptr stores a value of .............. << \" << ptr2ptr << '\\n';\n    std::cout << \"ptr2ptr resides in memory at address ... << \" << &ptr2ptr << \"\\n\\n\";\n\n    std::cout << \"ref2int stores a value of .............. << \" << ref2int << '\\n';\n    std::cout << \"ref2int resides in memory at address ... << \" << &ref2int << std::endl;\n}","metadata":{"trusted":true},"execution_count":2,"outputs":[],"id":"0579d3c2-3f9d-4904-8762-f1d6cdb2581d"},{"cell_type":"code","source":"check()","metadata":{"trusted":true},"execution_count":3,"outputs":[{"name":"stdout","text":"iVal stores a value of ................. 1\niVal resides in memory at address ...... 0x7fffea96f2ac\n\nptr2int stores a value of .............. << 0x7fffea96f2ac\nptr2int resides in memory at address ... << 0x7fffea96f2a0\n\nptr2ptr stores a value of .............. << 0x7fffea96f2a0\nptr2ptr resides in memory at address ... << 0x7fffea96f298\n\nref2int stores a value of .............. << 1\nref2int resides in memory at address ... << 0x7fffea96f2ac\n","output_type":"stream"}],"id":"3a7034da-2d7f-4429-9853-ffd5e62eefcb"},{"cell_type":"markdown","source":"The last two lines clearly show the difference between a pointer and a reference.\nWhile the pointer is a variable with its own memory location, the reference is only an alias of sorts. It occupies the same place in memory and stores the same value as the object it is bound/initialised to.","metadata":{},"id":"410de220-647d-48f0-93e5-1789d2a72300"},{"cell_type":"markdown","source":"# Dynamic Object Creation","metadata":{},"id":"7fd67f39-e7cf-473b-a316-9d093b97dc7c"},{"cell_type":"markdown","source":" - Often we will only find out how large a problem is during execution of our program.\n - Imagine e.g. that you want to run a Finite Element simulation. Typically you will use a mesh generated by an external meshing program for that. Thus, you cannot say apriorily how much memory space you will need to store the triangles/tetrahedra of that mesh.\n\n - Thus, we need a way to allocate memory at run-time, i.e. during execution of our program.\n - C++ provides the **new expression** for this.","metadata":{},"id":"ff9a0e59-ce0c-4e45-a6fd-55fe94f6953e"},{"cell_type":"code","source":"// Let's check whether this works?\nint main() {\n  int a = new int;\n  a = 2;\n  std::cout << \"a stores \" << a << std::endl;\n}\n\nmain();","metadata":{"trusted":true},"execution_count":4,"outputs":[{"name":"stderr","text":"input_line_10:3:7: error: cannot initialize a variable of type 'int' with an rvalue of type 'int *'\n  int a = new int;\n      ^   ~~~~~~~\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"8adf9d0f-35a5-4c01-b2b1-6ed24733ba00"},{"cell_type":"code","source":"#include <iostream>\n\n// Corrected version\nint main() {\n  int* a = new int;\n  *a = 2;\n  std::cout << \"a stores \" << a << std::endl;\n  std::cout << \"*a stores \" << *a << std::endl;\n    \n  // let's check whether new initialises?\n  double* dPtr = new double;\n    std::cout << \"*dPtr = \" << *dPtr << std::endl;\n     short* sPtr = new short;\n    std::cout << \"*sPtr = \" << *sPtr << std::endl;\n    \n  // no, it does not (at least not for PODs)\n}\n\nmain();","metadata":{"trusted":true},"execution_count":1,"outputs":[{"name":"stdout","text":"a stores 0x55c31b5d3ff0\n*a stores 2\n*dPtr = 4.65886e-310\n*sPtr = 11024\n","output_type":"stream"}],"id":"b6955ae1-ec49-4ddc-8dc6-53d459bd6a4e"},{"cell_type":"code","source":"// Now let's try to dynamically allocate an array\ndouble* array = new double[10];\n\nfor( int k = 0; k < 10; k++ ) {\n    std::cout << \"array[\" << k << \"] = \" << array[k] << '\\n';\n}","metadata":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"array[0] = 4.65886e-310\narray[1] = 0\narray[2] = 0\narray[3] = 0\narray[4] = 4.65886e-310\narray[5] = 0\narray[6] = 0\narray[7] = 4.65886e-310\narray[8] = 4.65886e-310\narray[9] = 4.65886e-310\n","output_type":"stream"}],"id":"cdb7efd5-0d40-4278-a19a-17a6d84e6dfd"},{"cell_type":"markdown","source":"### Deletion and Memory Leaks\n - When we dynamically allocate something, we should also take care to delete it, once we no longer need it\n - Otherwise we might generate a **memory leak**\n - Same happens, if we loose the address of the allocated object","metadata":{},"id":"0dc4e4be-11c2-4039-a9de-4ac522969417"},{"cell_type":"code","source":"#include <iostream>\n#include <new>      // for std::nothrow\n                    // but also comes in indirectly via iostream\n#include <cstdlib>  // for exit(), EXIT_FAILURE\n#include <limits>   // for numeric_limits\n\ndouble* getMem( unsigned long long n ) {\n\n    double* dPtr = nullptr;\n\n    // nothrow avoid throwing an exception if allocation fails;\n    // instead a nullptr is returned\n    dPtr = new( std::nothrow ) double[n];  // \n\n    // check, if that went smoothly\n    if( dPtr != nullptr ) {\n      std::cout << \"Allocated \" << sizeof(double)*n\n                << \" bytes at address \" << dPtr << std::endl;\n    }\n    else {\n      std::cout << \"\\n *** Problem with memory allocation ***\" << std::endl;\n      std::exit( EXIT_FAILURE );\n    }\n\n    // hand address back\n    return dPtr;\n\n} // (1) dPtr goes out of scope now, but dynamic memory is not deleted!","metadata":{"trusted":true},"execution_count":3,"outputs":[],"id":"2a70c970-aef1-4fe3-a953-6c37fe86438d"},{"cell_type":"code","source":"int main() {\n\n  double* arr = getMem( 100 );\n  std::cout << \"arr starts at \" << arr << std::endl;\n\n  // (1) address is still valid\n  arr[0] = 1.0;\n  delete[] arr;  // delete memory block again, need [] because it's an array\n\n  // ask for too much\n  arr = getMem( std::numeric_limits<unsigned long long>::max() );\n\n}\n\nmain();","metadata":{"trusted":true},"execution_count":null,"outputs":[{"name":"stdout","text":"Allocated 800 bytes at address 0x55c319454d30\narr starts at 0x55c319454d30\n\n *** Problem with memory allocation ***\n","output_type":"stream"}],"id":"e3a66e21-f10f-4a49-8663-c47cde6ca315"},{"cell_type":"code","source":"#include <iostream>\n\nint main() {\n    int* iPtr = nullptr;\n    \n    for( unsigned int k = 1; k <= 5; ++k ) {\n        iPtr = new int[10];\n        std::cout << \"40 byte block allocated at address \" << iPtr << std::endl;\n    }\n    \n    std::cout << \"deleting memory block with starting address \" << iPtr << std::endl;\n    delete[] iPtr;\n}\n\nmain();","metadata":{"trusted":true},"execution_count":1,"outputs":[{"name":"stdout","text":"40 byte block allocated at address 0x5621091b4c70\n40 byte block allocated at address 0x562108fb6400\n40 byte block allocated at address 0x56210913a370\n40 byte block allocated at address 0x562109439530\n40 byte block allocated at address 0x562108f09610\ndeleting memory block with starting address 0x562108f09610\n","output_type":"stream"}],"id":"b2c0e344-1663-444d-898b-162f78af0ca2"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"b9883aff-e19f-4a66-9177-da60faa124ec"}]}
\ No newline at end of file
+{"metadata":{"orig_nbformat":4,"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"17"},"kernelspec":{"name":"xcpp17","display_name":"C++17","language":"C++17"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# References","metadata":{},"id":"eb6a84a9-1411-4a9b-8cbb-c7ed29ecb9c0"},{"cell_type":"code","source":"#include <iostream>\n#include <memory>","metadata":{},"execution_count":1,"outputs":[],"id":"40de00aa-5b48-420d-8743-099cef5d316a"},{"cell_type":"code","source":"void check() {\n    int iVal = 1;\n    int* ptr2int = &iVal;\n    int** ptr2ptr = &ptr2int;\n    int& ref2int = iVal;\n    // int&& ref2ref = ref2int; // this will not work\n    \n    std::cout << \"iVal stores a value of ................. \" << iVal << '\\n';\n    std::cout << \"iVal resides in memory at address ...... \" << std::addressof( iVal ) << \"\\n\\n\";\n\n    std::cout << \"ptr2int stores a value of .............. << \" << ptr2int << '\\n';\n    std::cout << \"ptr2int resides in memory at address ... << \" << &ptr2int << \"\\n\\n\";\n\n    std::cout << \"ptr2ptr stores a value of .............. << \" << ptr2ptr << '\\n';\n    std::cout << \"ptr2ptr resides in memory at address ... << \" << &ptr2ptr << \"\\n\\n\";\n\n    std::cout << \"ref2int stores a value of .............. << \" << ref2int << '\\n';\n    std::cout << \"ref2int resides in memory at address ... << \" << &ref2int << std::endl;\n}","metadata":{},"execution_count":2,"outputs":[],"id":"0579d3c2-3f9d-4904-8762-f1d6cdb2581d"},{"cell_type":"code","source":"check()","metadata":{},"execution_count":3,"outputs":[{"name":"stdout","text":"iVal stores a value of ................. 1\niVal resides in memory at address ...... 0x7fffea96f2ac\n\nptr2int stores a value of .............. << 0x7fffea96f2ac\nptr2int resides in memory at address ... << 0x7fffea96f2a0\n\nptr2ptr stores a value of .............. << 0x7fffea96f2a0\nptr2ptr resides in memory at address ... << 0x7fffea96f298\n\nref2int stores a value of .............. << 1\nref2int resides in memory at address ... << 0x7fffea96f2ac\n","output_type":"stream"}],"id":"3a7034da-2d7f-4429-9853-ffd5e62eefcb"},{"cell_type":"markdown","source":"The last two lines clearly show the difference between a pointer and a reference.\nWhile the pointer is a variable with its own memory location, the reference is only an alias of sorts. It occupies the same place in memory and stores the same value as the object it is bound/initialised to.","metadata":{},"id":"410de220-647d-48f0-93e5-1789d2a72300"},{"cell_type":"markdown","source":"# Dynamic Object Creation","metadata":{},"id":"7fd67f39-e7cf-473b-a316-9d093b97dc7c"},{"cell_type":"markdown","source":" - Often we will only find out how large a problem is during execution of our program.\n - Imagine e.g. that you want to run a Finite Element simulation. Typically you will use a mesh generated by an external meshing program for that. Thus, you cannot say apriorily how much memory space you will need to store the triangles/tetrahedra of that mesh.\n\n - Thus, we need a way to allocate memory at run-time, i.e. during execution of our program.\n - C++ provides the **new expression** for this.","metadata":{},"id":"ff9a0e59-ce0c-4e45-a6fd-55fe94f6953e"},{"cell_type":"code","source":"// Let's check whether this works?\nint main() {\n  int a = new int;\n  a = 2;\n  std::cout << \"a stores \" << a << std::endl;\n}\n\nmain();","metadata":{},"execution_count":4,"outputs":[{"name":"stderr","text":"input_line_10:3:7: error: cannot initialize a variable of type 'int' with an rvalue of type 'int *'\n  int a = new int;\n      ^   ~~~~~~~\n","output_type":"stream"},{"ename":"Interpreter Error","evalue":"","traceback":["Interpreter Error: "],"output_type":"error"}],"id":"8adf9d0f-35a5-4c01-b2b1-6ed24733ba00"},{"cell_type":"code","source":"#include <iostream>\n\n// Corrected version\nint main() {\n  int* a = new int;\n  *a = 2;\n  std::cout << \"a stores \" << a << std::endl;\n  std::cout << \"*a stores \" << *a << std::endl;\n    \n  // let's check whether new initialises?\n  double* dPtr = new double;\n    std::cout << \"*dPtr = \" << *dPtr << std::endl;\n     short* sPtr = new short;\n    std::cout << \"*sPtr = \" << *sPtr << std::endl;\n    \n  // no, it does not (at least not for PODs)\n}\n\nmain();","metadata":{},"execution_count":1,"outputs":[{"name":"stdout","text":"a stores 0x55c31b5d3ff0\n*a stores 2\n*dPtr = 4.65886e-310\n*sPtr = 11024\n","output_type":"stream"}],"id":"b6955ae1-ec49-4ddc-8dc6-53d459bd6a4e"},{"cell_type":"code","source":"// Now let's try to dynamically allocate an array\ndouble* array = new double[10];\n\nfor( int k = 0; k < 10; k++ ) {\n    std::cout << \"array[\" << k << \"] = \" << array[k] << '\\n';\n}","metadata":{},"execution_count":2,"outputs":[{"name":"stdout","text":"array[0] = 4.65886e-310\narray[1] = 0\narray[2] = 0\narray[3] = 0\narray[4] = 4.65886e-310\narray[5] = 0\narray[6] = 0\narray[7] = 4.65886e-310\narray[8] = 4.65886e-310\narray[9] = 4.65886e-310\n","output_type":"stream"}],"id":"cdb7efd5-0d40-4278-a19a-17a6d84e6dfd"},{"cell_type":"markdown","source":"### Deletion and Memory Leaks\n - When we dynamically allocate something, we should also take care to delete it, once we no longer need it\n - Otherwise we might generate a **memory leak**\n - Same happens, if we loose the address of the allocated object","metadata":{},"id":"0dc4e4be-11c2-4039-a9de-4ac522969417"},{"cell_type":"code","source":"#include <iostream>\n#include <new>      // for std::nothrow, also comes in indirectly via iostream\n#include <cstdlib>  // for exit(), EXIT_FAILURE\n#include <limits>   // for numeric_limits\n\ndouble* getMem( unsigned long long n ) {\n\n    double* dPtr = nullptr;\n\n    // nothrow avoid throwing an exception if allocation fails;\n    // instead a nullptr is returned\n    dPtr = new( std::nothrow ) double[n];\n\n    // check, if that went smoothly\n    if( dPtr != nullptr ) {\n      std::cout << \"Allocated \" << sizeof(double)*n\n                << \" bytes at address \" << dPtr << std::endl;\n    }\n    else {\n      std::cout << \"\\n *** Problem with memory allocation ***\" << std::endl;\n      std::exit( EXIT_FAILURE );\n    }\n\n    // hand address back\n    return dPtr;\n\n} // (1) dPtr goes out of scope now, but dynamic memory is not deleted!","metadata":{"trusted":true},"execution_count":1,"outputs":[],"id":"2a70c970-aef1-4fe3-a953-6c37fe86438d"},{"cell_type":"code","source":"int main() {\n\n  double* arr = getMem( 100 );\n  std::cout << \"arr starts at \" << arr << std::endl;\n\n  // (1) address is still valid\n  arr[0] = 1.0;\n  delete[] arr;  // delete memory block again, need [] because it's an array\n\n  // ask for too much\n  arr = getMem( std::numeric_limits<unsigned long long>::max() );\n\n}\n\nmain();","metadata":{"trusted":true},"execution_count":null,"outputs":[{"name":"stdout","text":"Allocated 800 bytes at address 0x5603a02f5d60\narr starts at 0x5603a02f5d60\n\n *** Problem with memory allocation ***\n","output_type":"stream"}],"id":"e3a66e21-f10f-4a49-8663-c47cde6ca315"},{"cell_type":"code","source":"#include <iostream>\n\nint main() {\n    int* iPtr = nullptr;\n    \n    for( unsigned int k = 1; k <= 5; ++k ) {\n        iPtr = new int[10];\n        std::cout << \"40 byte block allocated at address \" << iPtr << std::endl;\n    }\n    \n    std::cout << \"deleting memory block with starting address \" << iPtr << std::endl;\n    delete[] iPtr;\n}\n\nmain();","metadata":{"trusted":true},"execution_count":1,"outputs":[{"name":"stdout","text":"40 byte block allocated at address 0x56501213e090\n40 byte block allocated at address 0x5650147437c0\n40 byte block allocated at address 0x56501472e010\n40 byte block allocated at address 0x565014488a70\n40 byte block allocated at address 0x5650140e0fe0\ndeleting memory block with starting address 0x5650140e0fe0\n","output_type":"stream"}],"id":"b2c0e344-1663-444d-898b-162f78af0ca2"},{"cell_type":"markdown","source":"In the above example we have unrecoverably **lost** 4 * 40 = 160 bytes of memory","metadata":{},"id":"63030691-3c76-4ad8-8ca8-450eade595a6"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"69f6ee2d-5622-47bf-a92c-ae4866103428"}]}
\ No newline at end of file