Updating vibe-qc

For an existing checkout you’ve already used. If this is your first install, see installation instead.

The easy button

./scripts/update.sh

That command pulls the latest tagged release, rebuilds any native dependencies whose source changed, refreshes the Python package inside your .venv/, and prints the new banner so you can confirm the version flipped. About 30 seconds wall-time on a vanilla git pull (no native-dep changes); 5–15 minutes if a vendored library version bumped between releases.

After the script finishes you should see a banner like:

╔══════════════════════════════════════════════════════════════════════════════════╗
║ Release v0.7.0 "Löwdin's Compass"  —  Quantum chemistry for molecules and solids ║
║ © Michael F. Peintinger · MPL 2.0  ·  https://vibe-qc.com                        ║
║ linked: libint 2.13.1 · libxc 7.0.0 · spglib 2.7.0                               ║
╚══════════════════════════════════════════════════════════════════════════════════╝

The first line tells you which release you’re on. If it shows the old version you didn’t expect, see common issues below.

Variants

./scripts/update.sh --release           # latest tagged release (default; flag is for symmetry)
./scripts/update.sh --dev               # bleeding-edge main (X.Y.devN banner)
./scripts/update-dev.sh                 # one-line wrapper, identical to `update.sh --dev`
./scripts/update.sh --ref main          # verbose form of --dev
./scripts/update.sh --ref v0.7.0        # pin to a specific tag
./scripts/update.sh --rebuild-native-deps   # see "Vendored library version bumps" below
./scripts/update.sh --dev --rebuild-native-deps    # combine flags

--release / --dev are named shortcuts; --ref accepts any branch or tag name. The three are mutually exclusive — pick one. The default (release) is the right choice for the vast majority of users — it tracks whatever the project author has tagged as the current public release. --dev (or update-dev.sh) switches to main for previewing in-flight features before they ship.

If you keep two checkouts side-by-side (one for production runs, one for dev), use update.sh inside the release tree and update-dev.sh inside the dev tree. The lighter-weight zsh vibe-update helper later in this page knows about both trees and refreshes them in one shot.

-h / --help prints the same option list inline.

Manual equivalent

If you’d rather invoke the steps by hand (e.g. when debugging an update that didn’t take), here’s what update.sh does:

# 1. Refuse if working tree is dirty (silently stashing user work
#    is worse than failing loud)
git diff --quiet && git diff --cached --quiet || \
    { echo "uncommitted changes — commit or stash"; exit 1; }

# 2. Fetch and check out the target ref (release branch by default)
git fetch origin --tags
git checkout release
git pull --ff-only origin release        # only on branches; tags don't pull

# 3. Rebuild any native deps whose source changed
./scripts/setup_native_deps.sh

# 4. Refresh the Python package (rebuilds the C++ extension)
.venv/bin/pip install -e '.[test]'

# 5. Confirm the new banner
.venv/bin/python -c "import vibeqc; vibeqc.print_banner()"

Step 3 is the heavy one if anything changed. Each build_*.sh skips work when its install/ tree already exists, so a vanilla pull usually takes seconds.

Switching between releases

Sometimes you want to drop to a previous release temporarily — to reproduce an old result, or to confirm a regression bisects to a specific tag.

./scripts/update.sh --ref v0.6.3
# ... run the calculation, compare ...
./scripts/update.sh --ref release         # back to current

The on-disk state is fully consistent at each step. Outputs from your previous calculations survive — only the vibe-qc code changes.

Going to bleeding-edge main

./scripts/update.sh --ref main

main is where active development lands. Banner reads dev X.Y.devN (main @ <sha>) — the SHA pins the build for reproducibility. Use this when you need a feature that’s already merged but not yet released; don’t use this for publication-quality numbers (subtle bugs may be undetected until the next regression-test pass).

To go back to the published release:

./scripts/update.sh --ref release

Common issues

Vendored library version bumps

setup_native_deps.sh’s per-dep idempotency check looks at “does third_party/<dep>/install/lib/cmake/X/XConfig.cmake exist?” — and skips if yes. If a release bumps libxc 7.0.0 → 7.0.1 (or libint / spglib / FFTW / libecpint), the user’s stale install silently sticks around and pip install links against it.

Diagnostic: after running ./scripts/update.sh, the banner’s linked: libint X.Y.Z · libxc A.B.C ... line should match the versions pinned in the various scripts/build_<dep>.sh. If it doesn’t, the stale-install case has bitten.

Fix:

./scripts/update.sh --rebuild-native-deps

That nukes every third_party/<dep>/install/ tree before re-running the native-deps orchestrator, forcing each dep to rebuild from source against its currently-pinned version.

OSError: cannot load library 'libxc.so.7'

A vendored dep’s install/ tree was wiped (or never built). Re-run:

./scripts/update.sh --rebuild-native-deps

CMake complains about a stale build cache after update

Rare but possible if scikit-build-core’s build cache went stale:

rm -rf build/ .scikit-build*
.venv/bin/pip install -e '.[test]' --force-reinstall --no-deps

Tests fail after update

.venv/bin/python -m pytest tests/

If a few tests fail right after a major update, that’s a real signal worth investigating — please open an issue with the banner output and the pytest tail. Don’t ignore the failures and run “real” calculations on the build until the regression is understood.

Shell aliases for daily workflow

If you activate the venv and run updates many times a day, a few zsh aliases save typing. Adapt the paths to your install.

# --- vibeqc helpers (~/.zshrc) ---
alias vibe-up='cd /path/to/vibeqc && source .venv/bin/activate'
alias vibe-up-experimental='cd /path/to/vibeqc-experimental && source .venv/bin/activate'
alias vibe-down='deactivate 2>/dev/null; cd ~'

vibe-up jumps to the repo and activates the venv; vibe-down deactivates and returns home. vibe-up-experimental activates a separate venv tracking an experimental feature branch (e.g. feature/v0.7-pyscf-pbc-parity while the periodic-SCF bug-fix work is in flight) — see the next section for what an experimental tree is for.

Maintaining dev + release (+ experimental) side-by-side

If you keep two checkouts — one tracking main for development, one tracking release for production runs — the function below pulls both, reinstalls the Python package, and verifies each tree by importing the freshly-installed package.

The function also knows about an optional experimental tree: a third checkout tracking a feature branch you want to keep current without disrupting your day-to-day dev / release setup. Typical uses: previewing in-flight bug-fix branches (e.g. the feature/v0.7-pyscf-pbc-parity periodic-SCF work), evaluating a breaking-change branch before it merges to main, or running side-by-side comparisons against the dev tree on the same data. The experimental tree is opt-in — if /path/to/vibeqc-experimental doesn’t exist, vibe-update silently skips it.

vibe-update() {
  local target="${1:-all}"
  local errors=0
  local repo  # NOT 'path' — see footgun note below

  case "$target" in
    dev|release|experimental|all|both) ;;
    *) echo "vibe-update: unknown target '$target' — try dev / release / experimental / all"
       return 2 ;;
  esac

  local GIT
  for candidate in /opt/homebrew/bin/git /usr/local/bin/git /usr/bin/git; do
    [[ -x "$candidate" ]] && { GIT="$candidate"; break; }
  done
  [[ -n "$GIT" ]] || { echo "vibe-update: no git found"; return 1; }

  local PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
  export PATH  # zsh's `local PATH` does not auto-export to subprocesses

  for tree in dev release experimental; do
    # Selection rules:
    #   target=all          → every configured tree
    #   target=both         → dev + release (back-compat alias for the
    #                         pre-experimental default)
    #   target=<tree-name>  → just that one
    case "$target" in
      all|"$tree") ;;
      both) [[ "$tree" == experimental ]] && continue ;;
      *) continue ;;
    esac

    case "$tree" in
      dev)          repo=/path/to/vibeqc-dev ;;
      release)      repo=/path/to/vibeqc-release ;;
      experimental) repo=/path/to/vibeqc-experimental ;;
    esac

    # Experimental is opt-in: if the directory doesn't exist and the
    # user didn't ask for it explicitly, skip silently rather than
    # erroring out — most users won't have an experimental checkout.
    if [[ ! -d "$repo" ]]; then
      [[ "$tree" == experimental && "$target" != experimental ]] && continue
      echo; echo "=== $tree ($repo) ==="
      echo "  ✗ no checkout at $repo — skipping"; ((errors++))
      continue
    fi

    echo; echo "=== Updating $tree ($repo) ==="
    [[ -d "$repo/.venv" ]] || { echo "  ✗ no .venv at $repo — skipping"; ((errors++)); continue; }
    pushd "$repo" >/dev/null || { ((errors++)); continue; }

    "$GIT" fetch origin && "$GIT" pull --ff-only \
      || { echo "  ✗ pull failed"; ((errors++)); popd >/dev/null; continue; }

    # Keep pip itself current — without this, every install prints a
    # "[notice] A new release of pip is available" pestering the user
    # with the `python -m pip install --upgrade pip` command they could
    # run to silence it. Doing it here suppresses the notice and keeps
    # the venv aligned with whatever pip version `pip install -e` wants.
    "$repo/.venv/bin/python" -m pip install --quiet --upgrade pip \
      || { echo "  ✗ pip self-upgrade failed"; ((errors++)); popd >/dev/null; continue; }

    "$repo/.venv/bin/pip" install -e "$repo" --no-deps --quiet \
      || { echo "  ✗ pip install failed"; ((errors++)); popd >/dev/null; continue; }

    "$repo/.venv/bin/python" -c \
      "import vibeqc; print(f'  ✓ vibe-qc {vibeqc.__version__} from {vibeqc.__file__}')"

    popd >/dev/null
  done

  echo
  (( errors > 0 )) && { echo "vibe-update: $errors tree(s) failed"; return 1; }
  echo "vibe-update: done"
}

Usage:

  • vibe-update — every configured tree (dev + release, plus experimental if you’ve checked one out).

  • vibe-update dev / vibe-update release / vibe-update experimental — that one tree only.

  • vibe-update both — dev + release only, skipping experimental. Back-compat alias from the pre-experimental version of this function.

Setting up the experimental tree

git clone https://gitlab.peintinger.com/mpei/vibeqc.git /path/to/vibeqc-experimental
cd /path/to/vibeqc-experimental
git checkout feature/v0.7-pyscf-pbc-parity     # or whichever branch
./scripts/setup_native_deps.sh                  # if it differs from your dev tree
python3 -m venv .venv
.venv/bin/pip install -e '.[test]'

After that, vibe-update (no args) will keep all three trees current on every invocation; vibe-up-experimental activates its venv when you want to actually use it.

This is a lighter-weight cousin of ./scripts/update.sh — it skips the native-deps rebuild and the banner print, so it’s only safe when no vendored library version bumped between pulls. For release-day updates or after a known native-dep bump, prefer update.sh.

Two zsh footguns baked into the function above

Both bit during the original write-up; documenting them here so the next reader doesn’t rediscover them the hard way.

Don’t name the variable path. zsh ties the lowercase path array to the uppercase PATH scalar via typeset -T. Assigning path=/some/dir silently overwrites $PATH, after which git can no longer find ssh and the pull fails with error: cannot run ssh: No such file or directory. The function uses repo for this reason.

local PATH=... needs an explicit export PATH. Inside a zsh function, local declares but does not re-export the variable to forked subprocesses. Without export, the localized PATH stays inside the shell and git’s child processes (in particular ssh) don’t see it. Same symptom as the path/PATH tie above, different cause.

Bash users can adapt the same function — local semantics differ slightly, but the path/PATH collision is a zsh-only issue.

Long-running calculations across an update

If you have a long calculation in flight and want to update vibe-qc without disturbing it, don’t: in-flight python processes hold references to the loaded _vibeqc_core.so, so an update mid-flight doesn’t break the running job, but starting a new calculation in the same shell will pick up the new code immediately. Cleanest is:

  1. Wait for the running job to finish (or pause it via Ctrl-Z and bg if you must).

  2. Update.

  3. Start subsequent jobs.

For background-run patterns (tmux + tee), see the running guide.

Where to go next

  • Quickstart — confirm the new install with a smoke-test calculation.

  • Release process — the upstream side of the release flow that drives what you’re updating to.

  • Changelog — what shipped in the release you just installed.