Repository navigation
Makie interactivity - #514
Makie interactivity #514Micki-D wants to merge 141 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an experimental Makie.jl-based plotting/visualization extension to BAT, including a new BATVisualizer infrastructure that can be wired into MCMC sampling to enable live/interactive plotting.
Changes:
- Introduces
BATVisualizer/BATVisBackendand integrates it intoBATContext, plus adds visualizer update hooks to the MCMC iteration loop. - Adds the
BATMakieExtpackage extension and Makie “recipes” implemented via Makie’sComputeGraph+SpecApito render corner-style plots and overlays. - Adds helper utilities for marginalization/index selection and plumbs Makie extension debug logging.
Reviewed changes
Copilot reviewed 19 out of 23 changed files in this pull request and generated 24 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/error_log.jl | Adds BATMakieExt to debug logging module set; minor formatting. |
| src/samplers/mcmc/multi_cycle_burnin.jl | Passes callback through to mcmc_iterate!! during burn-in cycles. |
| src/samplers/mcmc/mcmc_state.jl | Mostly formatting/whitespace; no functional changes noted. |
| src/samplers/mcmc/mcmc_sample.jl | Initializes/upgrades visualizer integration during sampling; adds output merge/append helpers. |
| src/samplers/mcmc/mcmc_algorithm.jl | Adds update_visualizer + callback kwargs to mcmc_iterate!! and invokes them each step. |
| src/samplers/mcmc/chain_pool_init.jl | Keyword formatting changes; no functional changes noted. |
| src/plotting/vsel_processing.jl | Formatting changes; marginalization utilities unchanged logically. |
| src/plotting/valueshapes_utils_internal.jl | Minor signature formatting; adjusts unshaped error message (currently buggy). |
| src/extdefs/makie_defs.jl | Adds Makie-related public type/function stubs for the extension. |
| src/extdefs/extdefs.jl | Includes makie_defs.jl. |
| src/algotypes/bat_visualizer.jl | Introduces BATVisualizer and backend abstractions. |
| src/algotypes/bat_context.jl | Adds visualizer::BATVisualizer to BATContext and updates constructors/helpers. |
| src/algotypes/algotypes.jl | Includes new bat_visualizer.jl. |
| Project.toml | Adds Makie as a weak dep + extension entry. |
| ext/BATMakieExt.jl | New Makie extension module wiring in Makie impl files and defining extension entrypoint. |
| ext/makie_impl/makie_plotting.jl | Includes Makie implementation files and imports Makie. |
| ext/makie_impl/bat_makie_recipe.jl | Defines recipe/cell status machinery and generic API hooks. |
| ext/makie_impl/makie_visualizer.jl | Implements the live Makie visualizer using ComputeGraph and async updates. |
| ext/makie_impl/makie_stats.jl | Implements stats overlay recipes (mean/std/cov/errorbars/pdf) for Makie. |
| ext/makie_impl/makie_scatter.jl | Implements scatter recipe. |
| ext/makie_impl/makie_kde.jl | Implements KDE + quantile KDE recipes. |
| ext/makie_impl/makie_hist.jl | Implements histogram + quantile histogram + hexbin recipes. |
| ext/makie_impl/makie_plot_samples.jl | Adds a Makie recipe (convert_arguments) and a bat_makie_plot helper for samples. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## makie-core-fixes #514 +/- ##
===================================================
Coverage ? 81.28%
===================================================
Files ? 144
Lines ? 11206
Branches ? 0
===================================================
Hits ? 9109
Misses ? 2097
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
a863c3c to
41acc14
Compare
…() for density samples
Rebuild owned cell plots when polygon content changes so fills cannot be appended above reused statistical overlays. Add shared Cairo and native GL window regressions, and declare all direct optional-test imports. Validated against the exact committed source: 59 primary cases, 12 selected floor cases, and native GL plus active live-publication checks on both pins. The restored baseline reuse rule is rejected by the new regression.
BJMCox
left a comment
There was a problem hiding this comment.
Reviewed 50ec5352 against 3647462b. Six reproducible findings are attached to the relevant changed lines, each with its MRE and proposed fix.
The full core suites pass on Julia 1.13.3: base 1,240 tests, head 105,706 tests. The MREs expose failures outside that coverage. Plotting checks used CairoMakie. Native GL was not exercised. The x86 finding uses current CI evidence.
I recommend addressing these six findings before merging. Non-blocking improvements are in a separate review comment.
| new_vars = _vsel_after_toggle(active_vars[], i, j, is_on) | ||
|
|
||
| if is_on && length(new_vars) > N_max | ||
| updating_programmatically[] = true | ||
| cb.checked[] = false |
There was a problem hiding this comment.
The picker shows only the first ten variables but retains the full initial selection. With valid vsel=[11] and N_max=1, variable 11 consumes the only slot and has no checkbox to remove it.
This capacity check then rejects every visible replacement. In the MRE, selecting variable 1 resets its checkbox to false and leaves the axis at $v_11$. None of the 55 checkboxes represents variable 11.
Make every selected variable removable. A clear-selection control or visible slots for selected high indices can preserve the compact picker.
Reproduced through the public plotting entry point at 50ec5352, using headless CairoMakie 0.15.16 and Makie 0.24.16.
MRE: an 11-dimensional sample and one available plot slot
using BAT, CairoMakie, Makie
function main()
samples = DensitySampleVector(
v = [Float64.(1:11), Float64.(2:12)],
logd = zeros(2),
)
recipes = (upper = Hist2D, diagonal = Hist1D, lower = Hist2D)
fig = bat_makie_plot(samples, recipes, [11], 1; display = false)
try
axis = only(filter(x -> x isa Axis, fig.content))
first_visible = first(filter(x -> x isa Checkbox, fig.content)) # variable 1
before = String(axis.xlabel[])
first_visible.checked[] = true # request variable 1
@show before String(axis.xlabel[]) first_visible.checked[]
finally
empty!(fig)
end
end
main()
BJMCox
left a comment
There was a problem hiding this comment.
Separate non-blocking suggestions for 50ec5352:
- Keep the weight checks and reduce allocation when no rows are removed. The new guards protect against invalid mass and overflow. With one million uniform weights,
drop_low_weight_samplesstill returns the original object but allocates 8,143,008 bytes at head versus zero at base. Each warmed result repeated three times. Validate the weights, then return early when their extrema prove that no filtering is needed. See weight normalization and the caller. This probe measures allocation, not throughput.
Allocation MRE: run at base 3647462 and head 50ec535
using BAT
function filter_identity_allocations(n)
samples = DensitySampleVector(v=fill([0.0], n), logd=zeros(n), weight=ones(n))
BAT.drop_low_weight_samples(samples) # Compile before measuring.
bytes = [@allocated(BAT.drop_low_weight_samples(samples)) for _ in 1:3]
@assert BAT.drop_low_weight_samples(samples) === samples
return (rows=n, allocated_bytes=bytes)
end
results = filter_identity_allocations.((1_000, 100_000, 1_000_000))
foreach(println, results)
results-
Skip timing storage when timing is absent. Shared static setup is useful. However,
_setup_static_graphcreates one empty timing vector per row even whenstep_intervals=nothing. It groups those vectors, then suppliessample_timing=nothing. Gate that construction on available timing. This observation follows from the source. No whole-figure speedup was measured. -
Connect the numerical tests to public behavior. The independent HDR, KDE scaling, KS, and bias-correction oracles are strong. Keep short public tests for disabled overlays, zero-weight recording, and constant log-density offsets. The inline MREs provide those cases. In the precompile workload tests, retain process isolation and cleanup assertions. Trim exact private-call counts where they constrain implementation without protecting supported behavior.
-
Make coverage match the optional tests that run. The bounded subprocess runner and separate optional jobs are useful. The Makie and Plots jobs currently omit coverage collection and upload. Enable both, including child Julia commands. Zero reported coverage does not mean those tests are absent. Keep the Cairo-only scope explicit and record native GL and real process-interruption checks separately. The latest Makie jobs took about 50–53 minutes against a 60-minute limit. Preserve isolation for lifecycle cases, and consider grouping deterministic numerical cases where isolation is unnecessary.
-
Use one credible-level policy across recipes. Histograms accept probability one and reject invalid levels. KDE recipes silently discard levels outside
0 < p < 1. Share validation and endpoint handling, or document the KDE restriction. The probe below returns zero filled polygons forlevels=[1.0], despite producing 2,048 curve points. This is an interface consistency suggestion. The current validation promise is specific to histograms.
MRE: KDE credible-level endpoint
using BAT, CairoMakie
ext = Base.get_extension(BAT, :BATMakieExt)
x = reshape([0., 1., 2., 3.], 1, :)
w = ones(4)
cfg = merge(BAT._default_makie_diagonal_config(), (levels=[1.0],))
result = ext.compute_plotting_primitives(
view(x, :, :), view(w, :), BAT.QuantileKDE1D(),
ext.LiveRecipe(), ext.LiveCell(), cfg)
@show length(result.polys) length(result.full_line)-
Add a Makie release note under an unreleased heading. The experimental plotting and live visualization features deserve an entry. The current new fixes sit under v5.0.0, although the file already contains v5.1.0 and v5.2.0. Place these changes under the intended next release.
-
Narrow the new coordinate promise or complete its handling. The new documentation permits nonfinite coordinates where weight is zero. Automatic
MarginalDistedges still inspect those rows. Positive mass at 5 plus a zero-weightInfrow causes an empty fallback histogram and an error. This edge-selection behavior exists at the base, so it is excluded from the introduced functional findings. Clarify the new promise or derive automatic edges from positive-weight rows. Preserve explicit-edge behavior.
MRE: the new documented coordinate rule and automatic bins
using BAT
samples(x, w) = DensitySampleVector([[v] for v in x], zeros(length(x)); weight=w)
positive_only = samples([5.0], [1.0])
with_zero_weight_row = samples([5.0, Inf], [1.0, 0.0])
@assert BAT.MarginalDist(positive_only, 1; bins=4) isa BAT.MarginalDist
try
BAT.MarginalDist(with_zero_weight_row, 1; bins=4)
println("The zero-weight row was accepted")
catch err
println("Actual: ", sprint(showerror, err))
end
println("Expected under the new documented coordinate rule: the zero-weight row does not make the marginal distribution fail")- Remove two trailing blank lines.
git diff --check 3647462b...50ec5352reports new EOF blank lines inmakie_hist.jlandmakie_plot_samples.jl.
BJMCox
left a comment
There was a problem hiding this comment.
Five further reproduced issues at 50ec5352, with standalone MREs and proposed fixes attached to the relevant lines. Plotting checks used CairoMakie. The sample-filter regression and plain transformed-posterior control were also checked against exact base 3647462b.
| vals, vecs = eigen(Σ) | ||
| stds = sqrt.(clamp.(vals, 0, Inf)) |
There was a problem hiding this comment.
This eigendecomposition can lose a positive eigenvalue, but the subsequent checks accept it as a genuinely singular direction. The resulting covariance ellipse silently understates spread.
The MRE uses four corners (±a,±b), with a=2^500, b=2^-500, and weights [3,1,1,3]. Its exact mean is zero and its covariance is [a² 1/2; 1/2 b²]. The correlation determinant is 3/4, so the population is nonsingular. Every coordinate and covariance entry is finite and normal in Float64.
The dense solver returns [0,a²]. The zero standard deviation bypasses the collapsed-axis check at line 111. The public figure renders successfully, but one axis becomes (0,0)-(0,0) and the small-coordinate half-extent becomes 0.49998442344707816*b instead of b. The curve's discretization error is below 0.013%, which cannot explain the roughly 50% error.
The eigensolver's range limitation is upstream. BAT should detect the lost covariance before displaying it, consistent with the overlay admission policy. Please verify the decomposition in each coordinate's scale, or use a stable scaled construction. Preserve genuine singular covariance. A whole-matrix relative norm can hide the small coordinate.
Reproduced through bat_makie_plot and its statistics toggle on Julia 1.13.3, OpenBLAS 0.3.30, Makie 0.24.16, and CairoMakie 0.15.16. The same weighted corners in unit coordinates give the correct ellipse.
MRE
using BAT, Makie, CairoMakie, LinearAlgebra, Test
function covariance_axis_loss()
a, b = 2.0^500, 2.0^-500
coordinates = [a -a a -a; b b -b -b]
source = DensitySampleVector([collect(v) for v in eachcol(coordinates)],
zeros(4); weight=[3.0, 1.0, 1.0, 3.0])
recipes = (upper=QuantileHist2D, diagonal=QuantileHist1D, lower=QuantileHist2D)
figure = bat_makie_plot(source, recipes, [1, 2], 2; display=false)
try
first(filter(x -> x isa Toggle, figure.content)).active[] = true
axis = only(filter(x -> x isa Axis, contents(figure[1, 1][1, 2])))
ellipse = only(filter(x -> x isa Makie.Lines, axis.scene.plots))[1][]
principal_axes = only(filter(x -> x isa Makie.LineSegments, axis.scene.plots))[1][]
image = Makie.colorbuffer(figure) # The public figure renders successfully.
# Population oracle: exact represented inputs and weights give mean 0,
# variances a^2 and b^2, and covariance a*b/2 == 1/2. Thus the true
# ellipse has horizontal half-extent b in this transposed upper cell.
# Its correlation determinant is 1 - (1/2)^2 == 3/4 > 0.
expected_covariance = [a^2 0.5; 0.5 b^2]
observed_ratio = maximum(abs, first.(ellipse)) / b
evidence = (; expected_covariance,
eigenvalues=eigvals(expected_covariance), principal_axes,
expected_small_coordinate_extent=b,
observed_small_coordinate_extent=maximum(abs, first.(ellipse)),
observed_ratio, rendered_size=size(image))
@show evidence
# Two hundred circle nodes imply <0.00013 relative discretization error.
# The reviewed head returns 0.49998442344707816, with one axis (0,0)-(0,0).
@test isapprox(observed_ratio, 1.0; rtol=0.001)
return evidence
finally
empty!(figure)
end
end
covariance_axis_loss()| lo, hi = Float64(rawlo), Float64(rawhi) | ||
| ((isfinite(rawlo) && !isfinite(lo)) || (isfinite(rawhi) && !isfinite(hi))) && | ||
| throw(ArgumentError("KDE support bounds must be representable as Float64")) |
There was a problem hiding this comment.
Converting support bounds here can round them inward before membership is checked against the original observations. A valid BigFloat boundary observation then gets rejected as outside its own declared support.
In the MRE, support equals the first and last BigFloat observations, parsed from 0.1 and 0.3. Float64 0.1 exceeds the original lower endpoint, while Float64 0.3 lies below the original upper endpoint. The later membership check compares the original values against that smaller interval. The public graph also stores the narrowed bounds.
The public call throws KDE positive-weight coordinates must be finite and inside hard support. Keeping the original observations and widening only the numerical bounds by one Float64 ULP succeeds and renders. These bounds are far from Float64's range limits.
Please validate original coordinates against original bounds, then convert validated coordinates and bounds consistently for grid construction. Retain conversion checks and rejection of points truly outside the original support. Expanding the membership interval alone would weaken that guarantee.
The MRE proves original membership, checks the direction of rounding, and renders the nearby control through CairoMakie.
MRE
using BAT, Makie, CairoMakie, Test
function kde_support_rounding()
values = parse.(BigFloat, ["0.1", "0.15", "0.2", "0.25", "0.3"])
support = (first(values), last(values))
@assert all(x -> support[1] <= x <= support[2], values)
@assert Float64(support[1]) > support[1]
@assert Float64(support[2]) < support[2]
source = DensitySampleVector([[x] for x in values], zeros(length(values)))
recipes = (upper=Scatter2D, diagonal=KDE1D, lower=Scatter2D)
failure = try
figure = bat_makie_plot(source, recipes, [1], 1;
support=[support], display=false)
empty!(figure)
nothing
catch err
err
end
# The original BigFloat coordinates pass all other numerical admission and
# render successfully when only the bounds expand by one Float64 ULP.
control_support = (prevfloat(Float64(support[1])), nextfloat(Float64(support[2])))
control = bat_makie_plot(source, recipes, [1], 1;
support=[control_support], display=false)
control_size = try
size(Makie.colorbuffer(control))
finally
empty!(control)
end
error_message = isnothing(failure) ? nothing :
last(split(sprint(showerror, failure), '\n'))
@show (all_observations_inside_original_support=true,
converted_support=Float64.(support), error_message, control_size)
# Valid boundary observations must not become outside-support observations
# solely because the renderer needs Float64 endpoints.
@test isnothing(failure)
end
kde_support_rounding()Skip exactly zero-weight mean contributions before multiplying nonfinite log densities. Admit finite, nonvanishing subnormal MCMC score moments while preserving strict parameter and weight checks and failure atomicity. Review: #514 (comment) Both reviewer reproducers and 81,930 assertions across seven selected statistics/sampler test files pass on Julia 1.13.2 and 1.10.12.
Include statistics visibility in graph dependencies and activate statistics only for a directly selected recipe or a visible overlay. Preserve numerical checks for requested statistics and cover static and live update paths. Addresses PR #514, R1-02: #514 (comment) Validation: 1,512 assertions pass on each of Julia 1.13.2 and 1.10.12.
Stream observations and enroll cancellation independently of output storage. Preserve outputs=nothing when stabilization samples are discarded. Addresses PR #514 R1-03: #514 (comment) Validated with the reviewer memory probe on Julia 1.13.2 and 1.10.12, seven core test files and two headless Makie cases on both pinned lanes.
Add Clear selection so coordinates outside the capped picker can be removed and replaced. Share checkbox selection updates and lifecycle ownership. Addresses PR #514 R1-04: #514 (comment) Validated with 1,502 focused assertions per pinned Julia/Makie lane, plus the amended public reproducer and rendered figure checks on both lanes.
Add the canonical Internal API entry required by the strict documentation build. Review: #514 (comment)
Keep positive counts within native Int limits, retain wider positive cases on 64-bit Julia, and check individual and aggregate count overflow. Review: #514 (comment)
Use InitFromTarget with the private display RNG when a pushforward source has no direct sampler. Preserve directly sampleable sources and display coordinates, with domain and live sampling parity regressions. Review: #514 (comment)
Handle zero mass before the uniform-weight marker-size shortcut shared by Scatter2D and ChainScatter2D. Cover direct zero-mass populations and Apply/All transitions through both public plotting paths. Addresses R2-02: #514 (comment)
Avoid fixed-width rational intermediate overflow in plotting normalization while retaining validation, original-weight cutoff ties and logarithmic normalization. Cover the reviewer example and plotting consumers. Addresses R2-03: #514 (comment)
Verify the reconstructed covariance in each coordinate’s marginal scale before drawing geometry. Preserve genuine singular populations and cover public overlay admission with the reviewer example. Addresses R2-04: #514 (comment)
Validate observations against original support before converting validated bounds and coordinates to Float64 for numerical grid construction. Preserve explicit and inferred support precision through graph setup. Review: #514 (comment)
There was a problem hiding this comment.
Rechecked bd2a6b0. All eleven findings from the 26 October reviews are addressed. The covariance fix now explicitly rejects inaccurate ellipses.
Local Julia 1.13.3 validation passed 105,935 core checks and 828 focused Makie assertions. Plotting checks used CairoMakie.
The remaining x86 statistics failure has four failed assertions and two errors. The inline comments identify three test expectations and one range-construction problem. These predate the latest fix batch.
BJMCox
left a comment
There was a problem hiding this comment.
The remaining coverage suggestion is attached separately to the optional Makie job.
|
Apart from the new visualization functionality, several sites in the core package have been altered in the course of the live visualization changes and the numerical hardening. Here is a list of these debatable changes. At
|
Make the extracted core-fixes branch an ancestor so PR #514 can review visualization changes against it. Preserve the existing Makie implementation and bring in the verified marginal-domain and uniform-filter corrections.
|
I’ve moved the shared core changes into The core branch contains the statistical and numerical fixes, density/AD changes, transform execution, and nonvisual sampler cleanup. These affect BAT without Makie, so the split lets us review them separately from the visualization APIs and live-display hooks. I also fixed two issues there:
Both full suites passed locally on Julia 1.13.3: 105,636 checks for the core branch and 105,946 for this PR. The 451 Plots checks, four selected Makie cases, and native interruption checks also passed. |
[Experimental][WIP] Add an extension for Makie.jl plotting
This is based on PR #477 using @philippeller 's custom Makie plotting but with dedicated BAT.jl infrastructure for handling the marginalization of samples.
This implements an extension module with reactive Makie.jl recipies that offer more flexible visualization and enable live plotting of MCMC data during sampling.