1 Intro
1.1 Repo structure and Usage
Repo Layout
num-sol-ws2526/
├─ CMakeLists.txt # tiny forwarder: just add_subdirectory(src)
├─ hdnum/ # submodule (header-only HDNUM library)
├─ src/ # build root for all programming exercises
│ ├─ CMakeLists.txt # real top-level: sets hdnum include, adds all ubN subdirs
│ ├─ ub1/
│ │ ├─ CMakeLists.txt # defines ub1 targets (e.g., ub1_task1), sets bin dir to build/bin/ub1
│ │ └─ ub1_task1.cpp
│ ├─ ub2/
│ │ ├─ CMakeLists.txt # same pattern for week 2 (easy place to add per-UB flags)
│ │ └─ ub2_task1.cpp
│ └─ ...
├─ theory/
│ ├─ ub1/
│ └─ ub2/
├─ .github/workflows/
│ └─ build.yml # CI: configure with -S . (root) now that we have a forwarder
└─ .gitignore
Teammate onboarding snippet
Send them this:
# First-time clone (includes submodule)
git clone --recurse-submodules https://github.com/igor-dimi/num-sol-ws2526.git
cd num-sol-ws2526
# Build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
# Create your branch for this week
git checkout -b ub1_<yourname>
# ...work...
git commit -am "ub1: your message"
git push -u origin ub1_<yourname>Day-to-day workflow (lightweight)
Sync main (everyone):
git checkout main git pull --ff-only git submodule update --init --recursiveWork on a branch for the week:
git checkout -b ub1_igor # edit src/ub1/... and theory/ub1/... cmake -S . -B build -DCMAKE_BUILD_TYPE=Release # usually only first time cmake --build build -j # build locally git add -A git commit -m "ub1: implement task1; add notes" git push -u origin ub1_igorMerge strategy:
- If your changes don’t conflict and are small: either open a PR and self-merge after CI is green or fast-forward/merge to
maindirectly. - If you both touched the same files or it’s risky: open a PR and ask for a quick review.
- If your changes don’t conflict and are small: either open a PR and self-merge after CI is green or fast-forward/merge to
With this setup we don’t enable branch protection. CI will still run and show you if
mainbreaks, but it won’t block merges.
Optional niceties (quick wins)
Labels: create
ub1,ub2, … labels; tag issues/PRs by sheet.CODEOWNERS (optional):
.github/CODEOWNERS/src/ub1/ @igor-dimi @teammate /src/ub2/ @igor-dimi @teammate(This just auto-requests reviews; it won’t block merges unless you add protection later.)
CI variants: if you don’t want GMP on CI, drop
libgmp-devand keepHDNUM_USE_GMP=OFFas default in CMake.
Example Workflow and Git Guide
Two options:
- linear (rebase + fast-forward) way first (preferred),
- simple merge commits way.
A) Linear history (rebase each branch onto main, then fast-forward)
This avoids “merge bubbles” and keeps history tidy.
1) Merge your branch
#### make sure main is up to date
git checkout main
git pull --ff-only
#### rebase your branch on top of the latest main
git checkout ub1_igor
git fetch origin
git rebase origin/main # resolve conflicts if any: edit -> git add -> git rebase --continue
#### sanity check
cmake --build build -j || { echo "Fix build before merging"; exit 1; }
#### fast-forward main to include your branch
git checkout main
git merge --ff-only ub1_igor # will fail if rebase wasn't done; that's good
git push2) Merge your teammate’s branch
git checkout ub1_malte
git fetch origin
git rebase origin/main # now 'main' already contains your work
#### resolve conflicts if any -> git add -> git rebase --continue
cmake --build build -j # from the src folder
git checkout main
git merge --ff-only ub1_malte
git pushIf during the rebase you had already pushed your branch earlier, you’ll need to update it (optional) with:
git push --force-with-leaseUse
--force-with-lease(not plain--force) to avoid clobbering a teammate’s work accidentally.
B) Simple merge commits (no rebases)
This is quickest if you don’t care about a perfectly linear history.
git checkout main
git pull --ff-only
#### merge your branch
git merge --no-ff ub1_igor # creates a merge commit even if FF would be possible
#### or: git merge ub1_igor # lets Git fast-forward if possible
cmake --build build -j
git push
#### merge your teammate's branch
git merge --no-ff ub1_malte
#### resolve conflicts if prompted: edit -> git add <files> -> git commit
cmake --build build -j
git pushConflict handling (both methods)
Git stops and shows
CONFLICTmarkers if you happened to touch the same lines.Open the files, keep the correct pieces, then:
git add <fixed-files> # continue the operation: # - during rebase: git rebase --continue # - during merge: git commitRebuild locally; only push once it compiles.
Submodule note
If neither branch changed the hdnum submodule pointer, nothing special. If one did, after merging:
git submodule update --init --recursiveWhich to choose?
- A) Rebase +
--ff-only: best if you value a clean, straight history. - B) Merge commits: fine for a small class repo; fewer commands; history will have merge nodes.
1.2 CMAKE
2) Top-level CMake for examples (code_examples/my_solutions/CMakeLists.txt)
- Defines a shared INTERFACE target
hdnum_common(provides include paths, optional GMP). - Adds each chapter as a subdirectory if it exists.
cmake_minimum_required(VERSION 3.16)
project(numerics_solutions CXX)
set(CMAKE_CXX_STANDARD 20)
# Put binaries under build/bin/ubX/
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
# hdnum include dir (submodule at repo root)
set(HDNUM_DIR "../hdnum")
# Shared interface target with include path (and optional GMP)
add_library(hdnum_common INTERFACE)
target_include_directories(hdnum_common INTERFACE "${HDNUM_DIR}")
option(HDNUM_USE_GMP "Enable GMP in hdnum" OFF)
if(HDNUM_USE_GMP)
target_compile_definitions(hdnum_common INTERFACE HDNUM_HAS_GMP=1)
find_library(GMPXX gmpxx)
find_library(GMP gmp)
if(GMPXX AND GMP)
target_link_libraries(hdnum_common INTERFACE ${GMPXX} ${GMP})
else()
message(FATAL_ERROR "GMP not found; install libgmp-dev or disable HDNUM_USE_GMP")
endif()
endif()
# Add each UB subdir in src/ if it has a CMakeLists.txt
foreach(ub IN ITEMS ub01 ub02 ub03 ub04 ub05 ub06 ub07 ub08 ub09 ub10)
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${ub}/CMakeLists.txt")
add_subdirectory("${ub}")
endif()
endforeach()3) Per UB-folder CMake with per-Ub output folders
- Each chapter decides which executables to build.
- Per-chapter runtime output goes to
build/bin/<chapter>/(and per-config subfolders on multi-config generators).
set(UB ub1)
# Group executables per UB on disk (and per-config for multi-config generators)
set(OUT "${CMAKE_BINARY_DIR}/bin/${UB}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OUT}")
foreach(cfg IN ITEMS Debug Release RelWithDebInfo MinSizeRel)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${cfg} "${OUT}/${cfg}")
endforeach()
function(add_ub_example name)
add_executable(${name} "${name}.cpp")
target_link_libraries(${name} PRIVATE hdnum_common)
set_target_properties(${name} PROPERTIES FOLDER "${UB}") # IDE grouping
endfunction()
# List ub1 binaries here:
add_ub_example(ub1_task1)
add_ub_example(ub1_task2)Repeat a similar
CMakeLists.txtinsrc/ub02,src/ueb03, … adding that chapter’s.cppfiles.
4) Build commands (configure once, then build)
Single-config (Linux/Mint, Makefiles/Ninja):
### configure (once per build dir or when options/CMakeLists change)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release # + -DHDNUM_USE_GMP=ON if needed
### build (repeat as you edit sources)
cmake --build build -jOutputs
build/bin/ub01/ub01_ode_demo
build/bin/ub01/ub01_newton_demo
build/bin/ub02/...
5) Using GMP (high precision) without editing submodule
Install dev package:
sudo apt install -y libgmp-devEnable once at configure time (persists in the build cache):
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DHDNUM_USE_GMP=ONCMake finds and links
gmpxx/gmp, and definesHDNUM_HAS_GMP=1for all examples viahdnum_common.
Tip: keep two build dirs if you switch often:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DHDNUM_USE_GMP=OFF cmake -S . -B build-gmp -DCMAKE_BUILD_TYPE=Release -DHDNUM_USE_GMP=ON cmake --build build cmake --build build-gmp
6) Troubleshooting quickies
fatal error: hdnum.hh: No such file or directory→HDNUM_DIRwrong; ensure submodule is initialized; include path points at the folder that containshdnum.hh.GMP not found→ installlibgmp-dev; reconfigure; or pass custom-DCMAKE_LIBRARY_PATH=/path -DCMAKE_INCLUDE_PATH=/path.- Changing
HDNUM_USE_GMPor editingCMakeLists.txt→ reconfigure (rerun thecmake -S . -B build ...step). Otherwise justcmake --build build -j.
1.3 Commit Guide
here’s a single, self-contained guide you can drop into your notes. it pulls together: clear commit message style, when/where to commit, fixup commits, squashing (locally and via PR), CI considerations, and safety tips.
why commits matter (even in a small class repo)
clean commits make it easy for teammates (and future you) to understand what changed and why. your history is your lab notebook: compact, accurate, readable.
guiding principles
- commit often while you work, but present a tidy history when you merge.
- keep
master/mainalways working; do experimental work on branches likeub1_igor. - use CI to sanity-check builds, not to gate every tiny doc tweak.
- write commit messages that explain why, not only what.
commit message style
short template
<type>(<scope>): <short, imperative summary>
Why:
- a brief explanation of motivation/context
- any side effects, constraints, or follow-ups
Common types: feat, fix, docs, ci, build, refactor, chore, merge.
examples tailored to your repo
feat(build): add root CMakeLists forwarder to src/
Why:
- allows 'cmake -S . -B build' at repo root
- improves IDE support that expects a top-level CMakeLists
feat(build): per-UB output dirs under build/bin/ubN/
Why:
- keeps binaries organized by sheet
- avoids mixing artifacts across weeks
feat(build): add hdnum_common interface target
Why:
- centralizes include path and optional GMP flags
- reduces repetition across ubN CMakeLists
fix(build): CI configure uses -S . with root forwarder
Why:
- matches local build instructions after adding root CMakeLists
feat(build): add HDNUM_USE_GMP option (default OFF)
Why:
- enable high precision when libgmp-dev is available
- keep default lightweight for CI and new clones
feat(ub1): add ub1_task1 using hdnum::Vector
- minimal example: fill vectors, combine, manual dot product
- adds per-UB CMakeLists template
feat(ub2): Newton demo for sqrt(a) with analytic Jacobian
Why:
- aligns with sheet 2 theory
- demonstrates hdnum::Newton usage and stopping criteria
refactor(ub1): extract print_vec helper; tidy includes
Why:
- remove duplication across ub1 tasks
fix(ub1): correct A*x multiplication order in minimal example
- y[0] now matches expected result
docs(ub1): add notes.qmd skeleton; link to code listings
- file-backed blocks include src/ub1/*.cpp
docs(readme): clarify -S . vs -S src with forwarder example
- add decision tree and CI note
chore(submodule): add hdnum as submodule at repo root
- pinned to commit abc1234
- usage: git submodule update --init --recursive
chore(submodule): bump hdnum to upstream vX.Y (abcdef0)
Why:
- picks up bugfix in ExplicitEuler step control
ci: add build workflow on push/PR; fetch submodules
- installs cmake, g++, libgmp-dev
- configures with -S .
ci: trigger only on src/** and workflows to reduce noise
Why:
- avoid rebuilding on docs-only changes
ci: cancel superseded runs for same branch
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
feat(tools): Makefile to copy qmd+sources to Seafile (NR param)
- rsync only newer files for theory/src
feat(share): Makefile for incremental render+zip in ubN/
- renders only stale PDFs
- zips PDFs and sources as Heinrich_BlattNN_*.zip
chore(branch): rename default branch master -> main
- update CI triggers to [ main, master ] for transition
merge: squash ub1_igor into master (UB1 solutions)
- ExplicitEuler example
- notes.qmd
- per-UB CMakeLists
where to commit: branches vs master
- on your UB branch (
ub1_igor,ub1_malte): commit as often as you like; small steps are fine. - on master: only push working, self-contained changes (build locally first). prefer merging/squashing from a branch over committing directly.
for tiny docs like README.md, direct commits to master are fine—especially if your CI is configured to trigger only on src/**.
making small corrections without clutter
amend the last commit (not pushed yet)
git add -A
git commit --amend
updates the previous commit in place (no new commit).
fixup commits (already pushed or want to mark follow-ups)
create small “fixup” commits that will be auto-squashed later:
git commit --fixup <target-commit-sha>
### later:
git rebase -i --autosquash origin/master
configure autosquash by default:
git config --global rebase.autosquash true
squashing commits (make history tidy)
fast one-liner: squash last N commits into one
git reset --soft HEAD~N
git commit -m "ub1: final solution and notes"
use before pushing, or be ready to force-update your branch.
interactive rebase (full control)
git rebase -i HEAD~N
change the list to squash/fixup the follow-ups into the first commit, edit the final message, then:
git push --force-with-lease
(--force-with-lease prevents clobbering a teammate’s remote work.)
squash when merging (GitHub UI)
open a PR and click “Squash and merge”. github creates one commit on master containing all your branch changes, regardless of intermediate commits.
squash without a PR (local)
git checkout master
git merge --squash ub1_igor
git commit -m "ub1: solutions"
git push
merging two UB branches that touched different files
option A (clean & linear):
git checkout master
git pull --ff-only
git checkout ub1_igor
git rebase origin/master
cmake --build build -j
git checkout master
git merge --ff-only ub1_igor
git push
git checkout ub1_malte
git rebase origin/master
cmake --build build -j
git checkout master
git merge --ff-only ub1_malte
git push
option B (quicker, allows merge commits):
git checkout master
git pull --ff-only
git merge ub1_igor
cmake --build build -j
git push
git merge ub1_malte
cmake --build build -j
git push
CI etiquette (so you don’t pay for needless runs)
- trigger builds only when relevant files change:
on:
push:
branches: [ master ]
paths:
- 'src/**'
- '.github/workflows/**'
pull_request:
branches: [ master ]
paths:
- 'src/**'
- '.github/workflows/**'
- optionally cancel superseded runs if you push multiple times quickly:
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
- for docs-only commits, you can use
[skip ci]in the message (sparingly).
when to open a PR vs pushing to master
open a PR when:
- both of you touched the same files
- the change is risky or large
- you want a quick review (PR = free proofreading + CI view)
push or fast-forward merge to master when:
- small, low-risk, and you built locally
- docs-only changes
submodule gotcha refresher (hdnum)
- after pulling, always align the submodule to the recorded commit:
git submodule update --init --recursive
- don’t edit
hdnum/in place; if you must, fork and repoint the submodule. - enabling GMP is a build flag, not an edit to hdnum:
cmake -S . -B build -DHDNUM_USE_GMP=ON
safety net: undo & recover
- view recent HEADs:
git reflog
- hard reset to a prior state (careful; this rewrites your working tree):
git reset --hard <reflog-hash-or-commit-sha>
- abort an in-progress rebase or merge:
git rebase --abort
git merge --abort
small “before you merge to master” checklist
### on your UB branch
git fetch origin
git rebase origin/master # optional but tidy
cmake --build build -j # local build passes
git log --oneline origin/master..HEAD # commits look meaningful
### decide: squash locally (rebase -i) or Squash-and-merge via PR
tl;dr
- commit freely on branches; squash before merging.
- keep master working; build locally first; let CI sanity-check.
- write messages that say why.
- use
--fixup+ autosquash for painless cleanup. - never rewrite shared branch history; do rewrite your feature branch history (then
--force-with-lease).