diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ce98bc5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: ci + +on: + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: the site builds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: peaceiris/actions-hugo@v3 + with: + hugo-version: "0.167.0" + extended: true + - run: hugo --minify + # A page that builds and says nothing is a page nobody notices is empty. + - name: every repository is on it + run: | + for repo in ndarray docs brand; do + grep -q "go-ndarray/$repo" public/index.html \ + || { echo "::error::$repo is not on the page"; exit 1; } + done + echo "the page names every repository this org has" diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 0000000..6eb811d --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,56 @@ +name: links + +# Taken from go-fileshare.github.io, where a dead repository link on the +# landing went unnoticed until a check like this one read the BUILT site. +# +# ⛔ THE CHECK READS THE BUILT SITE, NOT THE LAYOUTS: templated links such as +# the per-repository cards only become real URLs once Hugo has expanded them. + +on: + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + links: + name: every link and image resolves + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + - uses: peaceiris/actions-hugo@v3 + with: + hugo-version: "0.167.0" + extended: true + - run: hugo --minify --destination ./public + - name: Check + run: | + set -uo pipefail + # The character class matters: an extractor that stops only at ) and " + # carries a closing > or < into the request. + urls=$(grep -rhoE 'https?://[^)">< [:space:]]+' public/ | sed 's/[.,]$//' | sort -u) + test -n "$urls" || { echo "::error::no links found; did the site build?"; exit 1; } + echo "checking $(printf '%s\n' "$urls" | wc -l) distinct URLs" + fail=0 + while read -r u; do + # img.shields.io renders anything asked of it and proves nothing. + case "$u" in *img.shields.io*) continue ;; esac + # Three attempts: one is a flake detector rather than a link + # checker, and a lane that fails at random is a lane people learn + # to ignore. + code=000 + for attempt in 1 2 3; do + code=$(curl -sS -o /dev/null -w '%{http_code}' -L --max-time 25 "$u" || echo 000) + case "$code" in 2*|3*) break ;; esac + sleep 3 + done + case "$code" in + 2*|3*) printf ' ok %s %s\n' "$code" "$u" ;; + *) printf ' FAIL %s %s\n' "$code" "$u"; fail=1 ;; + esac + done <<< "$urls" + test "$fail" -eq 0 || { echo "::error::at least one link or image does not resolve"; exit 1; } diff --git a/hugo.toml b/hugo.toml index 1eb6352..d1d9040 100644 --- a/hugo.toml +++ b/hugo.toml @@ -14,62 +14,6 @@ disableKinds = ['taxonomy', 'term', 'rss', 'sitemap', '404'] [params] description = "go-ndarray is a pure-Go (CGO=0) NumPy-style n-dimensional array library for float64 — slicing/views, broadcasting, ufuncs, reductions and linear algebra, multicore + SIMD on amd64, arm64, ppc64le, loong64 and riscv64, with a Workspace arena for loops." -# The colours are read by partials/styles.html. Emerald, shared with go-fft: -# the two are one scientific-computing family, told apart by their glyphs. -# The values are go-fft's, unchanged. -[params.brand] - base = "#059669" # emerald-600: accent, primary button, gradient start - deep = "#047857" # emerald-700: gradient end - bright = "#34d399" # emerald-400: accent in dark mode - shadow = "rgba(5,150,105,.7)" - chipBg = "#d1fae5" - chipBorder = "#a7f3d0" - chipFg = "#047857" - chipBgDark = "rgba(52,211,153,.14)" - chipBorderDark = "rgba(52,211,153,.32)" - chipFgDark = "#6ee7b7" - glow = "rgba(110,231,183,.22)" - glow2 = "rgba(5,150,105,.10)" - glyphShadow = "rgba(5,150,105,.6)" - statsTint = "#a7f3d0" - bandFg = "#d1fae5" - # White on emerald-600 is 3.77:1, under the 4.5:1 body text needs; the band - # paragraph and the primary button sit on this gradient, so it runs one step - # darker than the accent: 700 -> 800 (white 5.48:1, #d1fae5 4.84:1 at worst). - gradFrom = "#047857" - gradTo = "#065f46" - # The hero's accent words: emerald-400 -> 300 on the dark background (the - # light theme keeps base -> deep, 600 -> 700). - gradTextDark = "linear-gradient(135deg,#34d399,#6ee7b7)" - [params.brand.surfaces] - ink = "#052e22" - slate = "#065f46" - muted2 = "#047857" - line = "#a7f3d0" - bg = "#f0fdf9" - bgSoft = "#d1fae5" - surface = "#ffffff" - navBg = "rgba(240,253,249,.82)" - navBorder = "#a7f3d0" - elevBorder = "#6ee7b7" - ghostHover = "#6ee7b7" - statsBg = "#052e22" - [params.brand.surfacesDark] - ink = "#d1fae5" - slate = "#a7f3d0" - muted2 = "#6ee7b7" - line = "#0a3d2a" - bg = "#04130d" - bgSoft = "#052e22" - surface = "#06291c" - navBg = "rgba(4,19,13,.82)" - navBorder = "#0a3d2a" - elevBorder = "#10593d" - ghostHover = "#10593d" - statsBg = "#06291c" - -# Each repo. Every field is rendered: "name" in the strip under the hero and on -# its card, "kind" as the card's tag, "href" as both links, "blurb" in full. [[params.repos]] name = "ndarray" kind = "library · v0.7.0" @@ -85,3 +29,46 @@ disableKinds = ['taxonomy', 'term', 'rss', 'sitemap', '404'] kind = "assets" href = "https://github.com/go-ndarray/brand" blurb = "Logos and icons for the organization, in SVG / PNG / JPG / ICO / ICNS across colour, white and black variants." + +[params.brand] +# Generated by logos/landing-kit/kit.py from static/img/logo.svg: the colours are +# READ from the logo's gradient, never chosen. Do not add other keys: +# neutrals (backgrounds, surfaces, greys) belong to the shared sheet. +# Contrast: base on white 5.48:1, bright on the dark background 10.05:1. + base = "#047857" + deep = "#024935" + bright = "#34d399" + shadow = "rgba(4,120,87,.7)" + glow = "rgba(4,120,87,.6)" + wash = "rgba(4,120,87,.10)" + washDeep = "rgba(2,73,53,.06)" + chipBg = "#e7faf3" + chipBorder = "#c2f2e0" + chipFg = "#024935" + chipBgDark = "rgba(52,211,153,.14)" + chipBorderDark = "rgba(52,211,153,.32)" + chipFgDark = "#8fe7c7" + +# The API cards (#api) and the count in the hero come from this list, never +# from a number typed beside it. +[[params.families]] + name = "Creation" + what = "`Zeros`, `Ones`, `Full`, `Arange`, `Linspace`, `FromData`, `Eye` and the rest of NumPy's constructors, float64 throughout." +[[params.families]] + name = "Views and indexing" + what = "`Reshape`, `Transpose` and NumPy basic indexing with `Slice(All(), A(0), …)`: strided views that share data, no copy." +[[params.families]] + name = "Broadcasting and ufuncs" + what = "Elementwise arithmetic with NumPy broadcasting, and the unary functions, including `Exp` and `Log` ported from Arm's optimized-routines (≤ 0.51 ULP)." +[[params.families]] + name = "Reductions" + what = "`Sum`, `Mean`, `Max`, `ArgMax`, `CumSum`, `Clip`, `Where`, whole-array or along an axis, multicore on large arrays." +[[params.families]] + name = "Manipulation" + what = "`Concatenate`, `Stack` and the shape operations NumPy users reach for." +[[params.families]] + name = "Linear algebra" + what = "`MatMul` and `Dot` through a panel-packed, cache-blocked GEMM with a go-asmgen SIMD micro-kernel per architecture." +[[params.families]] + name = "Workspace" + what = "An arena for loops: `Use`, `Reset`, `Detach` — results stop costing the garbage collector from one pass to the next." diff --git a/layouts/index.html b/layouts/index.html index 2caa948..7809021 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -11,11 +11,9 @@
go-ndarray is the numpy equivalent for Go: a float64 n-dimensional array with creation routines, strided views that share data, broadcasting elementwise ops and ufuncs, reductions (incl. arg / cumulative / clip / where), manipulation, and linear algebra (a panel-packed, cache-blocked GEMM) — all in pure Go, with cgo disabled.
The lines in the hero, built into a program against the published module and run. Views share data, broadcasting stretches row over both rows, and MatMul goes through the same cache-blocked SIMD kernel as a 4096² product. The output below is a real run, with nothing edited.
# go-ndarray v0.7.0 — each step of the example, printed +$ go run ./ndcheck +m Array(shape=[2 3], data=[0 1 2 3 4 5]) +m + row Array(shape=[2 3], data=[10 21 32 13 24 35]) +m.T Array(shape=[3 2], data=[0 3 1 4 2 5]) +sum(m,0) Array(shape=[3], data=[3 5 7]) +m[:,0] Array(shape=[2], data=[0 3]) +m @ b Array(shape=[2 2], data=[10 13 28 40])+
NumPy is the lingua franca of numerical computing, but in Go the choices are gonum (matrix-centric, amd64-only assembly) or a C extension. go-ndarray brings the n-dimensional array vocabulary — shapes, strided views, broadcasting, ufuncs, reductions and linear algebra — to Go with no cgo, cross-compiling to a static binary everywhere. The hot paths are multicore, and SIMD on amd64, arm64, ppc64le and loong64.
NumPy is the lingua franca of numerical computing, but in Go the choices are gonum (matrix-centric, amd64-only assembly) or a C extension. go-ndarray brings the n-dimensional array vocabulary — shapes, strided views, broadcasting, ufuncs, reductions and linear algebra — to Go with no cgo, cross-compiling to a static binary everywhere. The hot paths are multicore, and SIMD on amd64, arm64, ppc64le, loong64 and riscv64.
CGO_ENABLED=0 across the org. No cgo, no BLAS shim.
Gated in CI, differentially checked against numpy.
Row-major strided arrays, NumPy broadcasting, and NumPy basic-indexing Slice returning strided views that share data.
Workspace arena for loopsBind the inputs with ws.Use, compute as usual, ws.Reset() at the end of each pass: results stop costing the garbage collector anything. On a Zen 3, x + y on 1 024 elements drops from 2.6 to 0.38 µs (2.1× NumPy).
go-asmgen SIMD kernels on amd64 (SSE2, AVX2/FMA picked at run time), arm64 (NEON), ppc64le (VSX) and loong64 (LASX, when the CPU has it); riscv64, s390x and the 32-bit targets run the same pure-Go code those kernels are tested against.
go-asmgen SIMD kernels on amd64 (SSE2, AVX2/FMA picked at run time), arm64 (NEON), ppc64le (VSX) and loong64 (LASX, when the CPU has it) and riscv64 (RVV, when the CPU has it); riscv64, s390x and the 32-bit targets run the same pure-Go code those kernels are tested against.
Exp/LogPorts of Arm's optimized-routines: worst 0.504 / 0.508 ULP against a 300-bit reference, and correct where Go's amd64 math.Exp returns +Inf (golang/go#81995) and math.Log is wrong on subnormals (golang/go#56600).
The suite runs on amd64, arm64 and 386 natively and on riscv64/loong64/ppc64le/s390x/arm under qemu, on Linux, macOS and Windows, and compiles for every GOOS/GOARCH pair Go supports; v0.2.0 was also run on real amd64, arm64, ppc64le, riscv64 and loong64 hardware.
Ruby has no cgo-free ndarray (Numo::NArray and NMatrix are C extensions) and gonum's optimized assembly is amd64-only. go-ndarray pairs a portable pure-Go core with multicore fan-out and SIMD kernels.
{{ .what | markdownify }}
No cgo, no BLAS shim. A portable pure-Go core, multicore, with SIMD kernels on amd64, arm64, ppc64le and loong64 and a Workspace arena that takes the garbage collector out of loops. Faster than NumPy on whole-array and row reductions, on large Exp and on MatMul against single-threaded BLAS, slower against 16-thread BLAS — the numbers are on the performance page.
No cgo, no BLAS shim. A portable pure-Go core, multicore, with SIMD kernels on amd64, arm64, ppc64le, loong64 and riscv64 and a Workspace arena that takes the garbage collector out of loops. Faster than NumPy on whole-array and row reductions, on large Exp and on MatMul against single-threaded BLAS, slower against 16-thread BLAS — the numbers are on the performance page.