diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index bda43dc8..83fc4aba 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,7 +13,7 @@ concurrency: jobs: test: - name: ${{ matrix.pkg.name }} - Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + name: ${{ matrix.pkg.name }} - Julia ${{ matrix.version }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -25,8 +25,6 @@ jobs: - ubuntu-latest - windows-latest - macOS-latest - arch: - - x64 pkg: - name: SSMProblems dir: './SSMProblems' @@ -40,7 +38,6 @@ jobs: uses: julia-actions/setup-julia@v3 with: version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - name: Cache artifacts uses: julia-actions/cache@v3 diff --git a/GeneralisedFilters/docs/literate.jl b/GeneralisedFilters/docs/literate.jl index feb1e47b..1fd8c921 100644 --- a/GeneralisedFilters/docs/literate.jl +++ b/GeneralisedFilters/docs/literate.jl @@ -14,6 +14,90 @@ Pkg.activate(EXAMPLEPATH) Pkg.instantiate() using Literate: Literate -# Convert to markdown and notebook +# Insert an "Open in Colab" badge (after the title) that opens the notebook published to +# the gh-pages docs. The version subdir matches Documenter's deploy target: `previews/PR` +# for pull-request previews, the tag (e.g. `v1.2.3`) for tagged releases, otherwise `dev`. +const REPO = "TuringLang/SSMProblems.jl" +const PKG = basename(dirname(@__DIR__)) + +function docs_subfolder() + ref = get(ENV, "GITHUB_REF", "") + if get(ENV, "GITHUB_EVENT_NAME", "") == "pull_request" + pr = match(r"^refs/pull/(\d+)/", ref) + pr === nothing || return string("previews/PR", only(pr.captures)) + end + tag = match(r"^refs/tags/(.+)$", ref) + tag === nothing || return only(tag.captures) + return "dev" +end + +const COLAB_BADGE = string( + "# [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)]", + "(https://colab.research.google.com/github/", + REPO, + "/blob/gh-pages/", + PKG, + "/", + docs_subfolder(), + "/examples/", + EXAMPLE, + ".ipynb)", +) + +function add_colab_badge(content) + lines = string.(split(content, '\n')) + idx = findfirst(line -> startswith(line, "# # "), lines) + idx === nothing && return string(COLAB_BADGE, "\n#\n", content) + insert!(lines, idx + 1, "#") + insert!(lines, idx + 2, COLAB_BADGE) + return join(lines, '\n') +end + const SCRIPTJL = joinpath(EXAMPLEPATH, "script.jl") -Literate.markdown(SCRIPTJL, OUTDIR; name=EXAMPLE, execute=true) + +# Top-level `using`/`import` package names, so the notebook can install them on Colab. +function script_packages(path) + pkgs = String[] + for line in eachline(path) + m = match(r"^\s*(?:using|import)\s+(.+)", line) + m === nothing && continue + for part in split(first(split(m.captures[1], ':')), ',') + token = first(split(strip(part), r"\s+as\s+")) + name = strip(first(split(token, '.'))) + isempty(name) || name in pkgs || push!(pkgs, String(name)) + end + end + return sort(pkgs) +end + +# Notebook postprocess: use a generic Julia kernel (so Colab picks its default Julia +# runtime, with no pinned version) and prepend a single Pkg.add for all imported packages +# so the notebook runs on a fresh Colab runtime. +function prepare_notebook(nb) + nb["metadata"]["kernelspec"] = Dict( + "display_name" => "Julia", "language" => "julia", "name" => "julia" + ) + packages = script_packages(SCRIPTJL) + if !isempty(packages) + setup = Dict( + "cell_type" => "code", + "execution_count" => nothing, + "metadata" => Dict(), + "outputs" => [], + "source" => + ["import Pkg\n", string("Pkg.add([", join(repr.(packages), ", "), "])")], + ) + idx = findfirst(cell -> cell["cell_type"] == "code", nb["cells"]) + insert!(nb["cells"], something(idx, lastindex(nb["cells"]) + 1), setup) + end + return nb +end + +# Convert to markdown and notebook +Literate.markdown(SCRIPTJL, OUTDIR; name=EXAMPLE, execute=true, preprocess=add_colab_badge) +# Also emit a runnable notebook; Documenter copies it into the deployed site, under the +# versioned docs dir (dev/, vX.Y/, or previews/PR/ for PR previews). The badge goes on +# the rendered page only; the notebook gets a Colab setup cell instead. +Literate.notebook( + SCRIPTJL, OUTDIR; name=EXAMPLE, execute=false, postprocess=prepare_notebook +) diff --git a/GeneralisedFilters/docs/make.jl b/GeneralisedFilters/docs/make.jl index 117d6cff..72597a59 100644 --- a/GeneralisedFilters/docs/make.jl +++ b/GeneralisedFilters/docs/make.jl @@ -11,7 +11,11 @@ mkpath(EXAMPLES_OUT) # Install and precompile all packages # Workaround for https://github.com/JuliaLang/Pkg.jl/issues/2219 -examples = filter!(isdir, readdir(joinpath(@__DIR__, "..", "examples"); join=true)) +# Only build directories that ship a Literate `script.jl`; other example dirs +# (e.g. script-only scratch examples) are skipped rather than failing the build. +examples = filter(readdir(joinpath(@__DIR__, "..", "examples"); join=true)) do path + return isdir(path) && isfile(joinpath(path, "script.jl")) +end above = joinpath(@__DIR__, "..") ssmproblems_path = joinpath(above, "..", "SSMProblems") let script = "using Pkg; Pkg.activate(ARGS[1]); Pkg.develop(path=\"$(above)\"); Pkg.develop(path=\"$(ssmproblems_path)\"); Pkg.instantiate()" diff --git a/GeneralisedFilters/examples/trend-inflation/utilities.jl b/GeneralisedFilters/examples/trend-inflation/utilities.jl index e3f9e0da..c3a731de 100644 --- a/GeneralisedFilters/examples/trend-inflation/utilities.jl +++ b/GeneralisedFilters/examples/trend-inflation/utilities.jl @@ -17,10 +17,12 @@ end # for normal collections mean_path(paths, states) = _mean_path(identity, paths, states) -# for rao blackwellised particles -function mean_path(paths::Vector{Vector{T}}, states) where {T<:GeneralisedFilters.RBState} - zs = _mean_path(s -> getproperty.(getproperty.(s, :z), :μ), paths, states) - xs = _mean_path(s -> getproperty.(s, :x), paths, states) +# for rao blackwellised particles; `get_ancestry` returns `ReferenceTrajectory`s, which we +# collect into plain vectors before extracting per-time-step state components +function mean_path(paths::AbstractVector{<:GeneralisedFilters.ReferenceTrajectory}, states) + trajectories = map(collect, paths) + zs = _mean_path(s -> getproperty.(getproperty.(s, :z), :μ), trajectories, states) + xs = _mean_path(s -> getproperty.(s, :x), trajectories, states) return zs, xs end diff --git a/SSMProblems/docs/literate.jl b/SSMProblems/docs/literate.jl index 879ec107..f985ba18 100644 --- a/SSMProblems/docs/literate.jl +++ b/SSMProblems/docs/literate.jl @@ -13,6 +13,90 @@ Pkg.activate(EXAMPLEPATH) Pkg.instantiate() using Literate: Literate -# Convert to markdown and notebook +# Insert an "Open in Colab" badge (after the title) that opens the notebook published to +# the gh-pages docs. The version subdir matches Documenter's deploy target: `previews/PR` +# for pull-request previews, the tag (e.g. `v1.2.3`) for tagged releases, otherwise `dev`. +const REPO = "TuringLang/SSMProblems.jl" +const PKG = basename(dirname(@__DIR__)) + +function docs_subfolder() + ref = get(ENV, "GITHUB_REF", "") + if get(ENV, "GITHUB_EVENT_NAME", "") == "pull_request" + pr = match(r"^refs/pull/(\d+)/", ref) + pr === nothing || return string("previews/PR", only(pr.captures)) + end + tag = match(r"^refs/tags/(.+)$", ref) + tag === nothing || return only(tag.captures) + return "dev" +end + +const COLAB_BADGE = string( + "# [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)]", + "(https://colab.research.google.com/github/", + REPO, + "/blob/gh-pages/", + PKG, + "/", + docs_subfolder(), + "/examples/", + EXAMPLE, + ".ipynb)", +) + +function add_colab_badge(content) + lines = string.(split(content, '\n')) + idx = findfirst(line -> startswith(line, "# # "), lines) + idx === nothing && return string(COLAB_BADGE, "\n#\n", content) + insert!(lines, idx + 1, "#") + insert!(lines, idx + 2, COLAB_BADGE) + return join(lines, '\n') +end + const SCRIPTJL = joinpath(EXAMPLEPATH, "script.jl") -Literate.markdown(SCRIPTJL, OUTDIR; name=EXAMPLE, execute=true) + +# Top-level `using`/`import` package names, so the notebook can install them on Colab. +function script_packages(path) + pkgs = String[] + for line in eachline(path) + m = match(r"^\s*(?:using|import)\s+(.+)", line) + m === nothing && continue + for part in split(first(split(m.captures[1], ':')), ',') + token = first(split(strip(part), r"\s+as\s+")) + name = strip(first(split(token, '.'))) + isempty(name) || name in pkgs || push!(pkgs, String(name)) + end + end + return sort(pkgs) +end + +# Notebook postprocess: use a generic Julia kernel (so Colab picks its default Julia +# runtime, with no pinned version) and prepend a single Pkg.add for all imported packages +# so the notebook runs on a fresh Colab runtime. +function prepare_notebook(nb) + nb["metadata"]["kernelspec"] = Dict( + "display_name" => "Julia", "language" => "julia", "name" => "julia" + ) + packages = script_packages(SCRIPTJL) + if !isempty(packages) + setup = Dict( + "cell_type" => "code", + "execution_count" => nothing, + "metadata" => Dict(), + "outputs" => [], + "source" => + ["import Pkg\n", string("Pkg.add([", join(repr.(packages), ", "), "])")], + ) + idx = findfirst(cell -> cell["cell_type"] == "code", nb["cells"]) + insert!(nb["cells"], something(idx, lastindex(nb["cells"]) + 1), setup) + end + return nb +end + +# Convert to markdown and notebook +Literate.markdown(SCRIPTJL, OUTDIR; name=EXAMPLE, execute=true, preprocess=add_colab_badge) +# Also emit a runnable notebook; Documenter copies it into the deployed site, under the +# versioned docs dir (dev/, vX.Y/, or previews/PR/ for PR previews). The badge goes on +# the rendered page only; the notebook gets a Colab setup cell instead. +Literate.notebook( + SCRIPTJL, OUTDIR; name=EXAMPLE, execute=false, postprocess=prepare_notebook +)