russell_tensor 3.0.1

Tensor analysis, calculus, and functions for continuum mechanics
Documentation
#!/usr/bin/env python3
"""Run all `russell_tensor` benchmarks and write the results to RESULTS.md.

The script runs:

1. `tensor_benchmark` with the stack layout (`--features intel_mkl`)
2. `tensor_benchmark` with the heap layout (`--features intel_mkl,heap`)
3. `polar_decomp_benchmark` with the stack layout (`--features intel_mkl`)
4. `eigen_values_benchmark` with the stack layout (`--features intel_mkl`)

and produces `RESULTS.md` (next to this file) with the tables of results.

Usage (from anywhere):

    python3 run_all.py
"""

import platform
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path

BENCH_DIR = Path(__file__).resolve().parent
WORKSPACE = BENCH_DIR.parent.parent  # repository root

# The tensor functions, in table order, as they appear in the benchmark.
TENSOR_FUNCTIONS = [
    "dsd_fn",
    "ssd_fn",
    "qsd_fn",
    "deriv2_invariant_jj3",
    "deriv2_invariant_lode",
    "deriv_squared_tensor",
]

# Polar-decomposition general cases and their condition numbers.
POLAR_CASES = [
    ("mild", "≈ 1.1"),
    ("well_conditioned", "≈ 4"),
    ("moderate_conditioned", "≈ 10³"),
    ("ill_conditioned", "≈ 10⁸"),
]

POLAR_ALGORITHMS = ["iterative", "quaternion", "eigen", "svd"]

# Eigenvalue input cases, the four `EigenValMethod` variants, and the derivative algos.
EIGEN_CASES = ["distinct", "coalescent"]
EIGEN_METHODS = ["analytical_hz", "analytical_ha22", "analytical_ha23", "iterative"]
EIGEN_DERIV_ALGOS = ["char_poly", "with_inv"]

TIME_RE = re.compile(r"time:\s*\[([^\]]+)\]")


def run(command):
    """Run a shell command in the workspace root, tee the output, return it."""
    print(f"\n$ {command}\n", flush=True)
    process = subprocess.Popen(
        command,
        cwd=str(WORKSPACE),
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
    )
    output = []
    assert process.stdout is not None
    for line in process.stdout:
        output.append(line)
        print(line, end="", flush=True)
    process.wait()
    if process.returncode != 0:
        sys.exit(f"\ncommand failed with exit code {process.returncode}: {command}")
    return "".join(output)


def parse_results(text):
    """Parse criterion output into ``{benchmark_name: (value, unit)}``.

    Criterion prints either::

        ssd_fn/unrolled/        time:   [182.50 ps 182.56 ps 182.60 ps]

    or, when the name is long, the name and ``time:`` on separate lines.
    """
    results = {}
    last_name = None
    for line in text.splitlines():
        match = TIME_RE.search(line)
        if match:
            prefix = line[: match.start()]
            name_match = re.search(r"(\S+)/\s*$", prefix)
            name = name_match.group(1) if name_match else last_name
            if name is not None:
                tokens = match.group(1).split()
                # tokens = [lo, unit, mid, unit, hi, unit]
                results[name] = (float(tokens[2]), tokens[3])
                last_name = None
        else:
            name_match = re.match(r"^(\S+)/\s*$", line)
            if name_match:
                last_name = name_match.group(1)
    return results


def format_time(value, unit):
    """Format a median time, normalizing picoseconds to nanoseconds."""
    if unit == "ps":
        return f"{value / 1000:.2f} ns"
    return f"{value:.2f} {unit}"


def cell(results, name):
    """Return a formatted table cell, or '—' if the benchmark is absent."""
    if name in results:
        return format_time(*results[name])
    return "—"


def fetch_cpu_model():
    try:
        for line in Path("/proc/cpuinfo").read_text().splitlines():
            if line.startswith("model name"):
                return line.split(":", 1)[1].strip()
    except OSError:
        pass
    return "unknown"


def fetch_os():
    try:
        for line in Path("/etc/os-release").read_text().splitlines():
            if line.startswith("PRETTY_NAME="):
                return line.split("=", 1)[1].strip().strip('"')
    except OSError:
        pass
    return platform.system()


def main():
    stack = parse_results(
        run("cargo bench -p russell_tensor --features intel_mkl --bench tensor_benchmark")
    )
    heap = parse_results(
        run("cargo bench -p russell_tensor --features intel_mkl,heap --bench tensor_benchmark")
    )
    polar = parse_results(
        run("cargo bench -p russell_tensor --features intel_mkl --bench polar_decomp_benchmark")
    )
    eigen = parse_results(
        run("cargo bench -p russell_tensor --features intel_mkl --bench eigen_values_benchmark")
    )

    lines = []
    add = lines.append

    add("# Russell Tensor — Benchmark Results")
    add("")
    add(f"Generated by `run_all.py` on {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S %Z')}.")
    add("")
    add("## System information")
    add("")
    add("| component | value |")
    add("| --------- | ----- |")
    add(f"| OS        | {fetch_os()} (kernel {platform.release()}) |")
    add(f"| CPU       | {fetch_cpu_model()} |")
    add("| BLAS      | Intel MKL |")
    add("")

    add("## Tensor functions")
    add("")
    add("Median times (Intel MKL):")
    add("")
    add("| function | stack/unrolled | heap/unrolled | stack/loops | heap/loops |")
    add("| --- | --- | --- | --- | --- |")
    for function in TENSOR_FUNCTIONS:
        add(
            f"| `{function}` "
            f"| {cell(stack, function + '/unrolled')} "
            f"| {cell(heap, function + '/unrolled')} "
            f"| {cell(stack, function + '/loops')} "
            f"| {cell(heap, function + '/loops')} |"
        )
    add("")

    add("## Polar decomposition")
    add("")
    add("### General (3×3): all algorithms")
    add("")
    add("| case | κ | " + " | ".join(f"`{a}`" for a in POLAR_ALGORITHMS) + " |")
    add("| --- | --- | " + " | ".join("---" for _ in POLAR_ALGORITHMS) + " |")
    for case, kappa in POLAR_CASES:
        cells = [cell(polar, f"polar_rotation_general_{case}/{a}") for a in POLAR_ALGORITHMS]
        add(f"| `{case}` | {kappa} | " + " | ".join(cells) + " |")
    add("")

    add("### In-plane: all algorithms")
    add("")
    add("| algorithm | time |")
    add("| --- | --- |")
    for algorithm in POLAR_ALGORITHMS:
        add(f"| `{algorithm}` | {cell(polar, 'polar_rotation_in_plane/' + algorithm)} |")
    add("")

    add("## Eigen")
    add("")
    add("Median times (Intel MKL).")
    add("")

    add("### Eigenvalues — `EigenValuesT2::calculate_mx`")
    add("")
    add("| case | " + " | ".join(f"`{m}`" for m in EIGEN_METHODS) + " |")
    add("| --- | " + " | ".join("---" for _ in EIGEN_METHODS) + " |")
    for case in EIGEN_CASES:
        cells = [cell(eigen, f"eigenvalues_{case}/{m}") for m in EIGEN_METHODS]
        add(f"| `{case}` | " + " | ".join(cells) + " |")
    add("")

    add("### Eigenprojectors — `EigenProjsT2::calculate_mx`")
    add("")
    add("| case | " + " | ".join(f"`{m}`" for m in EIGEN_METHODS) + " |")
    add("| --- | " + " | ".join("---" for _ in EIGEN_METHODS) + " |")
    for case in EIGEN_CASES:
        cells = [cell(eigen, f"eigen_projectors_{case}/{m}") for m in EIGEN_METHODS]
        add(f"| `{case}` | " + " | ".join(cells) + " |")
    add("")

    add("### Eigenprojector derivatives (distinct) — `EigenProjDerivsT2`")
    add("")
    add("| algorithm | time |")
    add("| --- | --- |")
    for algo in EIGEN_DERIV_ALGOS:
        add(f"| `{algo}` | {cell(eigen, 'eigen_proj_derivs_distinct/' + algo)} |")
    add("")

    output = "\n".join(lines).rstrip() + "\n"
    (BENCH_DIR / "RESULTS.md").write_text(output)
    print(f"\nResults written to {BENCH_DIR / 'RESULTS.md'}\n")


if __name__ == "__main__":
    main()