From 3b48fd5cb21d15b4e636832034f511e2a881bf9d Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 8 Jun 2026 17:11:47 -0400 Subject: [PATCH 1/2] fix: correct errors and add shell chapter from course review Multi-agent review of all course material (accuracy, pedagogy, prose, code) with adversarial verification of every technical/code finding. Applies 171 confirmed fixes across 46 files. Highlights: - Code bugs that broke examples: pytest fixture syntax (@pytest.fixture("platform", ...) -> params=...), MyList.append infinite recursion, missing @dataclasses.dataclass decorators, missing type annotations/imports, undefined vars in Matlab/JS exists() examples. - Conceptual fixes: asyncio.to_thread runs sync (not async) code; corrected C++ move-assignment to steal the pointer instead of recursing; __neq__ -> __ne__; parameter() -> perimeter() (incl. mermaid diagram). - Currency: recommend Python 3.12+ (3.9 is EOL); git output master -> main; pipx links (pypy -> pypa typo, pypa.github.io/pipx -> pipx.pypa.io). - Filled the empty week13 shell.md stub with a full practical chapter. - ~95 prose/typo fixes. Assisted-by: ClaudeCode:claude-opus-4.8 --- content/intro.md | 4 +- content/week01_intro/cleanup_bessel.ipynb | 16 +- content/week01_intro/intro.md | 2 +- content/week01_intro/practices.md | 8 +- content/week01_intro/programming_basics.ipynb | 6 +- content/week01_intro/python_setup.md | 27 +-- content/week01_intro/setup.md | 14 +- content/week01_intro/vcs.md | 17 +- content/week02_testing/debugging.md | 6 +- content/week02_testing/pytest.md | 28 +-- content/week02_testing/tdd.md | 6 +- content/week02_testing/testing.md | 6 +- content/week03_git/advanced_steps.md | 6 +- content/week03_git/first_steps.md | 27 +-- content/week03_git/intro.md | 6 +- content/week04_package/packaging.md | 4 +- content/week04_package/precommit.md | 8 +- content/week04_package/task_runners.md | 5 +- content/week04_package/using_packages.md | 10 +- content/week05_ci/cd.md | 7 +- content/week05_ci/ci.md | 6 +- content/week05_ci/docs.md | 26 ++- .../geom_example/geometry/classic.py | 6 +- .../geom_example/tests/test_classic.py | 4 +- content/week06_oop/introoo.md | 18 +- content/week06_oop/oodesign.md | 19 +- content/week07_design/designpatt.md | 20 +- content/week07_design/functional.md | 14 +- content/week08_static_typing/typing.md | 41 ++-- .../week09_compiled/Compiled_Languages.ipynb | 8 +- content/week09_compiled/debugging.md | 16 +- content/week09_compiled/profiling.md | 6 + .../week09_compiled/rust_example/compiled.md | 17 +- content/week09_compiled/rust_example/rust.md | 28 +-- .../01-shared-objects/01-shared-objects.ipynb | 11 +- .../02-cpython/02-cpython.ipynb | 12 +- .../week10_binding/03-pybind/03a-pybind.ipynb | 10 +- .../week10_binding/03-pybind/03b-pybind.ipynb | 6 +- content/week10_binding/06-rust.md | 8 +- content/week11_omp/concepts.md | 18 +- content/week11_omp/intro.md | 4 +- content/week11_omp/piexample/procexec.py | 2 +- content/week11_omp/piexample/threadexec.py | 2 +- content/week11_omp/threading.md | 13 +- content/week13_misc/shell.md | 196 ++++++++++++++++++ content/week13_misc/webassembly.md | 8 +- 46 files changed, 484 insertions(+), 248 deletions(-) diff --git a/content/intro.md b/content/intro.md index c3566c3..6ec7be3 100644 --- a/content/intro.md +++ b/content/intro.md @@ -18,8 +18,8 @@ group project. [WebAssembly version](https://se-for-sci.github.io/live) • [Binder version](https://mybinder.org/v2/gh/se-for-sci/se-for-sci.github.io/main?urlpath=lab) -Note the WebAssembly version does not have a shell, and `time.sleep` doesn't -work (the web is async). +Note the WebAssembly version does not have a shell, and `time.sleep` returns +instantly instead of sleeping (the web is async). ```{tableofcontents} diff --git a/content/week01_intro/cleanup_bessel.ipynb b/content/week01_intro/cleanup_bessel.ipynb index ccba6e7..86fb3b1 100644 --- a/content/week01_intro/cleanup_bessel.ipynb +++ b/content/week01_intro/cleanup_bessel.ipynb @@ -87,7 +87,7 @@ "\n", "def down(x, order, start):\n", " \"\"\"\n", - " Method down, recurse downward.\n", + " Compute spherical Bessel function j_order(x) for scalar x using downward recursion.\n", " \"\"\"\n", " j = np.zeros((start + 2), dtype=float)\n", "\n", @@ -112,8 +112,10 @@ "x = np.arange(x_min, x_max, step)\n", "\n", "# Warning! red/green are bad colors to use, default colors are better\n", + "# Both plots use the same approach here; `down` takes a scalar, so we loop\n", + "# over x with a list comprehension.\n", "ax.plot(x, [down(x_i, 10, start) for x_i in x], \"r.\")\n", - "ax.plot(x, np.vectorize(down)(x, 1, start), \"g.\")\n", + "ax.plot(x, [down(x_i, 1, start) for x_i in x], \"g.\")\n", "\n", "plt.show()" ] @@ -150,7 +152,10 @@ "\n", "def down(x, order, start):\n", " \"\"\"\n", - " Method down, recurse downward.\n", + " Compute spherical Bessel function j_order(x) for array x using downward recursion.\n", + "\n", + " Parameters: x (ndarray), order (int), start (int).\n", + " Returns: ndarray of same shape as x.\n", " \"\"\"\n", " j = np.zeros((start + 2, len(x)), dtype=float)\n", "\n", @@ -174,8 +179,9 @@ "\n", "x = np.arange(x_min, x_max, step)\n", "\n", - "ax.plot(x, down(x, 10, 50), \"r.\", label=\"L=10\")\n", - "ax.plot(x, down(x, 1, 50), \"g.\", label=\"L=1\")\n", + "# Use the default (colorblind-friendly) color cycle instead of red/green\n", + "ax.plot(x, down(x, 10, 50), \".\", color=\"C0\", label=\"L=10\")\n", + "ax.plot(x, down(x, 1, 50), \".\", color=\"C1\", label=\"L=1\")\n", "ax.legend()\n", "\n", "plt.show()" diff --git a/content/week01_intro/intro.md b/content/week01_intro/intro.md index 329efce..1c11666 100644 --- a/content/week01_intro/intro.md +++ b/content/week01_intro/intro.md @@ -13,7 +13,7 @@ easy to read, always strive for readability. What does someone working on code spend time on? Rewriting code that others have already written. The action of rewriting code to improve its readability or performance without changing how it operates is commonly called refactoring. -Other reasons to rewrite code is because it didn't scale or was not flexible +Other reasons to rewrite code are because it didn't scale or was not flexible enough. It also can feel like you spend a lot of time debugging. Lots of painful debugging. Having strong unit tests and using version control can simplify or eliminate some debugging problems. diff --git a/content/week01_intro/practices.md b/content/week01_intro/practices.md index a5b6c42..cc72c71 100644 --- a/content/week01_intro/practices.md +++ b/content/week01_intro/practices.md @@ -77,7 +77,7 @@ follow them whenever possible. For example, naming in Python & C++: If you are in a language that uses a different convention (lowerCamelCase, also known as dromedaryCase, for example), follow what is used in that language. -For C and C++, you should loop from 0 to 1-len: +For C and C++, you should loop from 0 to n-1 (or equivalently, while i < n): ```cpp // Good @@ -157,11 +157,11 @@ This might do what you expect at first: ```python my_list = ["start"] -print(add_to_list(my_list)) +print(add_end_to_list(my_list)) ``` But check the contents of `my_list` afterwards. Even better, try running it with -the default argument (`add_to_list()`) and see what it returns. +the default argument (`add_end_to_list()`) and see what it returns. Due to the above, it's a convention in Python to never use a mutable structure (we'll discuss mutation in detail in a few weeks) like a list or a dict for an @@ -175,7 +175,7 @@ def add_end_to_list(x=()): If you do need to mutate arguments, it should be well documented and clear as possible from the function and argument names. Usually you should not return the -list +list in a mutating function: ```python def append_end_to_list(x=None): diff --git a/content/week01_intro/programming_basics.ipynb b/content/week01_intro/programming_basics.ipynb index 15b49e4..963b617 100644 --- a/content/week01_intro/programming_basics.ipynb +++ b/content/week01_intro/programming_basics.ipynb @@ -33,7 +33,7 @@ "\n", "Just to keep the computational model straight, we are using:\n", "\n", - "* Python 3.12 (3.11+ recommended), a interpreted programming language\n", + "* Python 3.12 (3.11+ recommended), an interpreted programming language\n", "* A REPL, which **R**eads a line, **E**valuates it, then **P**rints it (in a **L**oop)\n", " - `print` not needed to see the last un-captured value in a cell\n", "* IPython, which is an enhancement to make Python more **I**nteractive\n", @@ -619,7 +619,7 @@ "f = g(f)\n", "```\n", "\n", - "So `g` is a function that takes a function and (hopefully) returns a function, probably a very similar one since you are giving it the same name as the old `f`. In Python 2.5, we gained the ability to write this instead:\n", + "So `g` is a function that takes a function and (hopefully) returns a function, probably a very similar one since you are giving it the same name as the old `f`. In Python 2.4, we gained the ability to write this instead:\n", "\n", "```python\n", "@g\n", @@ -679,7 +679,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It's best to think of this as a \"modifier\" (or \"decorator\") for functions (and classes). Don't worry to much about how it works, and especially how to write them. My favorite is a \"decorator factory\", which is simply a function that returns a function that takes a function that returns a function! But in practice, it just looks like a decorator that takes arguments.\n", + "It's best to think of this as a \"modifier\" (or \"decorator\") for functions (and classes). Don't worry too much about how it works, and especially how to write them. My favorite is a \"decorator factory\", which is simply a function that returns a function that takes a function that returns a function! But in practice, it just looks like a decorator that takes arguments.\n", "\n", "Let's look at an example: `functools.lru_cache`:" ] diff --git a/content/week01_intro/python_setup.md b/content/week01_intro/python_setup.md index e0f3b9d..6fa6ef4 100644 --- a/content/week01_intro/python_setup.md +++ b/content/week01_intro/python_setup.md @@ -27,7 +27,7 @@ For Linux, if you use your system Python, make sure it is new enough, and never modify the base environment except though your package manager (generally true, but more so here). The system Python is really intended for use in other system packages, and is not intended for you to modify. Modern pip and modern systems -(like Ubuntu 24.04+) now work together to provide safegaurds for this. +(like Ubuntu 24.04+) now work together to provide safeguards for this. ### Virtual environments @@ -52,7 +52,7 @@ than one, your virtual environment should be at the root of your project with the name `.venv`. Never check it into git; it should be listed in your `.gitignore`. If you have the third-party package `virtualenv`, it's just faster and the pre-installed pip is updated more regularly than the Python standard -library allows - it has the same interface). +library allows (it has the same interface). (Notice I didn't mention `python3 -m ensurepip`? That's because you don't need it with a virtualenv, the virtualenv will come with pip installed, even if it's @@ -88,11 +88,11 @@ Virtual environments work great for projects, but what about applications that you find on PyPI that you want to use? There's a simple solution for this: pipx, which is pip's counterpart for "executables". When you run `pipx install `, pipx will create a managed virtual environment for -just that application, and only expose it's applications on the command line. So +just that application, and only expose its applications on the command line. So `pipx install twine` will allow you to run `twine` anywhere, but you will not be -able to `import twine`, since it really lives in it's own virtual environment. +able to `import twine`, since it really lives in its own virtual environment. -Even better, `pip run ` will combine the two steps of installing and +Even better, `pipx run ` will combine the two steps of installing and running an application into one command; pipx will install the app into a temporary virtual environment (reused if you rerun the same command less than a week later), and then run it. With `pipx run`, you never have to think about @@ -120,11 +120,11 @@ that name. The last common need is to run a series of commands in a specific environment. This can be your tests, your documentation, or various other tasks. The original -tool for this is `tox`, but due to it's custom configuration format, the +tool for this is `tox`, but due to its custom configuration format, the Python-based tool `nox` is recommended instead for newcomers as well as experienced users. -You write a `noxfile.py` with functions that represent the tasks you want to to +You write a `noxfile.py` with functions that represent the tasks you want to run. It looks something like this: ```python @@ -160,11 +160,12 @@ easier or faster. Here are some popular ones: - `poetry` - The first major attempt to make a modern package manager. It's become a bit too opinionated in some areas, like it is the only one to force - you to use it's build-backend, and is behind on following standards. + you to use its build-backend, and is behind on following standards. - `pdm`: A mostly drop-in replacement for poetry that is more flexible and follows standards better. It can also do things like install Python for you. - `hatch`: The only tool in this list that can do multiple environments properly - (uv might later), but also the only one to not have built-in locking yet. + (uv might support this later), but also the only one to not have built-in + locking yet. - `uv` - The most interesting new tool, it will be covered in depth below. Each tool has strengths and drawbacks. Before uv, the best tool for projects @@ -176,8 +177,8 @@ interesting entry, so let's cover that below. The team at astral-sh has been developing Rust-based tooling for Python. They introduced `uv`, which started out as a drop-in replacement for venv, quite -drop-in), and had many long-requested features added (to be fair, uv has has -more dedicated developer time than these other tools combined). Since launching, +drop-in, and had many long-requested features added (to be fair, uv has more +dedicated developer time than these other tools combined). Since launching, they've also replaced pipx, build, Python installers, and are starting to replace poetry/pdm. By targeting the stand alone tools first, it's easy to just use uv for whatever you want faster without fully committing to it like, for @@ -245,7 +246,7 @@ using anything besides uv and you aren't going to make SDists/wheels, you can leave it off. `"hatchling"` is a great, flexible, and extendable backend, but there is a built in backend as well (shown above) for very simple projects. If you use hatchling, make sure the import name matches the project name (or look -up it's configuration options if you have a good reason not to match the names, +up its configuration options if you have a good reason not to match the names, which you don't). The `[project]` table should always have `name` and `version`, as they are @@ -342,7 +343,7 @@ there are several installers like "miniforge" and "mambaforge" that are just conda or mamba with conda-forge set as the default channel; you can do this yourself with the base tools, and pixi already defaults to conda-forge. -Note that conda-forge has it's own compiler toolchain. You generally should not +Note that conda-forge has its own compiler toolchain. You generally should not be compiling code with conda-forge packages; if you have to, make sure you get the compiler toolchain from conda-forge as well. Wheels mostly work, but you lose the advantages of conda's shared libraries. If you are just using conda to diff --git a/content/week01_intro/setup.md b/content/week01_intro/setup.md index a512169..cf4651f 100644 --- a/content/week01_intro/setup.md +++ b/content/week01_intro/setup.md @@ -19,10 +19,10 @@ From this point on, we should all be on nearly the same page. ## Shell -The UNIX shell is Bash (or a Bashwards compatible shell like Zsh). We'll be -using that (though you might see the Fish shell occasionally, we will point out -any differences that matter). Fish is a nicer shell than Bash, but Bash is a -common default. +The UNIX shell is Bash (or a Bash-compatible shell like Zsh). We'll be using +that (though you might see the Fish shell occasionally, we will point out any +differences that matter). Fish is a nicer shell than Bash, but Bash is a common +default. ## Version control @@ -37,8 +37,8 @@ the course, you should set up a ssh key pair so we can push to GitHub. ## Python -For Python, we'll use a recent version of Python (3.11+ highly recommended, 3.9+ -is _probably_ okay). One way to get Python is via Conda (like anaconda, +For Python, we'll use a recent version of Python (3.12+ highly recommended, +3.11+ is _probably_ okay). One way to get Python is via Conda (like anaconda, miniconda, etc). Another way is to use homebrew (often on Linux). If you are using Ubuntu 22.04 or newer, the system Python will be fine. @@ -51,6 +51,6 @@ installation then. ## Editor You should have an editor you like. I'll be using a VI interface, either -natively or in VS Code. You can pick EMacs, or something else. Software +natively or in VS Code. You can pick Emacs, or something else. Software development _is_ writing and editing text files. Learning a powerful editor will vastly improve how you work with code. diff --git a/content/week01_intro/vcs.md b/content/week01_intro/vcs.md index 4951da0..b32271b 100644 --- a/content/week01_intro/vcs.md +++ b/content/week01_intro/vcs.md @@ -16,9 +16,10 @@ Getting a copy of the repository locally is called **cloning**. The history of a Git repository is stored with it. Each stored point in time is called a **commit**. Git is a bit clever in how it stores history, so making -lots of commits is fairly cheap. Multiple commits can share a parent, making the -commit history look a bit like a tree (technically a graph, since you can also -share parents, but we won't worry about that too much yet). +lots of commits is fairly cheap. Most commits have one parent, making the commit +history look like a tree. But some commits can have multiple parents (in +merges), making the history technically a directed acyclic graph (DAG). For now, +the tree mental model is fine. You can create a moving "name" that points at a commit called a **branch**, or a stationary name called a **tag** (at least it should be stationary). One of the @@ -27,11 +28,11 @@ a nice, incrementing number? Because git is distributed. ## Distributed -Unlike older systems, every full git checkout is a complete, independent copy of -the repository! You have all history locally. If you clone a remote repository, -you could push to a different remote if you wished. If GitHub, GitLab, or -whatever other **hosting** service you are using disappears tomorrow, you can -simply push somewhere else and have all your history intact. +Unlike older systems, every cloned git repository is a complete, independent +copy of the repository! You have all history locally. If you clone a remote +repository, you could push to a different remote if you wished. If GitHub, +GitLab, or whatever other **hosting** service you are using disappears tomorrow, +you can simply push somewhere else and have all your history intact. ## Hosted diff --git a/content/week02_testing/debugging.md b/content/week02_testing/debugging.md index 6347e07..ed8a5bc 100644 --- a/content/week02_testing/debugging.md +++ b/content/week02_testing/debugging.md @@ -23,7 +23,7 @@ log = logging.getLogger("unique") You'll often see this shortcut used in modules: ```python -log = logging.getLogger(__module__) +log = logging.getLogger(__name__) ``` Here are a couple of logging statements: @@ -59,8 +59,8 @@ beautiful setting (for use in applications, not libraries). The hardest part of logging is generally setting up the infrastructure for controlling the logger, usually; it’s best if you have a flag or environment variable that can control this, and you have to decide or allow a choice on -whether you want all loggers or just yours to change level. And you have might -want to log to a file, rotate logs, etc; everything is doable but not all that +whether you want all loggers or just yours to change level. And you might want +to log to a file, rotate logs, etc; everything is doable but not all that pretty. ### Combining with pytest diff --git a/content/week02_testing/pytest.md b/content/week02_testing/pytest.md index 25c3418..a0405cf 100644 --- a/content/week02_testing/pytest.md +++ b/content/week02_testing/pytest.md @@ -53,7 +53,7 @@ def f(x): class TestFunction(unittest.TestCase): def test_f(self): self.assertEqual(f(0), 0) - self.assertEqual(f(1), 2) + self.assertEqual(f(1), 1) self.assertEqual(f(2), 4) ``` @@ -219,12 +219,12 @@ def slow(): return "world" -def test_a(something): - assert something == "world" +def test_a(slow): + assert slow == "world" -def test_b(something): - assert something == "world" +def test_b(slow): + assert slow == "world" ``` Fixtures support setup _and_ teardown, and they do so using the same "trick" @@ -262,7 +262,7 @@ import sys def test_some_function_linux(monkeypatch): - monkepatch.setattr(sys, "platform", "linux") + monkeypatch.setattr(sys, "platform", "linux") # stuff here will think it's on linux ... # after the test, the monkeypatching is removed! @@ -287,7 +287,7 @@ import sys @pytest.mark.parametrize("platform", ["linux", "win32", "darwin"]) def test_some_function_linux(monkeypatch, platform): - monkepatch.setattr(sys, "platform", platform) + monkeypatch.setattr(sys, "platform", platform) ... ``` @@ -305,13 +305,13 @@ import pytest import sys -@pytest.fixture("platform", ["linux", "win32", "darwin"]) +@pytest.fixture(params=["linux", "win32", "darwin"]) def platform(request): return request.param def test_some_function_linux(monkeypatch, platform): - monkepatch.setattr(sys, "platform", platform) + monkeypatch.setattr(sys, "platform", platform) ... ``` @@ -324,13 +324,13 @@ import pytest import sys -@pytest.fixture("platform", ["linux", "win32", "darwin"]) +@pytest.fixture(params=["linux", "win32", "darwin"]) def platform(request, monkeypatch): - monkepatch.setattr(sys, "platform", platform) + monkeypatch.setattr(sys, "platform", request.param) return request.param -def test_some_function_linux(monkeypatch, platform): ... +def test_some_function_linux(platform): ... ``` Now we automatically get the monkeypatching each time, too! @@ -413,7 +413,7 @@ A few plugins of note: - `pytest-mock`: Makes the excellent `unittest.mock` built-in library nicer to use from pytest as native fixtures. -- `pytest-ascyncio`: Allows pytest to natively test ascync functions. +- `pytest-asyncio`: Allows pytest to natively test async functions. - `pytest-xdist`: Distributed testing, loop on failing. - `pytest-subprocess`: Mocks subprocess calls. - `pytest-benchmark`: Compute benchmarks as part of testing (also see @@ -448,7 +448,7 @@ tests failed and were skipped, and why. `--strict-markers` will make sure you don't try to use an unspecified fixture. And `--strict-config` will error if you make a mistake in your config. `xfail_strict` will change the default for `xfail` to fail the tests if it doesn't fail - you can still override locally in -a specific xfail for a flaky failure. `filter_warnings` will cause all warnings +a specific xfail for a flaky failure. `filterwarnings` will cause all warnings to be errors (you can add allowed warnings here too). `log_cli_level` will report `INFO` and above log messages on a failure. Finally, `testpaths` will limit `pytest` to just looking in the folders given - useful if it tries to pick diff --git a/content/week02_testing/tdd.md b/content/week02_testing/tdd.md index 5eec8f4..9d276a0 100644 --- a/content/week02_testing/tdd.md +++ b/content/week02_testing/tdd.md @@ -141,7 +141,9 @@ def test_my_max(): assert my_max([3]) == 3 ``` -To pass this test, we can just accept an argument and return 3. +To pass this test, we can just accept an argument and return 3. (Note that +naming the parameter `input` shadows Python's built-in `input` function; we'll +fix this in the refactor stage below.) ```python # green @@ -267,7 +269,7 @@ And to pass import pytest -def my_max(input_list: list[int]) -> int: +def my_max(input_list: list[int]) -> int | None: if not isinstance(input_list, list): raise ValueError("Non-list argument passed to my_max") diff --git a/content/week02_testing/testing.md b/content/week02_testing/testing.md index c0ea5c0..e360776 100644 --- a/content/week02_testing/testing.md +++ b/content/week02_testing/testing.md @@ -40,7 +40,7 @@ rely on. The adversarial view --- don't be wishful. Try to find defects. Document for the user things that could cause problems but that you are not guarding against -(a.k.a. make explicit caveat emptors). We'll discuss documentation later. +(a.k.a. make explicit caveat emptor). We'll discuss documentation later. Write code that's amenable to atomic testing --- modularity, DRY vs. WET, etc all help (as we'll see). The better your design and workflow play with Git, then @@ -59,7 +59,7 @@ Tests are themselves code --- it should adhere to good development principles (i.e. writing more and more tests is part of the code authoring process). Test code often outnumbers real code --- or at least it should. SQLite famously -has 608 times more test code than source code. +has 590 times more test code than source code. Testing well isn't trivial --- tools can help, but it's a skill and an art. @@ -147,7 +147,7 @@ development, since you are writing smaller portions at a time. Test driven development flips the tables on test vs. code; instead of writing the code then testing it, you write the tests and then make them pass by writing -the code. It may seem unnatural at first, but is has some significant benefits: +the code. It may seem unnatural at first, but it has some significant benefits: - Forces you to think about and design the _interface_ first. How should this be used? It's easier to answer that question when making a change doesn't require diff --git a/content/week03_git/advanced_steps.md b/content/week03_git/advanced_steps.md index 4c9a5c2..325157a 100644 --- a/content/week03_git/advanced_steps.md +++ b/content/week03_git/advanced_steps.md @@ -193,7 +193,7 @@ $ git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short ## Merging branches Now that we have a better code, we want to import in this better branch what was -done in the `master` branch. In other words, we want to merge to work done in +done in the `master` branch. In other words, we want to merge the work done in these 2 diverging versions of the code. We can do this using the `git merge` command. @@ -378,7 +378,7 @@ $ git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short * | ce60f0f 2022-10-07 | Trying to merge again [Romain Teyssier] |\| | * 07557d4 2022-10-07 | Modify file1.txt [Romain Teyssier] -* | 8b3b89f 2022-10-07 | Merge branch 'master' into better_code This is necessary. [Romain Teyssier] +* | 8b3b89f 2022-10-07 | Merge branch 'master' into better_code Merging previous work in better version of the code [Romain Teyssier] |\| | * 41f8d80 2022-10-06 | Committing file3 (tag: v2) [Romain Teyssier] | * c6e6535 2022-10-06 | Committing file2 [Romain Teyssier] @@ -504,7 +504,7 @@ We can check now that `file5.txt` is now modified as in the original repository. ```console $ cat file5.txt -I have changed file5.txt +I have changed file5.txt ``` When using `git pull`, you are in fact merging the remote branch with your local diff --git a/content/week03_git/first_steps.md b/content/week03_git/first_steps.md index 6123101..cf688b5 100644 --- a/content/week03_git/first_steps.md +++ b/content/week03_git/first_steps.md @@ -35,7 +35,7 @@ Add this file to the staging area and commit your first change. ```console $ git add file1.txt $ git commit -m "First commit" -[master (root-commit) c073d19] First commit +[main (root-commit) c073d19] First commit 1 file changed, 1 insertion(+) create mode 100644 file1.txt ``` @@ -51,7 +51,7 @@ We can now check the status of our repository using the command ```console $ git status -On branch master +On branch main nothing to commit, working tree clean ``` @@ -61,11 +61,11 @@ Let's now make our first change. $ echo "This is my first file but I modified it." > file1.txt ``` -Let see now the status of our repository. +Let's see now the status of our repository. ```console $ git status -On branch master +On branch main Changes not staged for commit: (use "git add ..." to update what will be committed) (use "git restore ..." to discard changes in working directory) @@ -79,7 +79,7 @@ Let's add these changes to the staging area. ```console $ git add file1.txt $ git status -On branch master +On branch main Changes to be committed: (use "git restore --staged ..." to unstage) modified: file1.txt @@ -89,16 +89,17 @@ Let's commit those changes. ```console $ git commit -m "Commit changes" -[master 476b980] Commit changes +[main 476b980] Commit changes 1 file changed, 1 insertion(+), 1 deletion(-) $ git status -On branch master +On branch main nothing to commit, working tree clean ``` When you commit changes, using `git commit -m` allows you to give a commit message on the command line. Without the `-m` options, git will launch an editor -(default is usually `vim`). To set your own editor, use: +(the default depends on your system configuration, often `vi` or `vim` on +Unix-like systems). To set your own editor, use: ```console $ export GIT_EDITOR='emacs -nw' @@ -114,7 +115,7 @@ To see the past history of your project, type: ```console $ git log -commit 476b9801a6fb1efefdcd6c4d1bc82bff43686f9e (HEAD -> master) +commit 476b9801a6fb1efefdcd6c4d1bc82bff43686f9e (HEAD -> main) Author: Romain Teyssier Date: Thu Oct 6 09:48:30 2022 -0400 @@ -131,7 +132,7 @@ A nicer way of looking at the history of your repository: ```console $ git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short -* 476b980 2022-10-06 | Commit changes (HEAD -> master) [Romain Teyssier] +* 476b980 2022-10-06 | Commit changes (HEAD -> main) [Romain Teyssier] * c073d19 2022-10-06 | First commit [Romain Teyssier] ``` @@ -142,7 +143,7 @@ and committing the new file. Your history must now look like this: ```console $ git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short -* 41f8d80 2022-10-06 | Committing file3 (HEAD -> master) [Romain Teyssier] +* 41f8d80 2022-10-06 | Committing file3 (HEAD -> main) [Romain Teyssier] * c6e6535 2022-10-06 | Committing file2 [Romain Teyssier] * 476b980 2022-10-06 | Commit changes [Romain Teyssier] * c073d19 2022-10-06 | First commit [Romain Teyssier] @@ -202,9 +203,9 @@ $ git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short We can go back to the last version using ```console -$ git checkout master +$ git checkout main Previous HEAD position was 476b980 Commit changes -Switched to branch 'master' +Switched to branch 'main' $ ls file1.txt file2.txt file3.txt ``` diff --git a/content/week03_git/intro.md b/content/week03_git/intro.md index 3b19005..69ca18c 100644 --- a/content/week03_git/intro.md +++ b/content/week03_git/intro.md @@ -20,7 +20,7 @@ Old school techniques are usually bad. - Track changes feature Google Docs or Word --- not so useful for anything complex -- Disaster recover is a disaster. +- Disaster recovery is a disaster. - Oh F#\@K! Did I just overwrite all my work from last night??!!!? ![title](phd101212s.png) @@ -112,8 +112,8 @@ Open a browser to this URL: https://learngitbranching.js.org/?NODEMO Other resources for git: - https://gitimmersion.com/ -- http://think-like-a-git.net/ +- https://think-like-a-git.net/ - http://ndpsoftware.com/git-cheatsheet.html - https://ohshitgit.com/ -- http://gitready.com/ +- https://gitready.com/ - https://explainshell.com/ diff --git a/content/week04_package/packaging.md b/content/week04_package/packaging.md index 41420c7..19c7a76 100644 --- a/content/week04_package/packaging.md +++ b/content/week04_package/packaging.md @@ -60,14 +60,14 @@ for specific ecosystems, like bioconda. Conda packages tend to be a lot larger, because they bundle in more dependencies. Binary wheels generally target a minimal subset of the host system -that works everywhere, while conda packages bundle everything and are build with +that works everywhere, while conda packages bundle everything and are built with custom toolchains. Conda distributes Python itself - it's just another dependency to Conda, while Pip can only install to an existing Python. There are several packages, so here's a quick summary: - **Conda**: The original, written in Python. The resolver is now from Mamba, so - it's much closer to speed in mamba that is used to be. a large ecosystem. + it's much closer in speed to what Mamba used to be, with a large ecosystem. - **Mamba**: A faster, drop-in replacement for conda that uses a different dependency resolver and is written in C++. - **MicroMamba**: Used to be different from Mamba, but now is simply a diff --git a/content/week04_package/precommit.md b/content/week04_package/precommit.md index bed79ca..ec4a666 100644 --- a/content/week04_package/precommit.md +++ b/content/week04_package/precommit.md @@ -55,8 +55,8 @@ repos: ``` **Helpful tip**: Pre-commit runs top-to-bottom, so put checks that modify -content (like the several of the pre-commit-hooks above, or Black) above checks -that might be more likely to pass after the modification (like flake8). +content (like several of the pre-commit-hooks above, or Black) above checks that +might be more likely to pass after the modification (like flake8). **Keeping pinned versions fresh**: You can use `pre-commit autoupdate` to move your tagged versions forward to the latest tags! Due to the design of @@ -87,7 +87,7 @@ There are a _few_ options, mostly to enable/disable certain files, remove string normalization, and to change the line length, and those go in your `pyproject.toml` file. -Here is the snippet to add Black to your `.pre-commit-config.yml`: +Here is the snippet to add Black to your `.pre-commit-config.yaml`: ```yaml - repo: https://github.com/psf/black-pre-commit-mirror @@ -146,7 +146,7 @@ additional_dependencies: [attrs==25.3.0] MyPy has a config section in `pyproject.toml` that looks like this: -```ini +```toml [tool.mypy] files = "src" python_version = "3.10" diff --git a/content/week04_package/task_runners.md b/content/week04_package/task_runners.md index 15e5a87..c842ae5 100644 --- a/content/week04_package/task_runners.md +++ b/content/week04_package/task_runners.md @@ -116,7 +116,7 @@ pytest. If a user does not have a particular version of Python installed, it will be skipped. You can use a Docker container to run in an environment where all -Python's (3.6+) are available: +Python versions (3.9+) are available: ```console $ docker run --rm -itv $PWD:/src -w /src quay.io/pypa/manylinux_2_28_x86_64:latest pipx run nox @@ -218,7 +218,7 @@ A standard [powered by nox](https://github.com/scikit-hep/hist/blob/main/noxfile.py) package in Pure Python in Scikit-HEP is Hist. -A package that happens to use PDM (like Poetry but better) is Scikit-HEP UHI, +A package that happens to use Hatch (like Poetry but faster) is Scikit-HEP UHI, which is [powered by nox](https://github.com/scikit-hep/uhi/blob/main/noxfile.py). Nox can setup a conda environment with ROOT (slow, but only nox and conda are @@ -235,7 +235,6 @@ running pip-tools' compile on every Python version to pin dependencies, as well as providing a standard interface to update Python and project listing update scripts. The docs job there runs mkdocs instead of Sphinx. Other PyPA projects using nox include [pip](https://github.com/pypa/pip/blob/main/noxfile.py), -[pipx](https://github.com/pypa/pipx/blob/main/noxfile.py), [manylinux](https://github.com/pypa/manylinux/blob/main/noxfile.py), [packaging](https://github.com/pypa/packaging/blob/main/noxfile.py), and [packaging.python.org](https://github.com/pypa/packaging.python.org/blob/main/noxfile.py). diff --git a/content/week04_package/using_packages.md b/content/week04_package/using_packages.md index 8d00965..6034bcc 100644 --- a/content/week04_package/using_packages.md +++ b/content/week04_package/using_packages.md @@ -7,8 +7,8 @@ Packaging is absolutely critical as soon as you: - Work in more than one place - Upgrade or change anything on your computer -Unfortunately, packing has a _lot_ of historical cruft, bad practices that have -easy solutions today but are still propagated. +Unfortunately, packaging has a _lot_ of historical cruft, bad practices that +have easy solutions today but are still propagated. We will split our focus into two situations, then pull both ideas together. @@ -66,7 +66,7 @@ black myfile.py ````` Now you have "black", but nothing has changed in your global site packages! You -cannot import black or any of it's dependencies! There are no conflicting +cannot import black or any of its dependencies! There are no conflicting requirements (more common in pip 20.3+, which now will refuse to install two packages that have incompatible requirements). @@ -90,7 +90,7 @@ pipx run black myfile.py ```` ````` -The first time you do this, pipx create a venv and puts black in it, then runs +The first time you do this, pipx creates a venv and puts black in it, then runs it. If you run it again, it will reuse the cached environment if it hasn't been cleaned up yet, so it's fast. @@ -123,7 +123,7 @@ pipx run --spec cibuildwheel==2.9.0 cibuildwheel --platform linux #### Self-contained scripts -You can now make a self-contained script; that is, one that describes it's own +You can now make a self-contained script; that is, one that describes its own requirements. You could make a `print_blue.py` file that looks like this: ```python diff --git a/content/week05_ci/cd.md b/content/week05_ci/cd.md index 182ecff..acabaa1 100644 --- a/content/week05_ci/cd.md +++ b/content/week05_ci/cd.md @@ -2,6 +2,10 @@ Example job: +Note: this example doesn't include `actions/setup-python` because `pipx` is +pre-installed on GitHub Actions runners. For workflows that require specific +Python versions or packages, see the CI section above. + ```yaml name: CD @@ -20,8 +24,9 @@ jobs: - name: Build SDist and wheel run: pipx run build - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@v8 with: + name: artifact path: dist/* publish: diff --git a/content/week05_ci/ci.md b/content/week05_ci/ci.md index 90c8494..962ceca 100644 --- a/content/week05_ci/ci.md +++ b/content/week05_ci/ci.md @@ -98,8 +98,8 @@ The `jobs:` dict is the other required key, and it has holds a dict with arbitrary keys; we used the name `tests:`, but it could have been `hefalump:` instead, it's the unique "id" of the job. Inside each job, you'll at least have a `runs-on:` setting that tells GHA which operating system image to run on (like -`ubuntu-latests`, `ubunutu-24.04`, `macos-latest`, `windows-latest`, etc.) There -is also are required `steps:`, containing a list of steps. +`ubuntu-latest`, `ubuntu-24.04`, `macos-latest`, `windows-latest`, etc.) There +are also required `steps:`, containing a list of steps. GitHub Actions runs each step. Steps have an optional (but nice) `name:`. Then they can either have a `uses:` key, which will load a GitHub "Action", or a @@ -371,7 +371,7 @@ There are also a few useful tools installed which can really simplify your workflow or adding custom actions. This includes system package managers (like brew, chocolaty, NuGet, Vcpkg, etc), as well as a fantastic cross platform one: -- [pipx](https://github.com/pypy/pipx): This is pre-installed on all runners +- [pipx](https://github.com/pypa/pipx): This is pre-installed on all runners (GitHub uses to set up other things), and is kept up to date. It enables you to use any PyPI application in a single line with `pipx run `. diff --git a/content/week05_ci/docs.md b/content/week05_ci/docs.md index f18f25d..b725640 100644 --- a/content/week05_ci/docs.md +++ b/content/week05_ci/docs.md @@ -83,9 +83,9 @@ integration tests, however, can be ;) There are two documentation engines quite popular in Python. The oldest and most used one is Sphinx - Python's own documentation is built in Sphinx! It has received a lot of work and good themes lately, like Furo (PyPA) and -sphinx-pydata-theme (SciPy). It is also the basis for JuyterBook, which is what +sphinx-pydata-theme (SciPy). It is also the basis for JupyterBook, which is what this material was written in! It has third-party tooling to use markdown instead -of the default RestructredText, include notebooks as pages, and more. Sphinx +of the default RestructuredText, include notebooks as pages, and more. Sphinx uses the aging docutils, which has an intermediate representation that produces HTML, or other formats too, like ebooks and LaTeX. Example sites: pretty much everything in Python; pip, build, numpy, scipy, etc. @@ -166,7 +166,7 @@ webbrowser. def serve(session: nox.Session) -> None: docs(session) print("Launching docs at http://localhost:8000/ - use Ctrl-C to quit") - session.run("python", "-m", "http.server", "8000", "-d", "_build/html") + session.run("python", "-m", "http.server", "8000", "-d", "build/html") ``` ### Making the docs yours @@ -186,7 +186,7 @@ docs = [ You can select any theme you want; `furo` is an ultra modern, well designed, lightweight theme used by the PyPA. -Next, exit your `conf.py` file. Your extensions should look like this: +Next, edit your `conf.py` file. Your extensions should look like this: ```python extensions = [ @@ -236,27 +236,25 @@ api/index Any valid markdown or Myst additions valid. -```` - This starts by hiding this page from the table of contents (options are in YAML format surrounded by `---`'s at the top, this is a common convention in markdown). -Then you have some text, then you have a table of contents (this is a Myst addition -to markdown to give you the ability to do a restructured text thing - in fact, you can see -a restructured text option `:hidden:` inside the block). The table of contents -is hidden so it isn't shown inline on the page (since it's already in the side -bar). This assumes you have three files with more content in them, and a folder where you -will put your API documentation. +Then you have some text, then you have a table of contents (this is a Myst +addition to markdown to give you the ability to do a restructured text thing - +in fact, you can see a restructured text option `:hidden:` inside the block). +The table of contents is hidden so it isn't shown inline on the page (since it's +already in the side bar). This assumes you have three files with more content in +them, and a folder where you will put your API documentation. The other pages can be placed in here in the same way, so let's focus on the api -pages. You can auto-generate them with +pages. You can auto-generate them with [sphinx-apidoc](https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html) (built-in): ```console $ sphinx-apidoc -o docs/api src/my_package -```` +``` Everything is dynamically imported, so protect code with `if __name__ == "__main__"`! There are lots of flags to control how it generates diff --git a/content/week06_oop/geom_example/geometry/classic.py b/content/week06_oop/geom_example/geometry/classic.py index 253a7b4..20f7089 100644 --- a/content/week06_oop/geom_example/geometry/classic.py +++ b/content/week06_oop/geom_example/geometry/classic.py @@ -14,7 +14,7 @@ def perimeter(self): @dataclasses.dataclass -class Triangle: +class Triangle(Shape): a: float b: float c: float @@ -28,7 +28,7 @@ def perimeter(self): @dataclasses.dataclass -class Rectagle(Shape): +class Rectangle(Shape): width: float height: float @@ -39,7 +39,7 @@ def perimeter(self): return 2 * (self.width + self.height) -class Square(Rectagle): +class Square(Rectangle): def __init__(self, side): super().__init__(side, side) diff --git a/content/week06_oop/geom_example/tests/test_classic.py b/content/week06_oop/geom_example/tests/test_classic.py index 1bde60f..c5b88ac 100644 --- a/content/week06_oop/geom_example/tests/test_classic.py +++ b/content/week06_oop/geom_example/tests/test_classic.py @@ -1,4 +1,4 @@ -from geometry.classic import Rectagle, Square, Circle, Triangle +from geometry.classic import Rectangle, Square, Circle, Triangle import math from pytest import approx @@ -10,7 +10,7 @@ def test_triangle(): def test_rectangle(): - r = Rectagle(3, 4) + r = Rectangle(3, 4) assert r.area() == 12 assert r.perimeter() == 14 diff --git a/content/week06_oop/introoo.md b/content/week06_oop/introoo.md index 6631d81..c2dd899 100644 --- a/content/week06_oop/introoo.md +++ b/content/week06_oop/introoo.md @@ -142,8 +142,8 @@ classdef Path obj.string_location = string_location; end - function res = exists() - res = isfile(filename) + function res = exists(obj) + res = isfile(obj.string_location) end end end @@ -174,7 +174,7 @@ class Path { } exists() { - return fs.existsSync(path); + return fs.existsSync(this.string_location); } } ``` @@ -309,7 +309,7 @@ rascal.eat("berries") ``` In this case, all `Animal`s can eat - if you know you take an `Animal`, you know -it can eat. However, `Raccoon` has have a custom eat function. It has the same +it can eat. However, `Raccoon` has a custom eat function. It has the same signature (important!), but it does a bit more. This is also how Python calls a method from the "class above", by using `super()`. You could have also said `Animal.eat(self, food)` here, but `super()` is better. @@ -449,7 +449,7 @@ new ones, almost always for Interfaces. At this point, an ABC is well defined (we have seen how to make one in code), but an Interface is a concept, an agreement between implementer and caller. We -fill formalize this later when we get to static typing with `Protocol`s. +will formalize this later when we get to static typing with `Protocol`s. ### Special methods @@ -472,9 +472,9 @@ Here are a few to give you a taste of what is available - `__radd__`/`__rsub__`/`__rmul__`/`__rtruediv__`: Reversed versions of math operators. These are called if the first operator is not a member of this class. -- `__eq__`/`__neq__`/`__lt__`/...: The comparison operators. You can just - specify two and then let `@functools.totalordering` generate the rest for you. -- `__repr__`/`__str__`: Controls hows the object is printed. Unlike some other +- `__eq__`/`__ne__`/`__lt__`/...: The comparison operators. You can just specify + two and then let `@functools.totalordering` generate the rest for you. +- `__repr__`/`__str__`: Controls how the object is printed. Unlike some other languages, Python allows customizing the repr ("programmer view") and the str ("user view"). @@ -504,7 +504,7 @@ these days: `````{tab-set} ````{tab-item} Dataclass ```python -from dataclasses import dataclass +import dataclasses @dataclasses.dataclass diff --git a/content/week06_oop/oodesign.md b/content/week06_oop/oodesign.md index e110454..34fc55b 100644 --- a/content/week06_oop/oodesign.md +++ b/content/week06_oop/oodesign.md @@ -93,8 +93,8 @@ is the child class or subclass of A. - Provides a way to "realize" or "implement" a specified interface (ABC or Protocol). -For example, in `content/week06/geom_example/geometry/classic.py`, `Shape` is a -base class with `area()` and `parameter()` methods. It doesn't know how to +For example, in `content/week06_oop/geom_example/geometry/classic.py`, `Shape` +is a base class with `area()` and `perimeter()` methods. It doesn't know how to compute those - they are abstract. This means you can't instantiate `Shape()`, doing so would give you an error (from the `abc` module). However, the subclasses of `Shape` like `Rectangle` and `Circle` do know how to compute this, @@ -145,25 +145,25 @@ classDiagram Shape <|-- Circle class Shape { +area()* float - +parameter()* float + +perimeter()* float } class Rectangle { +height: float +width: float +area() float - +parameter() float + +perimeter() float } class Triangle { +a float +b float +c float +area() float - +parameter() float + +perimeter() float } class Circle { +radius float +area() float - +parameter() float + +perimeter() float } class Square { +side: float @@ -447,6 +447,7 @@ version of a counter is easier to read: import dataclasses +@dataclasses.dataclass class Incr: start: int = 0 @@ -499,7 +500,7 @@ build a custom mini-language on top of the Python syntax. For example, let's say I want to make path-like objects that I can join with `/`: -```{code-cell} Python +```{code-cell} python3 class Path(str): def __truediv__(self, other): return self.__class__(f"{self}/{other}") @@ -514,11 +515,11 @@ Just in case you want to make a `Path` class like the one above - don’t, use ### Mixins Multiple inheritance can be tricky to use, but a common, useful pattern is a -limited form of multiple inhertitance called mixins. With mixins, you provide a +limited form of multiple inheritance called mixins. With mixins, you provide a few reusable features, and then compose the classes from one or more mixins, with an optional superclass. Let's rewrite the Path example using mixins: -``` +```python class PathMixin: def __truediv__(self, other): return self.__class__(f"{self}/{other}") diff --git a/content/week07_design/designpatt.md b/content/week07_design/designpatt.md index 923d84c..611aa45 100644 --- a/content/week07_design/designpatt.md +++ b/content/week07_design/designpatt.md @@ -134,7 +134,7 @@ the function, and users should not have to look into the body! This issue in Python will be corrected when we cover static types. Generators can be used as a programming model. For example, you might have the -following imperative code to counts the words in a file: +following imperative code to count the words in a file: ```python with open(name, encoding="utf-8") as f: @@ -386,7 +386,7 @@ register callbacks (slots) that we can then trigger when emitting a signal. ```python @dataclasses.dataclass class Signal: - slots = dataclasses.field(default_factory=list) + slots: list = dataclasses.field(default_factory=list) def connect(self, slot): self.slots.append(slot) @@ -415,8 +415,8 @@ import reaktiv name = reaktiv.Signal("Alice") age = reaktiv.Signal(30) -greeting = Computed(lambda: f"Hello, {name()}! You are {age()} years old.") -greeting_effect = Effect(lambda: print(f"Updated: {greeting()}")) +greeting = reaktiv.Computed(lambda: f"Hello, {name()}! You are {age()} years old.") +greeting_effect = reaktiv.Effect(lambda: print(f"Updated: {greeting()}")) # Updated: Hello, Alice! You are 30 years old. name.set("Bob") @@ -561,7 +561,7 @@ online compilers is , which supports a massive number of compilers and has the most advanced interface. "Godbolting" is a term you'll sometimes hear when it comes to testing something out quickly. -You can find similar online tools for most of the other languages (all snipits +You can find similar online tools for most of the other languages (all snippets in this course work on online playgrounds). For example, Rust has . @@ -588,7 +588,7 @@ int main() { // The "main" stack is allocated here In C, you used to have to declare all variables at the top of a function, because it's preparing the stack for the current function. In modern C and C++, you can define variables anywhere, and the compiler will prepare the appropriate -stack for you. It's still placed at the top, because the stack is contagious. +stack for you. It's still placed at the top, because the stack is contiguous. The biggest problem with the stack is it's not dynamic; you can't request a runtime dependent amount of it. It's also limited (you can adjust the limit in @@ -733,7 +733,11 @@ class HeapHolder { } HeapHolder& operator=(const HeapHolder& other) = delete; // copy assignment HeapHolder& operator=(HeapHolder&& other) noexcept { // move assignment - return *this = HeapHolder(other); + if(value != nullptr) + delete value; + value = other.value; + other.value = nullptr; + return *this; } ~HeapHolder() { // Destructor if(value != nullptr) @@ -989,5 +993,5 @@ look up if you are curious: - Factory pattern: We've touched on this lightly, classes `__init__` method, for example. You can have other factories with `@classmethod`'s that return new instances. (or static methods in C++, etc). -- Ascync patterns: Lightly touched on during generators. +- Async patterns: Lightly touched on during generators. - Event loop: A common pattern for reacting to multiple possible inputs. diff --git a/content/week07_design/functional.md b/content/week07_design/functional.md index 21c7621..31344eb 100644 --- a/content/week07_design/functional.md +++ b/content/week07_design/functional.md @@ -285,13 +285,13 @@ println(sum_sq_odds) This is often considered a highly functional language, so I've included it here for completeness. ```` -````{tab-item} Hascal +````{tab-item} Haskell ``` foldl (+) 0 . filter ((==) 1 . flip mod 2) . map (^2) $ [1..5] ``` -Hascal is a purely functional language. It's also a bit odd in that idiomatic -Hascal reads right to left (from mathematics). However, as of 4.8 it does have +Haskell is a purely functional language. It's also a bit odd in that idiomatic +Haskell reads right to left (from mathematics). However, as of 4.8 it does have reverse function application operator (also using a lambda to match the other examples more closely): @@ -299,7 +299,7 @@ examples more closely): [1..5] & map (^2) & filter (\x -> x `mod` 2 == 1) & foldl (+) 0 ``` -I've left of variable assignment & printing since those are a bit different in online playgrounds. +I've left out variable assignment & printing since those are a bit different in online playgrounds. ```` ````{tab-item} C++20 Ranges ```cpp @@ -312,13 +312,13 @@ int main() { std::vector items {1, 2, 3, 4, 5}; auto odd_sq = items | std::views::transform([](int i){return i*i;}) | std::views::filter([](int i){return i%2==1;}); - auto sum_sq_odds = std::accumulate(std::begin(odd_sq), std::end(odd_sq), 0, [](int a, int b){return a + b;}) + auto sum_sq_odds = std::accumulate(std::begin(odd_sq), std::end(odd_sq), 0, [](int a, int b){return a + b;}); std::cout << sum_sq_odds << std::endl; return 0; } ``` -Not that C++ is slowly gaining support; `std::ranges::fold_left` is in C++23, but for +Note that C++ is slowly gaining support; `std::ranges::fold_left` is in C++23, but for C++20, we have to drop back to a classic `std::accumulate` algorithm & begin and end iterators. @@ -337,7 +337,7 @@ int main() { } ``` -Not that stdlib module support is not available yet unless you enable it in +Note that stdlib module support is not yet available unless you enable it in CMake experimental mode and have a very recent compiler. ```` diff --git a/content/week08_static_typing/typing.md b/content/week08_static_typing/typing.md index 21fe2c8..25f144e 100644 --- a/content/week08_static_typing/typing.md +++ b/content/week08_static_typing/typing.md @@ -57,7 +57,7 @@ both good tests and static types. This is not a course about Python. So why learn Python types? Almost everything you do with Python types applies to compiled code too - it's just required, rather than optional. Hopefully by learning it now you'll be able to focus on -adapting to other differences with compiled languages - and you'l have a new +adapting to other differences with compiled languages - and you'll have a new valuable Python skill too! ``` @@ -205,11 +205,9 @@ optional.py:5: error: Item "None" of "list[str] | None" has no attribute "__iter Found 1 error in 1 file (checked 1 source file) ``` -Ignoring the old style syntax (it should say -`Item "None" of "list[str] | None"`), it did find the problem. If someone does -not pass `prefix=`, our function will try to iterate over None, which throw a -runtime error! Even without a test case for using the default argument, MyPy can -detect it as problematic. +It did find the problem. If someone does not pass `prefix=`, our function will +try to iterate over None, which throw a runtime error! Even without a test case +for using the default argument, MyPy can detect it as problematic. The fix is simple - just use `for prefix in prefixes or []:`. Once we learn about Protocols, another fix would be to replace the `list[str]` with something @@ -667,7 +665,7 @@ def could_be_none(y: bool) -> int | None: return 42 if y else None -x: bool = return_a_bool() +x: bool = True a: int = could_be_none(True) b: None = could_be_none(False) c: int | None = could_be_none(x) @@ -691,7 +689,7 @@ class Direction(Enum): down = "down" -def handle_direciton(direction: Direction) -> str: +def handle_direction(direction: Direction) -> str: if direction == Direction.up: return "up" if direction == Direction.down: @@ -729,7 +727,7 @@ handled by the type checker; it's called exhaustiveness checking: from typing_extensions import assert_never # typing in 3.11+ -def handle_direciton(direction: Direction) -> str: +def handle_direction(direction: Direction) -> str: if direction == Direction.up: return "up" if direction == Direction.down: @@ -773,7 +771,7 @@ Never = NoReturn def assert_never(val: Never) -> NoReturn: - assert False, f"Unhandled value: {value} ({type(value).__name__})" + assert False, f"Unhandled value: {val} ({type(val).__name__})" ``` The actual `Never` return type gives a better type checker error, so it's nice that it's directly available now. @@ -848,7 +846,11 @@ class DoesSomething(Protocol): def do_something(self) -> None: ... -assert isinstance(MyThing, DoesSomething) +class MyThing: + def do_something(self) -> None: ... + + +assert isinstance(MyThing(), DoesSomething) ``` This will pass if `MyThing` has a `do_something` method. Unlike the static @@ -858,8 +860,7 @@ check the type signature (it's a runtime construct, after all). If you use a `hasattr(x, "do_something")` pattern, a runtime checkable Protocol can replace it and type checkers will correctly narrow as well. Though if it is a performance critical section of code, the `runtime_checkable` `Protocol` is a -little slower that then `hasattr` and a `type: ignore` comment until Python -3.12. +little slower than `hasattr` and a `type: ignore` comment until Python 3.12. ### Verifying a Protocol @@ -987,9 +988,9 @@ def f[T](x: T) -> T: TypeVar's do not hold a type by themselves. They always occur at least once in the _input_ of a of function. They may occur multiple times, or in the output, but they must occur in the input, since that's how they are bound to a type. -Above, when you call `f`, `T` will have the type you of the variable you called -`f` with. So the above will pass through any types. You can use this as an -argument to generics, as well: +Above, when you call `f`, `T` will have the type of the variable you called `f` +with. So the above will pass through any types. You can use this as an argument +to generics, as well: ```python def make_a_list(*args: T) -> list[T]: @@ -1012,12 +1013,18 @@ wanted to make a custom container that holds arbitrary types, called MyList. Here's how you'd do it: ```python +from collections.abc import Iterable +from typing import Generic, TypeVar + +T = TypeVar("T") + + class MyList(Generic[T]): def __init__(self, items: Iterable[T]) -> None: self.items = list(items) def append(self, element: T) -> None: - self.append(element) + self.items.append(element) ... ``` diff --git a/content/week09_compiled/Compiled_Languages.ipynb b/content/week09_compiled/Compiled_Languages.ipynb index b78b622..36f15db 100644 --- a/content/week09_compiled/Compiled_Languages.ipynb +++ b/content/week09_compiled/Compiled_Languages.ipynb @@ -513,7 +513,7 @@ "id": "47", "metadata": {}, "source": [ - "You should see a long list of directories conrtaining all the executables accessible to you, including:\n", + "You should see a long list of directories containing all the executables accessible to you, including:\n", "\n", "``/se-for-sci/content/week09``" ] @@ -618,9 +618,9 @@ "id": "56", "metadata": {}, "source": [ - "We will not dwelled on the new C syntax introduced here: declaring integer and floating point variables, a for loop and the external functions ``rand()`` and ``pow()``.\n", + "We will not dwell on the new C syntax introduced here: declaring integer and floating point variables, a for loop and the external functions ``rand()`` and ``pow()``.\n", "\n", - "The key points are that we now need to add more ``include`` statements to allow the use of these external functions. The new library element ``strlib.h`` is already contained in the standard GNU C libraries. The library element ``math.h`` is not. We need to tell the compiler to look into an external library to find the ``pow()`` function. \n", + "The key points are that we now need to add more ``include`` statements to allow the use of these external functions. The new library element ``stdlib.h`` is already contained in the standard GNU C libraries. The library element ``math.h`` is not. We need to tell the compiler to look into an external library to find the ``pow()`` function. \n", "\n", "This is done with the compiler using the ``-l`` option that tells the compiler to **link** your code with an external library of already compiled functions. In our case, the name of the **math** library is simply ``m``, so we have to type the command:" ] @@ -1556,7 +1556,7 @@ "\n", "We have now modern tools that allow to compile complex codes dealing properly with dependencies. \n", "\n", - "Historically, the first tool to deliver such a service was ``make``. We will describe it briefly in this course, as you will have to use ``Makefile`` unfortunately. The message here is that ``make`` is depreciated and should be replaced as much as possible with ``cmake``, the modern version of ``make``. " + "Historically, the first tool to deliver such a service was ``make``. We will describe it briefly in this course, as you will have to use ``Makefile`` unfortunately. The message here is that ``make`` is deprecated and should be replaced as much as possible with ``cmake``, the modern version of ``make``. " ] }, { diff --git a/content/week09_compiled/debugging.md b/content/week09_compiled/debugging.md index 6d6e26a..3013786 100644 --- a/content/week09_compiled/debugging.md +++ b/content/week09_compiled/debugging.md @@ -1,6 +1,6 @@ # Debugging -Debugging is one of the most painful and addictive activity in software +Debugging is one of the most painful and addictive activities in software engineering. One could compare it to running or sudoku. You suffer for hours trying to understand why your code is crashing, and when you find the bug, the prize is a dopamine rush and a working code. @@ -134,7 +134,7 @@ All compilers accept the `-g` option. - However, using `-g` slows down the code significantly -- Removes optimizations (unless one uses `–gopt` or `–g –O3`) +- Removes optimizations (unless one uses `-gopt` or `-g -O3`) - Start with `-g –O0` (no optimization) for most accurate correspondence between executable instructions and source code line @@ -145,7 +145,9 @@ All compilers accept the `-g` option. - Running with `-g` is sometimes sufficient to find a bug. The code crashes and indicates where the error occurred -- `cargo` defaults to compiling with the equivalent of `-g -O0`. +- `cargo` defaults to compiling with the equivalent of `-g -O0` in debug mode + (the default). The release profile defaults to optimization with `-O3` and no + debug symbols. ### The `-g` option makes the bug go away! @@ -158,8 +160,8 @@ All compilers accept the `-g` option. address when the optimized code is executed - Look at your compiler’s documentation for how you can use the `-g` option - while keeping most of the optimizations intact, such as `-gopt` for the PGI - compiler (Portland Group), or simply `-g –O2` for Intel + while keeping most of the optimizations intact, such as `-gopt` for the NVIDIA + HPC compiler (formerly Portland Group), or simply `-g -O2` for Intel - caveat: these solutions can sometimes point you to the wrong location in the source code @@ -187,7 +189,7 @@ All compilers accept the `-g` option. ### Try different compilers if you can - Whenever you can, it is always a good idea to try different compilers if you - have access to different plavorms or different compilers on the same platform + have access to different platforms or different compilers on the same platform - Some compilers are a lot stricter than others and can catch potential problems at compile time @@ -830,7 +832,7 @@ examples are: - In python, you have PyCharm developed by Jetbrains available at [this web page](https://www.jetbrains.com/pycharm/). -- For Python, but also C, C++ anf Fortran you have the brand new Visual Studio +- For Python, but also C, C++ and Fortran you have the brand new Visual Studio 2022 from Microsoft available [here](https://visualstudio.microsoft.com/vs/). Here is a screenshot of the PyCharm IDE: ![](complexLook.jpg) diff --git a/content/week09_compiled/profiling.md b/content/week09_compiled/profiling.md index 0dd48ec..728db85 100644 --- a/content/week09_compiled/profiling.md +++ b/content/week09_compiled/profiling.md @@ -1,4 +1,10 @@ # Profiling +Profiling is the process of measuring where your code spends time and resources. +For scientific applications that may run for days or weeks on large +supercomputers, even small performance improvements in critical bottlenecks can +lead to massive time and energy savings. In these slides, we cover profiling +tools and techniques for compiled languages. + Please access the PDF of the slides [here](/_static/pdfs/profiling_stone_teyssier_slides.pdf). diff --git a/content/week09_compiled/rust_example/compiled.md b/content/week09_compiled/rust_example/compiled.md index 3143fb7..9a9e61f 100644 --- a/content/week09_compiled/rust_example/compiled.md +++ b/content/week09_compiled/rust_example/compiled.md @@ -1,7 +1,7 @@ # Intro to compiled languages (Rust) -For the classic approach, please use the jupyter notebook in this directory to -follow the lecture notes. This is a complete rewrite using Rust. +For the classic approach, please use the jupyter notebook in the parent +directory to follow the lecture notes. This is a complete rewrite using Rust. ## Intro to compiled languages @@ -38,12 +38,11 @@ tricky. Modern languages take packaging seriously; a few of those are: - **Go**: Google tool "all the best stuff" from previous languages and made a solid language. Has a garbage collector. - **Rust**: A "memory-safe" language _without_ a garbage collector. Used in - Linux/Windows kernels, but as powerful as (very modern) C++ in many of it's + Linux/Windows kernels, but as powerful as (very modern) C++ in many of its concepts, without the old ways of doing things. -Of these, the most interesting language is currently Rust - it's "systems" -design means you can use it everywhere (unlike Go, which is only for -applications). +Of these, the most interesting language is currently Rust - its "systems" design +means you can use it everywhere (unlike Go, which is only for applications). ## Step 1: Getting the compiler @@ -89,7 +88,7 @@ $ ./example Hello world! ``` -We skipped one step you often see: linking. You can build library code with +We skipped one step you often see: linking. You can build library code without making an executable; then you can either statically link (final binary has everything) or dynamically link (the library is still required at runtime) the code. Your build system (CMake for classic languages, Cargo for rust) will @@ -118,7 +117,7 @@ want it to also set up git for you (due to the way cargo works, you'll have a This makes just three files: `.gitignore` with the `/target` directory ignored, a simple `src/main.rs` hello world application, and creates the `Cargo.toml` -configuration file. That's the new one we want to look at; it's contents should +configuration file. That's the new one we want to look at; its contents should look about like this: ```toml @@ -285,7 +284,7 @@ something that is very challenging for older languages. Because of how easy it is, Rust doesn't encourage a massive standard library; things like regex are relegated to dependencies. Even the Rust AST parser is a dependency. Let's try adding a fun dependency: [`strum`](https://docs.rs/strum/latest/strum/), a -string enum package. We want to use it's "derive" feature too, so we'll request +string enum package. We want to use its "derive" feature too, so we'll request that. You can add it with a command: diff --git a/content/week09_compiled/rust_example/rust.md b/content/week09_compiled/rust_example/rust.md index b17ed87..15df635 100644 --- a/content/week09_compiled/rust_example/rust.md +++ b/content/week09_compiled/rust_example/rust.md @@ -3,8 +3,8 @@ This is an introduction to some of the unique design of Rust. This is not comprehensive - the fantastic [Rust Book][] is much better if you want to fully learn Rust. Instead, we'll look at how it's different from what we've seen so -far in Python, and discuss some of the aspects that make special, like how it is -memory safe without resorting to a garbage collector, the trait system, and +far in Python, and discuss some of the aspects that make it special, like how it +is memory safe without resorting to a garbage collector, the trait system, and syntactic macros. ## Basic syntax @@ -47,7 +47,7 @@ You might notice some functions in Rust have a `!` in them, such as: println!("Hello world"); ``` -This are not normal functions, but syntactic macros. Functions in Rust cannot +These are not normal functions, but syntactic macros. Functions in Rust cannot take a variable number of arguments or optional arguments, but you can work around this using macro functions. These are incredibly powerful - you can process the tokens yourself and return the new code, and you can write them in @@ -64,7 +64,7 @@ let array = vec![1,2,3]; ### Pattern matching Both Python (3.10+) and Rust have pattern matching, though Rust has it deeply -baked in from a much earlier point in it's design. It's an integral part of how +baked in from a much earlier point in its design. It's an integral part of how error propagation works, how optional values are processed, how enums (Union+enum in Python) are used, and more. @@ -103,13 +103,13 @@ let cost = match coin { }; ``` -You can put `{}` blocks here if you want. This is is so common, there's even a +You can put `{}` blocks here if you want. This is so common, there's even a shortcut for a 1-2 branch match, called `if let`: ```rust if let [x, y] = variable { println!("The values are {x}, {y}") -}; +} ``` This will only define `x, y` and run the block if the pattern matches - that is, @@ -127,7 +127,7 @@ Also error handling, etc. ### Structs -Rust is careful to call it's data collection type a "struct", though it has many +Rust is careful to call its data collection type a "struct", though it has many similarities with classes from other libraries. The main reason they avoid the object-oriented term is due to the fact that Rust's struct doesn't support inheritance - the Trait system (later) replaces it. Defining a `struct` looks @@ -220,11 +220,11 @@ for c in chars { } ``` -This will convert the unicode string into a chars iterator, the apply the lambda -function to each char. The `.to_digit` call returns `Option`, which is `None` if -it's not a valid digit, which gets filtered out, so only `3` and `8` remain. -Here, `chars` is a lazy iterator; it hasn't computed anything yet. It only gets -processed when you iterator over it. +This will convert the unicode string into a chars iterator, then apply the +lambda function to each char. The `.to_digit` call returns `Option`, which is +`None` if it's not a valid digit, which gets filtered out, so only `3` and `8` +remain. Here, `chars` is a lazy iterator; it hasn't computed anything yet. It +only gets processed when you iterator over it. ### Traits @@ -273,7 +273,7 @@ There are traits for mathematical operations, and much more. Rust has an interesting rule for traits: Traits can only be implemented if you "own" either the trait or the type. So you can add `Display` to your own types, -but you can't get a third-party library and add a `Display` to one of there +but you can't get a third-party library and add a `Display` to one of their types, since you don't own the stdlib trait or the third party type. This means you can't ever collide with multiple trait definitions by mistake when loading two libraries. @@ -375,7 +375,7 @@ many `&` as you want, but not both. You don't have to worry about cleaning up variables. Since Rust is a compiled language, the compiler can check to see if a variable is used again, and the -last place it's used is it's lifespan. For example, this is valid: +last place it's used is its lifespan. For example, this is valid: ```rust let mut x = SomeType{}; diff --git a/content/week10_binding/01-shared-objects/01-shared-objects.ipynb b/content/week10_binding/01-shared-objects/01-shared-objects.ipynb index 009097f..869662e 100644 --- a/content/week10_binding/01-shared-objects/01-shared-objects.ipynb +++ b/content/week10_binding/01-shared-objects/01-shared-objects.ipynb @@ -99,7 +99,7 @@ "* Shared libraries with C interfaces only\n", "* No help with creating/managing the SO's.\n", "\n", - "C bindings are very easy. Just compile into a shared library, then open it in Python with the built in [ctypes](https://docs.python.org/3.7/library/ctypes.html) module:" + "C bindings are very easy. Just compile into a shared library, then open it in Python with the built in [ctypes](https://docs.python.org/3.13/library/ctypes.html) module:" ] }, { @@ -263,7 +263,7 @@ "source": [ "### But what about C++ (and all other languages)?\n", "\n", - "This wasn't really language specific, it is just a property of compiled libraries. However, C++ (and possibly other libraries) do not by default provide a nice exported interface. In C++, because of features like overloading, the names are \"mangled\" in a compiler specific way. But you can manually export a nice interface:" + "This wasn't really language specific, it is just a property of compiled libraries. However, C++ applies name mangling to function symbols by default, making the exported symbol names unpredictable and compiler-specific. To provide a stable C interface that ctypes or other FFI tools can reliably load, you explicitly use `extern \"C\"` to disable name mangling for specific functions and maintain C linkage:" ] }, { @@ -589,6 +589,13 @@ "squares(arr)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We capture `lib.square` in a variable because Numba's `@vectorize` decorator needs to access the function at compilation time, and ctypes function objects have special behavior that works better when referenced this way." + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/content/week10_binding/02-cpython/02-cpython.ipynb b/content/week10_binding/02-cpython/02-cpython.ipynb index 9417c70..9883d8a 100644 --- a/content/week10_binding/02-cpython/02-cpython.ipynb +++ b/content/week10_binding/02-cpython/02-cpython.ipynb @@ -8,7 +8,7 @@ } }, "source": [ - "# [CPython](python.org)\n", + "# [CPython](https://www.python.org)\n", "\n", "* Let's see how bindings work before going into C++ binding tools\n", "* This is how CPython itself is implemented (and PyPy supports this too)\n", @@ -66,7 +66,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "* `PyArg_ParseTuple(PyObject*, format, *result)` will parse a tuple into a C.\n", + "* `PyArg_ParseTuple(PyObject*, format, *result)` will parse a tuple into C types.\n", "* `PyFloat_FromDouble(double)` will convert a C item into a `PyObject*`." ] }, @@ -99,7 +99,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we need a `PyMethodDef`. This is a structure that holds functions. The structure is \"name\", wrapper function, argument type, and doc string. Since C doesn't know when an array ends, you use a null terminated row at the end to signal that that you are done. The argument type is an item from [this list](https://docs.python.org/3/c-api/structures.html); I'm excited about the new `METH_FASTCALL` in 3.7+...\n", + "Next, we need a `PyMethodDef`. This is a structure that holds functions. The structure is \"name\", wrapper function, argument type, and doc string. Since C doesn't know when an array ends, you use a null terminated row at the end to signal that you are done. The argument type is an item from [this list](https://docs.python.org/3/c-api/structures.html); I'm excited about the new `METH_FASTCALL` in 3.7+...\n", "\n", "Looking back, this could have been a *little* simpler with `METH_O`, which takes exactly one argument and therefore you'd avoid the Tuple." ] @@ -126,7 +126,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we need a module structure. I won't go into it in great detail; `PyModuleDef_HEAD_INIT` is always there. Then the name of the module, then the module docstring (or NULL), then a size for subinterpreters (-1 to disable), the the methods you defined above." + "Now we need a module structure. I won't go into it in great detail; `PyModuleDef_HEAD_INIT` is always there. Then the name of the module, then the module docstring (or NULL), then a size for subinterpreters (-1 to disable), the methods you defined above." ] }, { @@ -254,7 +254,7 @@ "\n", "One possible benefit of building your own Python extensions this way is that you can use the *Python limited API*. This defines a reduced superset of functionality that you must stick to, but in return, you now can use a single compiled extension with *multiple* versions of Python, from a minimum (3.2 or later) to the current version (and future versions!).\n", "\n", - "~~Notice that sadly~~, [PyBuffer](https://jakevdp.github.io/blog/2014/05/05/introduction-to-the-python-buffer-protocol/) is not part of the limited API. (Added in 3.11!)\n", + "[PyBuffer](https://jakevdp.github.io/blog/2014/05/05/introduction-to-the-python-buffer-protocol/) protocol support was not part of the limited API until Python 3.11.\n", "\n", "Nanobind (a light weight set of the core pybind11 functionality for C++17+ and Python 3.8+) supports the limited ABI starting for Python 3.12." ] @@ -265,7 +265,7 @@ "source": [ "To use:\n", " \n", - "* Define `#define Py_LIMITED_API 0x03080000` at the top of your file (above the Python.h include). This number is the minimum python version you want to support (`0x03020000`, or 3.2, is the earliest). Currently supported Python is 3.9+, just to let you know.\n", + "* Define `#define Py_LIMITED_API 0x03080000` at the top of your file (above the Python.h include). This number is the minimum python version you want to support (`0x03020000`, or 3.2, is the earliest). As of Python 3.9, the limited API is widely supported across tooling; for older Python support, consult the Python documentation.\n", "* Set `py_limited_api=True` in the setuptools Extension or set a similar feature in scikit-build-core/meson-python\n", "* Make sure you don't use any of the (now missing) `Py*` functions that are not part of the limited API." ] diff --git a/content/week10_binding/03-pybind/03a-pybind.ipynb b/content/week10_binding/03-pybind/03a-pybind.ipynb index 7615029..4eba492 100644 --- a/content/week10_binding/03-pybind/03a-pybind.ipynb +++ b/content/week10_binding/03-pybind/03a-pybind.ipynb @@ -8,7 +8,7 @@ } }, "source": [ - "[![pybind11](pybind11.png)](http://pybind11.readthedocs.io/en/stable/)\n", + "[![pybind11](pybind11.png)](https://pybind11.readthedocs.io/en/stable/)\n", "\n", "# pybind11\n", "\n", @@ -268,19 +268,19 @@ "\n", " Vector2D(double x, double y): x(x), y(y) {}\n", " \n", - " float get_x() const {\n", + " double get_x() const {\n", " return x;\n", " }\n", " \n", - " float get_y() const {\n", + " double get_y() const {\n", " return y;\n", " }\n", " \n", - " void set_x(float val) {\n", + " void set_x(double val) {\n", " x = val;\n", " }\n", " \n", - " void set_y(float val) {\n", + " void set_y(double val) {\n", " y = val;\n", " }\n", "\n", diff --git a/content/week10_binding/03-pybind/03b-pybind.ipynb b/content/week10_binding/03-pybind/03b-pybind.ipynb index 00321b2..d59b1bf 100644 --- a/content/week10_binding/03-pybind/03b-pybind.ipynb +++ b/content/week10_binding/03-pybind/03b-pybind.ipynb @@ -132,7 +132,7 @@ "%%writefile CMakeLists.txt\n", "\n", "cmake_minimum_required(VERSION 3.18...3.29)\n", - "project(Minuit2SimpleExamle LANGUAGES CXX)\n", + "project(Minuit2SimpleExample LANGUAGES CXX)\n", "\n", "include(FetchContent)\n", "FetchContent_Declare(\n", @@ -227,7 +227,7 @@ "* Avoids imports (fast compile)\n", "\n", "```cpp\n", - "include \n", + "#include \n", "namespace py = pybind11;\n", "\n", "void init_part1(py::module &);\n", @@ -474,7 +474,7 @@ "%%writefile CMakeLists.txt\n", "\n", "cmake_minimum_required(VERSION 3.18...3.25)\n", - "project(Minuit2SimpleExamle LANGUAGES CXX)\n", + "project(Minuit2SimpleExample LANGUAGES CXX)\n", "\n", "set(CMAKE_POSITION_INDEPENDENT_CODE ON)\n", "\n", diff --git a/content/week10_binding/06-rust.md b/content/week10_binding/06-rust.md index 4fffba4..db495ce 100644 --- a/content/week10_binding/06-rust.md +++ b/content/week10_binding/06-rust.md @@ -126,8 +126,8 @@ Bound PyModule and running `.add_*` functions on it to add (much like pybind11). Functions need to be given in the `wrap_pyfunction!` macro. This is the classic interface; there's a new interface based on Rust inline -modules that is much nicer, as well. Here's the new interface, currently (0.21) -requires the `experimental-declarative-modules` (PyO3) feature: +modules that is much nicer, as well. As of PyO3 0.22.0, declarative modules are +a stable feature available by default (no feature flag needed): ```rs /// A Python module implemented in Rust. @@ -153,8 +153,8 @@ mod rust_example { use super::*; #[pyfunction] - fn square(a: usize, b: usize) -> usize { - a * b + fn square(a: usize) -> usize { + a * a } } ``` diff --git a/content/week11_omp/concepts.md b/content/week11_omp/concepts.md index a21760e..46b483e 100644 --- a/content/week11_omp/concepts.md +++ b/content/week11_omp/concepts.md @@ -23,7 +23,7 @@ you modify a variable, there's a potential problem. Here's an example: ``` First, I set up a mutable variable, which is simply a list containing an item. I -did this so I can avoid writing the word `gllobal`, to show that the problem is +did this so I can avoid writing the word `global`, to show that the problem is about mutating variables shared between threads, not unique to global variables. I use `+=` to add 1. Remember, `x+=1` really does `x = x + 1`: the processor reads the value, then adds one, then writes the value. If another thread reads @@ -43,7 +43,7 @@ threads. We'll look at several ways to make or use thread safe variables. ## Mutex -One of the most general tools for thread safely is a mutex. In Python, it's +One of the most general tools for thread safety is a mutex. In Python, it's called a `Lock` or `RLock`, depending on if you can re-enter it in the same thread. Let's take a look: @@ -87,10 +87,10 @@ processors have specialized instructions to allow a value (like an integer) to be updated in a way that is threadsafe without all the overhead of a lock (and also they don't need a separate lock object). -Python doesn't have this, since modifying a Python object far more than a single -integer operation, and it's just a performance benefit over a mutex. You can use -`Event`, which is basically an atomic bool that you can wait on to be set -(True). +Python doesn't have this, since modifying a Python object is far more than a +single integer operation, and it's just a performance benefit over a mutex. You +can use `Event`, which is basically an atomic bool that you can wait on to be +set (True). ## Queue @@ -177,6 +177,6 @@ Let's try the same thing with asyncio: ``` We don't sort the output here, but otherwise, it runs about the same, and takes -the same total amount of time. The difference here is we using a pre-existing -awaitable (sleep), so we have to use `await`, which is really just `yield from` -but using the async-named special methods. +the same total amount of time. The difference here is that we're using a +pre-existing awaitable (sleep), so we have to use `await`, which is really just +`yield from` but using the async-named special methods. diff --git a/content/week11_omp/intro.md b/content/week11_omp/intro.md index 3acb01a..1065c76 100644 --- a/content/week11_omp/intro.md +++ b/content/week11_omp/intro.md @@ -1,4 +1,4 @@ # Introduction to parallel computing -Please access the PDF of the slides -[here](/_static/pdfs/Parallel_Programming_Intro.pdf). +Please access the +[Parallel Programming Introduction slides](/_static/pdfs/Parallel_Programming_Intro.pdf). diff --git a/content/week11_omp/piexample/procexec.py b/content/week11_omp/piexample/procexec.py index 4ec56be..8c2e7fe 100644 --- a/content/week11_omp/piexample/procexec.py +++ b/content/week11_omp/piexample/procexec.py @@ -12,7 +12,7 @@ def timer(): print(f"Took {time.monotonic() - start:.3}s to run") -def pi_each(trials: int) -> None: +def pi_each(trials: int) -> float: Ncirc = 0 for _ in range(trials): diff --git a/content/week11_omp/piexample/threadexec.py b/content/week11_omp/piexample/threadexec.py index d1f3a13..4fca6a4 100644 --- a/content/week11_omp/piexample/threadexec.py +++ b/content/week11_omp/piexample/threadexec.py @@ -12,7 +12,7 @@ def timer(): print(f"Took {time.monotonic() - start:.3}s to run") -def pi_each(trials: int) -> None: +def pi_each(trials: int) -> float: Ncirc = 0 rand = random.Random() diff --git a/content/week11_omp/threading.md b/content/week11_omp/threading.md index 10493f9..6b0c3e6 100644 --- a/content/week11_omp/threading.md +++ b/content/week11_omp/threading.md @@ -146,11 +146,10 @@ Took 2.677322351024486s to run ### Threading library The most general form of threading can be achieved with the `threading` library. -You can start a thread by using `worker.thread.Thread(target=func, args=(...))`. -Or you can use the OO interface, and subclass `Thread`, replacing the `run` -method. +You can start a thread by using `threading.Thread(target=func, args=(...))`. Or +you can use the OO interface, and subclass `Thread`, replacing the `run` method. -Once you a ready to start, call `worker.start()`. The code in the Thread will +Once you are ready to start, call `worker.start()`. The code in the Thread will now start executing; Python will switch back and forth between the main thread and the worker thread(s). When you are ready to wait until the worker thread is done, you can call `worker.join()`. @@ -337,6 +336,7 @@ but here's the basic idea: The high level interface looks like this: ```python import concurrent.futures +from concurrent.futures import InterpreterPoolExecutor import random import statistics @@ -491,8 +491,9 @@ awaited, Python will show a warning otherwise. CPU. It is mostly used for "reactive" programs that do something based on external input (GUIs, networking, etc). -It is also possible to run `async` code in a thread by awaiting on -`asyncio.to_thread(async_function, *args)`. +It is also possible to run synchronous (blocking) code in a thread by awaiting +on `asyncio.to_thread(sync_function, *args)` (note that the function passed +should be synchronous, not async). ```{literalinclude} piexample/asyncpi_thread.py :linenos: diff --git a/content/week13_misc/shell.md b/content/week13_misc/shell.md index b557d96..f2a0652 100644 --- a/content/week13_misc/shell.md +++ b/content/week13_misc/shell.md @@ -1 +1,197 @@ # Useful shell tools and tricks + +The shell (usually `bash` or `zsh`) is the glue that holds a scientific +computing workflow together. You probably already use it to run scripts and +launch jobs, but a little fluency with a handful of classic tools can save you +from writing throwaway Python scripts for tasks the shell does in one line. + +This chapter is a practical tour of the tools worth knowing. None of it is +exhaustive - the goal is to show you what exists so you know what to reach for. + +## Pipes and redirection + +Every command has three standard streams: standard input (`stdin`), standard +output (`stdout`), and standard error (`stderr`). The shell lets you redirect +these. + +```bash +command > out.txt # stdout to a file (overwrites) +command >> out.txt # stdout appended to a file +command 2> err.txt # stderr to a file +command > all.txt 2>&1 # stdout and stderr to the same file +command < in.txt # read stdin from a file +``` + +The pipe `|` connects one command's `stdout` to the next command's `stdin`, +which lets you build pipelines: + +```bash +cat data.csv | sort | uniq -c | sort -rn | head +``` + +This reads a file, sorts it, counts unique lines, sorts by count +(reverse-numeric), and shows the top results - no script required. (You can +often drop the leading `cat` and let the first tool read the file directly, e.g. +`sort data.csv`.) + +## Searching text: grep and ripgrep + +`grep` searches for patterns (regular expressions) in text: + +```bash +grep "error" logfile.txt # lines containing "error" +grep -i "error" logfile.txt # case-insensitive +grep -n "error" logfile.txt # show line numbers +grep -r "TODO" src/ # search recursively +grep -v "DEBUG" logfile.txt # invert: lines NOT matching +grep -c "error" logfile.txt # count matches +``` + +For searching codebases, [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) +is much faster and respects `.gitignore` by default: + +```bash +rg "def main" # recursive by default, skips .git and ignored files +rg -t py "import numpy" # only Python files +``` + +## Finding files: find and fd + +`find` locates files by name, type, size, or modification time: + +```bash +find . -name "*.py" # all Python files below the current dir +find . -type f -mtime -1 # files modified in the last day +find . -name "*.tmp" -delete # find and delete temp files +``` + +[fd](https://github.com/sharkdp/fd) is a friendlier, faster alternative with a +simpler syntax (and, like `rg`, it respects `.gitignore`): + +```bash +fd "\.py$" # find Python files +fd -e csv # find files with the csv extension +``` + +## xargs: turning output into arguments + +`xargs` takes a list on `stdin` and turns it into command-line arguments. This +is how you connect "find some things" to "do something to each": + +```bash +fd -e py | xargs wc -l # count lines in every Python file +fd -e py | xargs -I{} cp {} backup/ # {} is replaced by each filename +fd -e py | xargs -P4 -I{} mytool {} # run 4 jobs in parallel +``` + +For filenames with spaces, use `fd -0 ... | xargs -0` (or `find ... -print0`) so +entries are separated by null bytes instead of whitespace. + +## Stream editing: sed and awk + +`sed` edits a stream line by line. The most common use is substitution: + +```bash +sed 's/old/new/' file.txt # replace first match per line +sed 's/old/new/g' file.txt # replace all matches (global) +sed -i 's/old/new/g' file.txt # edit the file in place +sed -n '10,20p' file.txt # print only lines 10-20 +``` + +`awk` is a small language for columnar data. It splits each line into fields +(`$1`, `$2`, ...) automatically: + +```bash +awk '{print $1}' data.txt # print the first column +awk '{sum += $2} END {print sum}' d.txt # sum the second column +awk -F, '{print $3}' data.csv # use comma as the field separator +``` + +These can replace surprisingly large Python scripts for quick data munging, but +reach for a real script once the logic stops fitting on one line. + +## History and shortcuts + +The shell remembers what you typed. A few high-value tricks: + +- `Ctrl-r` searches your command history interactively - start typing and it + finds the last matching command. +- `!!` re-runs the previous command; `sudo !!` re-runs it with `sudo`. +- `!$` expands to the last argument of the previous command (handy for + `mkdir foo && cd !$`). +- `Ctrl-a` / `Ctrl-e` jump to the start / end of the line; `Ctrl-w` deletes the + word before the cursor; `Ctrl-u` clears the line. +- `cd -` returns to the previous directory. + +## Working on remote machines: ssh, scp, rsync + +Scientific work often happens on clusters or remote servers. `ssh` opens a +remote shell: + +```bash +ssh user@host +ssh user@host "command" # run one command remotely and return +``` + +Copy files with `scp` (simple) or `rsync` (efficient, resumable, only transfers +differences): + +```bash +scp local.txt user@host:/path/ # copy a file to the remote +scp user@host:/path/file.txt . # copy a file from the remote +rsync -avz local_dir/ user@host:dir/ # sync a directory, compressed +rsync -avz --progress big.dat user@host:data/ +``` + +`rsync` is the right choice for large datasets: rerun it and it only sends what +changed. Set up an SSH key (`ssh-keygen`, then `ssh-copy-id user@host`) so you +are not typing passwords, and add host aliases to `~/.ssh/config` to shorten +long connection strings. + +## Keeping work alive: jobs, &, nohup, and tmux + +If you start a long-running job and your connection drops, the job usually dies +with it. There are a few ways to avoid that. + +Background a command in the current shell: + +```bash +mytraining_run & # start in the background +jobs # list background jobs +fg # bring the most recent job to the foreground +``` + +Press `Ctrl-z` to suspend a running foreground job, then `bg` to resume it in +the background. + +To survive logout, prefix with `nohup` (and redirect output): + +```bash +nohup ./long_job.sh > job.log 2>&1 & +``` + +The most robust option is a terminal multiplexer like +[tmux](https://github.com/tmux/tmux/wiki) (or the older `screen`). It keeps your +session running on the remote machine even after you disconnect: + +```bash +tmux # start a new session +# ... run your jobs ... +# press Ctrl-b then d to detach; the session keeps running +tmux attach # reattach later, even after logging out and back in +``` + +tmux also gives you split panes and multiple windows in a single terminal, which +is invaluable when juggling an editor, a running job, and a log tail. + +## Where to learn more + +You do not need to memorize every flag. Two tools make the shell much +friendlier: + +- [explainshell.com](https://explainshell.com) - paste any command and it + annotates each piece (flags, pipes, arguments) with the relevant manual text. +- [tldr](https://tldr.sh) - community-maintained cheat sheets with practical + examples; run `tldr tar` instead of reading the full `man tar`. + +And of course, `man ` and ` --help` are always available. diff --git a/content/week13_misc/webassembly.md b/content/week13_misc/webassembly.md index aa6b7b7..944c827 100644 --- a/content/week13_misc/webassembly.md +++ b/content/week13_misc/webassembly.md @@ -8,8 +8,8 @@ There is no terminal in WASM. You _do_ have a filesystem, though it is a sandboxed one - you can't access arbitrary files on a users computer. There is -an opt-in experiment for chrome that loads a a directory with user permissions. -JuptyerLite keeps its directory in your browser's cache, so it is persistent. +an opt-in experiment for chrome that loads a directory with user permissions. +JupyterLite keeps its directory in your browser's cache, so it is persistent. Sync IO does not work in WASM. This includes `time.sleep`, which just returns instantly. Use `await asyncio.sleep` instead. @@ -40,8 +40,8 @@ await micropip.install("rich") You can install anything that has a pure Python wheel, and has either pure Python wheel dependencies or dependencies already compiled into the Pyodide distribution. Binary packages have to be compiled into Pyodide or compiled -separately into WASM and provided by URL - binaries are not get backward -compatible enough to be proposed and added to PyPI. +separately into WASM and provided by URL - binary compatibility is not stable +enough to be proposed and added to PyPI. Pyodide compiles many of the most popular data science libraries, like NumPy, Pandas, and more. It even has a patched version of matplotlib. From 717ee96df35a8043c20263faafebeb041fbce5b0 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 8 Jun 2026 17:38:19 -0400 Subject: [PATCH 2/2] fix: minor cleanup after AI review Signed-off-by: Henry Schreiner --- content/week01_intro/cleanup_bessel.ipynb | 16 +- content/week13_misc/shell.md | 197 ---------------------- myst.yml | 1 - 3 files changed, 5 insertions(+), 209 deletions(-) delete mode 100644 content/week13_misc/shell.md diff --git a/content/week01_intro/cleanup_bessel.ipynb b/content/week01_intro/cleanup_bessel.ipynb index 86fb3b1..ccba6e7 100644 --- a/content/week01_intro/cleanup_bessel.ipynb +++ b/content/week01_intro/cleanup_bessel.ipynb @@ -87,7 +87,7 @@ "\n", "def down(x, order, start):\n", " \"\"\"\n", - " Compute spherical Bessel function j_order(x) for scalar x using downward recursion.\n", + " Method down, recurse downward.\n", " \"\"\"\n", " j = np.zeros((start + 2), dtype=float)\n", "\n", @@ -112,10 +112,8 @@ "x = np.arange(x_min, x_max, step)\n", "\n", "# Warning! red/green are bad colors to use, default colors are better\n", - "# Both plots use the same approach here; `down` takes a scalar, so we loop\n", - "# over x with a list comprehension.\n", "ax.plot(x, [down(x_i, 10, start) for x_i in x], \"r.\")\n", - "ax.plot(x, [down(x_i, 1, start) for x_i in x], \"g.\")\n", + "ax.plot(x, np.vectorize(down)(x, 1, start), \"g.\")\n", "\n", "plt.show()" ] @@ -152,10 +150,7 @@ "\n", "def down(x, order, start):\n", " \"\"\"\n", - " Compute spherical Bessel function j_order(x) for array x using downward recursion.\n", - "\n", - " Parameters: x (ndarray), order (int), start (int).\n", - " Returns: ndarray of same shape as x.\n", + " Method down, recurse downward.\n", " \"\"\"\n", " j = np.zeros((start + 2, len(x)), dtype=float)\n", "\n", @@ -179,9 +174,8 @@ "\n", "x = np.arange(x_min, x_max, step)\n", "\n", - "# Use the default (colorblind-friendly) color cycle instead of red/green\n", - "ax.plot(x, down(x, 10, 50), \".\", color=\"C0\", label=\"L=10\")\n", - "ax.plot(x, down(x, 1, 50), \".\", color=\"C1\", label=\"L=1\")\n", + "ax.plot(x, down(x, 10, 50), \"r.\", label=\"L=10\")\n", + "ax.plot(x, down(x, 1, 50), \"g.\", label=\"L=1\")\n", "ax.legend()\n", "\n", "plt.show()" diff --git a/content/week13_misc/shell.md b/content/week13_misc/shell.md deleted file mode 100644 index f2a0652..0000000 --- a/content/week13_misc/shell.md +++ /dev/null @@ -1,197 +0,0 @@ -# Useful shell tools and tricks - -The shell (usually `bash` or `zsh`) is the glue that holds a scientific -computing workflow together. You probably already use it to run scripts and -launch jobs, but a little fluency with a handful of classic tools can save you -from writing throwaway Python scripts for tasks the shell does in one line. - -This chapter is a practical tour of the tools worth knowing. None of it is -exhaustive - the goal is to show you what exists so you know what to reach for. - -## Pipes and redirection - -Every command has three standard streams: standard input (`stdin`), standard -output (`stdout`), and standard error (`stderr`). The shell lets you redirect -these. - -```bash -command > out.txt # stdout to a file (overwrites) -command >> out.txt # stdout appended to a file -command 2> err.txt # stderr to a file -command > all.txt 2>&1 # stdout and stderr to the same file -command < in.txt # read stdin from a file -``` - -The pipe `|` connects one command's `stdout` to the next command's `stdin`, -which lets you build pipelines: - -```bash -cat data.csv | sort | uniq -c | sort -rn | head -``` - -This reads a file, sorts it, counts unique lines, sorts by count -(reverse-numeric), and shows the top results - no script required. (You can -often drop the leading `cat` and let the first tool read the file directly, e.g. -`sort data.csv`.) - -## Searching text: grep and ripgrep - -`grep` searches for patterns (regular expressions) in text: - -```bash -grep "error" logfile.txt # lines containing "error" -grep -i "error" logfile.txt # case-insensitive -grep -n "error" logfile.txt # show line numbers -grep -r "TODO" src/ # search recursively -grep -v "DEBUG" logfile.txt # invert: lines NOT matching -grep -c "error" logfile.txt # count matches -``` - -For searching codebases, [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) -is much faster and respects `.gitignore` by default: - -```bash -rg "def main" # recursive by default, skips .git and ignored files -rg -t py "import numpy" # only Python files -``` - -## Finding files: find and fd - -`find` locates files by name, type, size, or modification time: - -```bash -find . -name "*.py" # all Python files below the current dir -find . -type f -mtime -1 # files modified in the last day -find . -name "*.tmp" -delete # find and delete temp files -``` - -[fd](https://github.com/sharkdp/fd) is a friendlier, faster alternative with a -simpler syntax (and, like `rg`, it respects `.gitignore`): - -```bash -fd "\.py$" # find Python files -fd -e csv # find files with the csv extension -``` - -## xargs: turning output into arguments - -`xargs` takes a list on `stdin` and turns it into command-line arguments. This -is how you connect "find some things" to "do something to each": - -```bash -fd -e py | xargs wc -l # count lines in every Python file -fd -e py | xargs -I{} cp {} backup/ # {} is replaced by each filename -fd -e py | xargs -P4 -I{} mytool {} # run 4 jobs in parallel -``` - -For filenames with spaces, use `fd -0 ... | xargs -0` (or `find ... -print0`) so -entries are separated by null bytes instead of whitespace. - -## Stream editing: sed and awk - -`sed` edits a stream line by line. The most common use is substitution: - -```bash -sed 's/old/new/' file.txt # replace first match per line -sed 's/old/new/g' file.txt # replace all matches (global) -sed -i 's/old/new/g' file.txt # edit the file in place -sed -n '10,20p' file.txt # print only lines 10-20 -``` - -`awk` is a small language for columnar data. It splits each line into fields -(`$1`, `$2`, ...) automatically: - -```bash -awk '{print $1}' data.txt # print the first column -awk '{sum += $2} END {print sum}' d.txt # sum the second column -awk -F, '{print $3}' data.csv # use comma as the field separator -``` - -These can replace surprisingly large Python scripts for quick data munging, but -reach for a real script once the logic stops fitting on one line. - -## History and shortcuts - -The shell remembers what you typed. A few high-value tricks: - -- `Ctrl-r` searches your command history interactively - start typing and it - finds the last matching command. -- `!!` re-runs the previous command; `sudo !!` re-runs it with `sudo`. -- `!$` expands to the last argument of the previous command (handy for - `mkdir foo && cd !$`). -- `Ctrl-a` / `Ctrl-e` jump to the start / end of the line; `Ctrl-w` deletes the - word before the cursor; `Ctrl-u` clears the line. -- `cd -` returns to the previous directory. - -## Working on remote machines: ssh, scp, rsync - -Scientific work often happens on clusters or remote servers. `ssh` opens a -remote shell: - -```bash -ssh user@host -ssh user@host "command" # run one command remotely and return -``` - -Copy files with `scp` (simple) or `rsync` (efficient, resumable, only transfers -differences): - -```bash -scp local.txt user@host:/path/ # copy a file to the remote -scp user@host:/path/file.txt . # copy a file from the remote -rsync -avz local_dir/ user@host:dir/ # sync a directory, compressed -rsync -avz --progress big.dat user@host:data/ -``` - -`rsync` is the right choice for large datasets: rerun it and it only sends what -changed. Set up an SSH key (`ssh-keygen`, then `ssh-copy-id user@host`) so you -are not typing passwords, and add host aliases to `~/.ssh/config` to shorten -long connection strings. - -## Keeping work alive: jobs, &, nohup, and tmux - -If you start a long-running job and your connection drops, the job usually dies -with it. There are a few ways to avoid that. - -Background a command in the current shell: - -```bash -mytraining_run & # start in the background -jobs # list background jobs -fg # bring the most recent job to the foreground -``` - -Press `Ctrl-z` to suspend a running foreground job, then `bg` to resume it in -the background. - -To survive logout, prefix with `nohup` (and redirect output): - -```bash -nohup ./long_job.sh > job.log 2>&1 & -``` - -The most robust option is a terminal multiplexer like -[tmux](https://github.com/tmux/tmux/wiki) (or the older `screen`). It keeps your -session running on the remote machine even after you disconnect: - -```bash -tmux # start a new session -# ... run your jobs ... -# press Ctrl-b then d to detach; the session keeps running -tmux attach # reattach later, even after logging out and back in -``` - -tmux also gives you split panes and multiple windows in a single terminal, which -is invaluable when juggling an editor, a running job, and a log tail. - -## Where to learn more - -You do not need to memorize every flag. Two tools make the shell much -friendlier: - -- [explainshell.com](https://explainshell.com) - paste any command and it - annotates each piece (flags, pipes, arguments) with the relevant manual text. -- [tldr](https://tldr.sh) - community-maintained cheat sheets with practical - examples; run `tldr tar` instead of reading the full `man tar`. - -And of course, `man ` and ` --help` are always available. diff --git a/myst.yml b/myst.yml index 8b28801..bfae166 100644 --- a/myst.yml +++ b/myst.yml @@ -91,7 +91,6 @@ project: - title: "Bonus: Exotic topics" children: - file: content/week13_misc/webassembly.md - - file: content/week13_misc/shell.md - title: "WIP: Agentic AI" children: - file: content/week14_agents/setup.md