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 @@
@@ -23,10 +21,10 @@

NumPy-style n-dimensional arrays in pure Go.
float64, multicore + SIMD.

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.

-
{{ len .Site.Params.repos }} repositoriesCGO_ENABLED=0, no BLAS shim100% test coverage
+
{{ len .Site.Params.families }} API families, NumPy's names5 SIMD targetsCGO=0 no BLAS shim
{{/* Lines taken verbatim from the Example section of go-ndarray/ndarray's README at v0.2.2. */}}
@@ -57,16 +55,37 @@

NumPy-style n-dimensional arrays in pure Go.
{{ range .Site.Params.repos }}{{ .name }}{{ end }}

+
+ Ask it, do not take our word +

The example above, actually run.

+

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 run ./ndcheck — zsh
+{{/* Verbatim: go run of the hero's lines against + github.com/go-ndarray/ndarray v0.7.0, on an Apple M4 (arm64), + 2026-10-08. Re-run it and paste its output when the API changes; never + retype a value here. */}} +
# 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])
+
+
+
Why go-ndarray

The n-dimensional array, without cgo.

-

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.

🐹

Pure Go

CGO_ENABLED=0 across the org. No cgo, no BLAS shim.

✅

100% test coverage

Gated in CI, differentially checked against numpy.

🔢

NumPy semantics

Row-major strided arrays, NumPy broadcasting, and NumPy basic-indexing Slice returning strided views that share data.

♻️

Workspace arena for loops

Bind 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).

-
⚡

SIMD on amd64, arm64, ppc64le & loong64

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.

+
⚡

SIMD on amd64, arm64, ppc64le, loong64 & riscv64

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.

🎯

Accurate Exp/Log

Ports 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).

🧪

Multi-arch CI

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.

💎

The cgo-free ndarray that was missing

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.

@@ -74,6 +93,16 @@

The n-dimensional array, without cgo.

+
+ What the module offers +

{{ len .Site.Params.families }} families, NumPy's names.

+
+ {{ range .Site.Params.families }} +

{{ .name }}

{{ .what | markdownify }}

+ {{ end }} +
+
+
100%
test coverage, differentially checked against numpy
@@ -100,8 +129,8 @@

What is here.

The cgo-free ndarray the Go and Ruby ecosystems lacked.

-

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.

- Read the source on GitHub → +

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.

+ {{ partial "icons/github.html" . }}Read the source on GitHub →
diff --git a/layouts/partials/icons/OCTICONS-LICENSE b/layouts/partials/icons/OCTICONS-LICENSE new file mode 100644 index 0000000..aa1fa80 --- /dev/null +++ b/layouts/partials/icons/OCTICONS-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/layouts/partials/icons/github.html b/layouts/partials/icons/github.html new file mode 100644 index 0000000..118ad5b --- /dev/null +++ b/layouts/partials/icons/github.html @@ -0,0 +1,5 @@ +{{- /* GitHub's mark: Octicons "mark-github" (16 px), primer/octicons @primer/octicons-react@19.40.0, + copied verbatim, MIT, (c) GitHub Inc.: the licence is OCTICONS-LICENSE + beside this file. currentColor, so it takes the button's text colour in + both themes; aria-hidden, since the button's text already names GitHub. */ -}} + diff --git a/layouts/partials/styles.html b/layouts/partials/styles.html index 519456b..d50c2a8 100644 --- a/layouts/partials/styles.html +++ b/layouts/partials/styles.html @@ -19,32 +19,22 @@ {{ $deep := $br.deep | default "#054A66" }} {{ $bright := $br.bright | default "#22CCE2" }} {{ $shadow := $br.shadow | default "rgba(0,121,168,.7)" }} +{{- /* Three more that were written into the RULES rather than taken as + parameters, so every org using this sheet glowed cyan whatever its logo + was. Measured on the published amber page: go-pkgx's rendered CSS carried + four cyan literals -- the nav glyph's drop shadow, both hero washes and + the performance hero's. Each default below is the exact text that was in + the rule, so a build with no [params.brand] still emits a byte-identical +