HyperTools 1.0: architecture refactor + bug hunt#272
Conversation
Extracts .data from the hosted DataGeometry pickles into plain pickles (gitignored rehost/) for re-upload before the DataGeometry deletion (Plan 7 T7). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d() returns raw data Plan 7 Task 7 (final geo-removal). Per Jeremy's 2026-07-04 decision, keep a HIDDEN/internal DataGeometry used ONLY to unpickle the hosted example-dataset geo pickles; users never receive a geo (plot() returns a Figure, load() returns raw data). No dataset re-hosting. - datageometry.py: trim to minimal internal unpickle-only class at its current import path (get_data + minimal __init__ for _load_legacy); "INTERNAL, not public API" docstring; drop plot()/save()/transform()/get_formatted_data() and _maybe_load_strings. - __init__.py: remove the public DataGeometry export (no more hyp.DataGeometry). - io/load.py: base path returns geo.get_data() (raw data); reduce/align/ normalize path returns analyze() output directly (drop the plot() detour). - _shared/helpers.py: remove dead check_geo (+ now-unused import copy). - tools/format_data.py: remove dead DataGeometry import + 'geo' dispatch branch. - tools/text2mat.py: load(corpus) now returns raw data (drop .get_data()). - plot/plot.py: add explicit `import copy` (was leaking via helpers `import *`). - Delete abandoned re-host artifacts (scripts/rehost_example_datasets.py, rehost/ gitignore line). - Rewrite the 26 geo tests: delete test_geo.py; test_load returns raw/analyzed data directly; test_reduce/normalize/describe/format_data geo tests call the function on raw data; retire test_datageometry_plot; fix .get_data() ripples in test_plot/test_align/test_procrustes. Full suite: 318 passed, 0 failed (MPLBACKEND=Agg, py3.12, dw0.5/pandas3.0.3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… verify) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ad API hyp.load() now returns raw data instead of a DataGeometry, and hyp.plot() is a top-level function returning a Figure (or (Figure, Animation) for matplotlib animations) instead of a geo.plot() method. Updates all 20 non-plot_geo gallery examples accordingly: - geo = hyp.load(...) -> data = hyp.load(...) - geo.get_data() / hyp.load(...).get_data() -> data (raw already) - geo.plot(**kw) -> hyp.plot(data, **kw) - the 6 animation examples: ani = geo.plot(animate=...).line_ani -> fig, ani = hyp.plot(data, animate=...) plot_procrustes.py also repoints its procrustes import from the retired hypertools.tools.procrustes shim to hypertools.align.procrustes (matching tests/test_procrustes.py, already updated in Plan 7 Task 2). All 20 examples verified to run standalone under MPLBACKEND=Agg with exit code 0, including save_movie.py's real ffmpeg mp4 write. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oved)
plot_geo.py was written around the removed DataGeometry/`geo` object
(geo.plot(), geo.save(), geo.transform(), geo.get_data()). Rewrite it to
demonstrate the actual 2.0 hyp.plot() return shapes: a plain Figure by
default, and {'fig','xform_data','models'} when return_model=True.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xecute Migrate all 13 hand-authored docs/tutorials/*.ipynb notebooks off the retired DataGeometry/geo API onto hyp.plot's figure-return API (fig = hyp.plot(...), fig, ani = hyp.plot(..., animate=True), raw hyp.load(...), return_model=True bundles), then re-execute every notebook end-to-end via nbconvert so committed outputs reflect real runs against this branch. - align/analyze/cluster/normalize/reduce/plot: drop geo/.get_data(), rename geo-> fig; plot.ipynb also repoints hyp.tools.procrustes to the 2.0 location (hypertools.align.procrustes); reduce.ipynb updated for the ndims=None (no forced reduction) default and a single-array-unwrap quirk in hyp.reduce. - conversation_trajectories/modern_sklearn_dynamics/streaming_data: adopt the (fig, ani) animate return tuple and fig.stream_info dict. - hugging_face_embeddings/wikipedia_embeddings/text: geo->fig renames, wiki.get_data() -> raw list indexing; enlarged text.ipynb's SOTU sample so UMAP has enough points for a stable neighbor graph. - geo.ipynb repurposed (matching Task 2's plot_geo.py) into "Working with plot outputs (figures & return_model)", replacing the DataGeometry-object walkthrough with figure/return_model/animate-tuple/save patterns. Re-ran scripts/add_colab_install_cell.py to refresh the branch-aware Colab install cells (now correctly dev-2.0-refactor instead of stale dev-2.0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… placed right on both backends BUG 3 (plotly markers): _MARKER_SYMBOLS in plotly_backend.py was a hand-maintained subset of matplotlib's marker set, omitting , 1 2 3 4 P X | _. _parse_fmt therefore failed to recognize those as markers and fell through to mode='lines', so e.g. hyp.plot(d, ',', backend='plotly') drew solid lines instead of pixel markers. Completed the table so every printable matplotlib marker char maps to a valid plotly symbol, and added 3D fallbacks for the new non-3D-legal symbols. matplotlib already handled all markers; both backends now agree. BUG 1 (legend clipping): the right-side legend (ax.legend(loc='center left', bbox_to_anchor=(1.02, 0.5))) was clipped off the figure's right edge on 3D plots because plt.tight_layout() reserves room for an outside legend on 2D axes but not on Axes3D. Added _fit_right_legend(), called after tight_layout for static matplotlib plots with a legend: it measures the rendered legend and pulls the subplot's right edge leftward via subplots_adjust until the legend fits fully within the canvas (no-op when it already fits). Tests: added plotly marker regressions + a matplotlib/plotly parity test (tests/test_interactive.py) and legend-placement tests for 2D/3D incl. long labels (tests/test_plot.py). Full suite 325 passed (was 318). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds examples/plot_shape_morph.py, a self-contained sphinx-gallery example that morphs between all seven shapes-zoo point clouds using Hungarian-matched point correspondences and a FuncAnimation with a rotating camera. Uses the current 2.0 API only (hyp.load returns raw arrays; hyp.plot(..., show=False) returns a bare Figure) -- no DataGeometry/get_data/line_ani. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…~6x too fast) Export writers derived per-frame delay from the (possibly subsampled) frame count (1000*duration/n_frames), so any frame subsampling in the export path collapsed total playback -- exported gifs played too fast. The committed dev/plotly_spin_demo.gif was written subsampled (45 frames, not 90). Decouple per-frame delay from frame count: _export_animation_file now sets frame_ms = round(1000/frame_rate) (the true inter-frame interval) over the FULL fig.frames, and the video branch uses fps=frame_rate directly. Frame subsampling stays only on the interactive-HTML embedding (_show_sphinx_gallery) and vector-SVG paths -- never the exported gif/png/mp4. Matplotlib path already saved every frame at PillowWriter(fps=frame_rate); documented the invariant. Regenerated dev/*.gif with the fixed code. Added regression tests asserting both the full frame count (frame_rate*duration) and total playback ~= duration on each backend. Interactive 900-frame pacing parity unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…api.rst + stale stubs
- api.rst: drop DataGeometry section, tools.procrustes->align.procrustes
- delete orphaned autosummary stubs for removed symbols (DataGeometry, tools.{reduce,load,cluster,procrustes})
- regenerate all gallery examples + tutorials under the 2.0 API (fixed plotly markers, unclipped 3D legends, full-length animation gifs, + shapes-zoo morph example)
- refresh docs/auto_examples/spin.gif to full-length (was stale 75-frame)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Installs Playwright (chromium) into .venv and adds scripts/verify_docs_playwright.py, which serves the already-built docs/_build/html/ over a real local HTTP server and drives it with a real headless Chromium browser -- no mocks. It asserts, on the gallery index, 5 example pages (2 static, 2 mp4-animated, 1 plotly-animated), and 2 tutorial pages: example images/thumbnails and animation frames decode to non-zero dimensions and are pixel-content non-blank (real stddev check via PIL/numpy, not existence-only); animated pages embed a real <video> or a rendered Plotly animation (Plotly.animate/addFrames calls); and the "Open in Colab" affordance is branch-aware (contains dev-2.0-refactor) on both the example-page badge and the tutorial-page install cell. All 8 pages pass. Screenshots + PR_EVIDENCE.md land in docs/images/v2.0-docs/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nor)
- io/load.py: any({...}) built a set of arg values; reduce/align dicts are
unhashable, crashing hyp.load with reduce={dict}/align={dict}. Switched
both guards to any(v is not None and v is not False for v in (...)).
- plot/plot.py: HDBSCAN n_clusters guard checked the raw `cluster` arg
(a dict in the dict-form branch) instead of the resolved model name,
letting n_clusters leak into HDBSCAN params and crash.
- plot/plot.py: return_model=True + animate=True dropped the only reference
to the FuncAnimation; the return_model bundle now carries 'animation'.
- plot/matplotlib_backend.py: update_lines_parallel hardcoded elev=10
instead of using the elev fargs parameter.
- core/model.py: fixed the apply_model(mode='auto') docstring to match the
actual (intended) predict_proba-first ordering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ver clipped Jeremy reported the default zoom for ANIMATED plots could clip the wireframe bounding box on the right during rotation. Measuring a full 360 deg spin on both backends (inked-bbox margin per frame, 90 azimuths, 640x480) showed NEITHER backend actually clips -- matplotlib kept an 80px min right margin, plotly 95px -- but Jeremy asked for a slight, symmetric zoom-out for comfort, so apply one conservatively (static plots unchanged). matplotlib: new _anim_box_zoom(zoom) = 9/(9-zoom) -> 1.125 at the default zoom=1 (was 10/(9-zoom)=1.25), used by update_lines_parallel/spin/serial. Static plots never call set_box_aspect(zoom=...), so they are unaffected. plotly: new _anim_zoom_r(zoom) = _zoom_r(zoom) * 1.1 pulls the animation camera ~10% farther back; used by the initial camera AND every frame when animating. Static plots keep _zoom_r unchanged (byte-identical). Result (full-rotation min right margin, before -> after): matplotlib 80 -> 95 px; plotly 95 -> 119 px. All edges gain margin, symmetrically, without leaving excessive whitespace. Regenerated dev/animation_demo.gif and dev/plotly_spin_demo.gif with the fixed code. Added regression tests: test_anim_box_zoom_is_zoomed_out, test_spin_box_never_clipped (real per-frame pixel bbox check across a full rotation), and test_plotly_animation_zooms_out_vs_static. Pacing/parity and animation-export suites stay green (73 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ries) Animated line plots draw a faint alpha=0.3 trail artist per dataset in addition to the in-focus moving window. Both carried the dataset's label, so ax.legend() collected each label twice. Set trail labels to '_nolegend_' so only the in-focus lines appear. The legend is built once from the upfront line artists, so it shows the static union of in-focus datasets and never changes across frames (covers the serial-animation case). plotly already tagged trails showlegend=False. Adds regression tests: default + chemtrails animations show exactly one legend entry per dataset; serial animation legend is the static union. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #259: importing hypertools no longer mutates global matplotlib rcParams. Removed the module-level pdf.fonttype=42 in matplotlib_backend.py; the editable-PDF default is now set inside backend.py's manage_backend scope (after its rcParams snapshot) so it applies to hypertools' own saves but is restored afterward, never leaking. - #223: update_position() no longer calls Axes3D-only get_proj() on 2D labeled plots, and the annotate_plot/update_position tuple shapes now match for both 2D (3-tuple) and 3D (4-tuple) -- fixes AttributeError/ ValueError on button-release over a 2D labeled plot. - #146 & #190: cluster() injects n_clusters only when the resolved model's signature accepts it (was hardcoded != 'HDBSCAN'), and MeanShift/DBSCAN/ OPTICS/AffinityPropagation are registered -- density/bandwidth clusterers now work by name and by class. - #148: show=False now closes the figure (when the user didn't supply an ax), removing it from pyplot's manager so it doesn't reappear via flush_figures/plt.show(); returned Figure/animation stay valid. - #214: load() docstring uses wiki_model (matches the dict key). - reduce.py: passing a custom sklearn class/instance to reduce= no longer raises UnboundLocalError (model_params was undefined in the else branch); class vs. instance handled correctly. Unblocks the custom-model path (#162). Adds regression tests: tests/plot/test_matplotlib_backend_bugs.py (#259/#223/ #148), plus cluster (MeanShift/DBSCAN) and reduce (custom class/instance) cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-runs the 7 matplotlib animated examples (animate, animate_MDS, animate_spin, chemtrails, precog, save_movie, plot_shape_morph) plus the hand-maintained spin.gif so the committed mp4s/gifs reflect the current backend: the animated 3D bounding box is zoomed out slightly (set_box_aspect 1.25 -> 1.125 + full-canvas axes) for a comfortable margin at every rotation angle, and animation legends no longer duplicate the in-focus line with its faint trail. plot_geo re-rendered (return_model docstring now lists the 'animation' key). animate_plotly retains its cached artifact (plotly 3D auto-fits; its 900-frame kaleido gif export is prohibitively slow here). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
notes/issues-to-close-on-merge.md catalogs all 67 open GitHub issues triaged against dev-2.0-refactor (close-on-merge list, bugs fixed this branch, and what to leave open). docs/images/v2.0-anim-fix/ holds before/after frames for the animated bounding-box zoom-out and the de-duplicated animation legend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fit
- Windows CI: datawrangler 0.5 evaluates os.getenv('HOME') at import time to
build its data dir; HOME is unset on Windows so dw (and hypertools) crashed
on import. Set HOME=expanduser('~') before importing dw in core/configurator.
- mpl 3.11 (Python 3.11+): plt.close(fig) (the show=False fix) resets the
figure canvas, so update_position() crashed on fig.canvas.renderer. Guard the
renderer (skip the reposition if absent) and render test_spin_box_never_clipped
through an explicit Agg canvas so buffer_rgba is always available.
- Legend clipping (gallery/example figs): _fit_right_legend now WIDENS the
figure (keeping the plot's size/position) until the legend has a right-edge
margin, instead of shrinking the axes and giving up at a floor. Crucially it
measures the rasterized pixels under DEFAULT rcParams -- hypertools draws
inside a seaborn rc_context whose font is narrower than the font the figure
is actually saved with downstream, so measuring under seaborn made wide
legends look like they fit when they clipped. Long labels / many entries now
stay fully visible (verified R>=12px margin on 3D+2D, short legends unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Asserts the SAVED figure keeps a right-edge margin for long labels / many entries -- fails on the pre-fix _fit_right_legend, passes with the widen-under- default-rcParams fix. The existing get_window_extent tests measured under the seaborn font and missed the clip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fix plot_legend, plot_PPCA, plot_missing_data re-rendered: legends now keep a consistent right-edge margin (figure widened as needed) instead of relying on axis-shrink. Regression-tested in tests/test_plot.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ned fig Two remaining CI failures were fallout from the GH #148 close: - Windows test_spin_box_never_clipped: on Windows the animate backend switch to TkAgg actually succeeds, so plt.close() destroys the FuncAnimation's real Tk timer; the animation's pending first-draw hook then crashes any later draw of the returned figure ('NoneType' object has no attribute 'start' / 'add_callback'). Animated figures are now exempt from the show=False close (the #148 complaint was about static figures; animations need their timer alive for playback). Headless Linux/mac fall back to Agg, which is why only Windows hit this. - Ubuntu 3.12 screenshot step (12/13 failed 'produced no matplotlib figures'): the harness discovered figures via plt.get_fignums(), which the #148 close empties. capture() now prefers the RETURNED figure(s) (Figure, (fig, ani), or return_model dict), falling back to the pyplot registry. Verified: spin + backend-bugs tests pass; generate_baseline_screenshots.py 13/13 succeeded. Also folds in gallery zips regenerated by the legend rebuild. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tream) dw 0.5.1 resolves ContextLab/data-wrangler#32 (config datadir built from os.getenv('HOME'), None on Windows) via os.path.expanduser. Bump the base and [text] pins; keep the zero-risk HOME setdefault guard for environments still on 0.5.0, with the comment updated to reflect the upstream fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0.5.1) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The modernization was over-numbered: this is HyperTools 1.0, not 2.0. - pyproject version 2.0.0.dev0 -> 1.0.0.dev0 (__version__ now reports 1.0.0.dev0) - All 'HyperTools/hypertools 2.0' prose -> 1.0 across library docstrings, examples, tutorials, committed gallery files, readme, notes (surgical phrase replacements; Apache-2.0, numpy/pandas versions, numeric literals untouched). 'hypertools < 2.0' comparisons -> '< 1.0' (pre-rewrite releases are 0.x, so the comparisons stay correct). - Renamed: docs/images/v2.0-* -> v1.0-*, tests/screenshots/*_v2.0 -> *_v1.0, dev/hypertools_2.0_dev*.ipynb -> hypertools_1.0_dev*.ipynb - Branch references (Colab install cells in 65 notebooks, badges, CI workflow triggers, conf.py docs) dev-2.0[-refactor] -> dev-1.0[-refactor]; the git branches are renamed to match. - Suite: 343 passed after the rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… supersedes #271 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…w data Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 2 of the predict/impute plan: pykalman EM+filter forecaster (NaN-tolerant, guarded import for the [predict] extra), sklearn GaussianProcessRegressor forecaster on the time index, and a recursive lagged-feature AutoRegressor supporting a string/class/instance sklearn regressor with MultiOutputRegressor fallback for multivariate targets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion preview (347 fixed) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… examples, live links 13 residual docstring fixes + 29 verified already-fixed by module waves (D11/D12/D13 confirmed findings); every touched example runs as a real doctest; replacement URLs curl-verified 200. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…torials re-executed C1: every README block runs verbatim (5/5, evidence rendered); deps/extras lists match pyproject; BibTeX key; CHANGELOG.md created (1.0.0 + release- audit section); conf.py fixes (scipy URL, copyright 2026, dead extlinks/ modindex); pipeline_order shared-stats caveat. C2: 32 examples fixed + 40 executed green with judged evidence PNGs; plot_sotus RESTORED to the real 29-address SOTU demo; params->kwargs migrations; save_movie no longer drops subject 17; story dispersion numbers recomputed. C3/C4: all 15 tutorials fixed + re-executed fresh IN PLACE, 0 error cells (plot.ipynb hue 8120 critical fixed; cluster/analyze cell fixes; real pylsl outlet for lsl tutorial; live yfinance for stock; prose recalibrated to fresh outputs; notebooks shrank ~47MB via gif refs). Findings: D01-*, D02-* (rst-side), D03..D06-*, D07-001 (critical), D07..D10-*, D13-* (readme/docs links), D14-* incl. CHANGELOG (D14-007) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + orphaned examples/spin.gif Build outputs that should never have been tracked (D02-016); spin.gif unreferenced outside regenerable auto_examples (C2 audit note). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… — 39 items D1: plot() no longer forwards its ndims default with reduce=None (root cause of the streaming-warning regression; 3 notebooks re-executed clean); streaming saves route video extensions to ffmpeg (verified .mp4 on disk); exact stream_max sample counts (no peek); lsl_stream input validation; reduce single-row warning rewritten; describe() max_dims clamp + warning; reduce warns on ndims>n_features skip; autoencoder hyperparameter validation; predict/impute case-insensitive names + all-NaN-column warning; text2mat literal-corpus warning; missing_inds never returns None; model= alias unified across dispatchers; 2-D KDE clipped to frame; plotly gif/apng exports grid-rounded like matplotlib. D2: CONTRIBUTING modernized (all links curl-200); doc_requirements cleaned (deepdish/ppca/hdbscan dropped, plotly+kaleido added); .readthedocs.yaml gains ffmpeg; Makefile PYTHON override; post_build Colab badges skip pages without notebooks; gallery video CSS unfloat; favicon generated from hypercube logo; analyze.ipynb DOI link. 16 further items verified already-fixed by earlier waves. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…all 54 examples regenerated Forced full gallery regeneration against the audited/fixed code (fresh figures incl. restored plot_sotus, palette-trimmed continuous hues); fixed the 22 short title overlines in example docstrings (D02-004); second incremental build confirms 0 sphinx warnings/errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…1 env-artifact), reconciliation 516 fixed / 98 to verify Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s; gitignore both (X5-006) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or residue Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…curity, packaging — 42 items
G1: Categorical/datetime64/MaskedArray/bool-list inputs handled with
documented conversions + warnings (masked cells -> NaN, never silently
plotted); nested array lists; vectorizer-typo errors name the kwarg
before HF 401s; DataFrame column names become axis labels (2-col/2D,
3-col/3D); plotly title px math consistent.
G2: align dedupes duplicate index labels (was silent misalignment);
minimal {'model': 'PCA'} dict works across all 6 dispatchers; cluster
dict honors args (was silently discarded); params/kwargs conflict warns
byte-identically everywhere; manip False-skip; all manip asserts ->
real ValueErrors (AST-enforced); unified None/empty-input errors via
shared helpers; Pipeline rejects duplicate step names; AutoRegressor
lags bounds; impute empty-vs-all-NaN distinguished; predict datetime
horizon guard; tuples accepted like lists; Smooth NaN clear error.
G3: file modes preserved on atomic saves (no more 0600 demotion);
PermissionError wrapped; gzip decompression capped at 2 GiB (bomb
guard); extensionless pickles sniffed before CSV parse; stream HEAD-
phase errors salvage data; ffmpeg prechecked before consuming streams;
exception cross-refs canonicalized.
G4: __init__ docstring import-forms corrected (subprocess-verified);
readme images absolute for PyPI; sdist ships the full runnable tests
tree; py3.14 classifier; SPDX license form (PEP 639).
Full suite: 2258 passed / 0 failed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, wave 6d + evidence curator launched Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…code-org, licensing H1 (22): SRM features / describe max_dims / apply_model ndims / rotations / zoom / transform / fmt / legend / n_clusters validation; align rejects 3-D; hyper-alias DeprecationWarning; cluster plain-int labels; predict bundle t-row consistency; stacklevel sweep (warnings now point at user code); UMAP/HyperAnimation/ARIMA warning hygiene; PPCA rank-deficiency error; targeted upstream-dw filter; animation cost docs. H2 (14): procrustes single implementation (np.matrix gone, index= actually works); brainiak Apache-2.0 header + pca-magic license text (compliance); shared predict/impute helper dedup; dead parse_args + star imports + tests/context.py removed; Clusterer/Reducer exported; exceptions canonicalized; stale dev/ (5.8MB) + 0.8.1 release notes deleted; CLAUDE.md vendored-code section corrected; describe helper tests; top-level __all__ (star-import no longer leaks internals). FINAL suite: 2331 passed / 0 failed; warnings 314 -> 159. + PR report draft + curated evidence branch (audit-evidence-2026-07 @ a709d8e). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Findings/verdicts JSONs, ledger, plan, and the report source remain in this branch's history (SHA-pinned pointers in the PR report); curated evidence lives on the permanent audit-evidence-2026-07 orphan branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… close animation-GC gap 62 pytest.warns(match=...) wraps (deliberately-provoked hypertools warnings now ASSERT their messages), 50 per-test scoped filters for upstream noise under contrived conditions (each justified inline), net +32 assertions. Production fix: raw FuncAnimation escaping without a HyperAnimation wrapper (exception path + return_model bundle) now gets the X4-012 GC silencing via shared mark_draw_started(). Suite: 2333 passed / 0 failed / ZERO warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full 1.0 pre-release audit (2026-07-11..17): 46 independent red-team auditors -> 708 findings -> 691 confirmed by blind adversarial verifiers -> 8 fix waves (41 commits) -> every fix independently re-verified by fresh adversarial re-auditors + 3 whole-branch reviews. 12 critical root causes fixed incl. silent wrong results (sotus data mapping, align row-order scramble, Smooth cross-dataset leak, Kalman never learning dynamics, CSV delimiter corruption, static-line vertex dropping), import crashes, packaging gaps. Suite 1490 -> 2333 tests, 0 failures, ZERO warnings; docs zero-warning full rebuild; all 54 examples + 15 tutorials re-executed fresh. Full report: posted on PR #272. Evidence: audit-evidence-2026-07. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…from run 29582796739 - plotly animated exports: shared kaleido session (one Chrome for the whole export instead of one per frame) + O(n^2) frame-copy hoist. Formerly-hanging animated-SVG test: >1200s on CI -> 47s; full local suite 17min -> 8min. Works with and without the kaleido sync-server API, and never stops a server it did not start. - matplotlib 3.11: plt.close() now detaches the canvas, making every show=False figure unrenderable after our leak-prevention close. plot() re-attaches the live canvas post-close (no-op on <=3.10). Fixes 10 margin tests + the pixel-identity test on CI — and, far more importantly, show=False rendering for every mpl>=3.11 user. - unknown vectorizer/semantic names: same clear ValueError with or without the HF text tier (ImportError path rewrapped like OSError); BONUS BUG: registry pollution made the rewrap first-use-only — membership now checked against frozen import-time name sets. Real subprocess import-blocker test (no mocks) + real-HF-call test kept. - packaging tests: build>=1.2 added to dev extra; importorskip guard (skip cleanly, never 10 collection ERRORs). - Windows: os.geteuid() at collection time killed all 4 Windows jobs 22s in; POSIX permission test now properly platform-guarded. - test_round3 unclosed-file ResourceWarnings fixed; one intermittent sklearn GP lbfgs ConvergenceWarning filter added (tiny-fixture noise). Verified green in BOTH the main venv (mpl 3.10.8 / plotly 6.8.0) and a CI-replica venv (mpl 3.11.0 / plotly 6.9.0 / sklearn 1.9.0 / build): full suite 2335 passed / 0 failed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of both 3.10 jobs dying at exit 4 after 48 tests: pytest aborts the ENTIRE session (UsageError) when a filterwarnings mark names a category it cannot import — pandas.errors.Pandas4Warning does not exist on the pandas-2 generation that py3.10 pins by design. All such marks now reference importable base classes (Pandas4Warning subclasses DeprecationWarning) with explanatory comments. Windows (first full test execution on this branch): POSIX mode-bit assertions (umask masking, chmod 0o604) skipped on win32 with honest reasons — the production mode-transfer helpers still run there; tilde tests set both HOME and USERPROFILE; the failed-save fixture now uses a parent-is-a-file target that fails on every platform (assertions unchanged); latent POSIX assumptions swept across all audit-wave test files. Verified: full suite green in the pandas-2 replica venv (2329 passed / 0 failed — was exit-4 abort) AND all 11 changed test files green in the main venv (240 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🔬 HyperTools 1.0 release audit — full reportScope: everything in this PR — every public function, every doc, every example, every tutorial. TL;DR
The criticals (all fixed)
Before / after (SHA-pinned, permanent)D05-gallery-data-text-003 — load('sotus') restored to the real 29 State of the Union addresses (1989-2018); the example now traces a genuine text trajectory t
F14-manip-normalize-001 — Smooth is applied per dataset, so A stays exactly 0 and B stays exactly 1 with no cross-subject bleed at the boundary
F01-plot-static-core-001 — the full series is drawn to its final sample and the terminal 50-sigma spike is clearly visible
F02-plot-hue-001 — the gradient survives a NaN hue value; points get their proper colors and the NaN point is shown neutrally in gray
F24-colors-fonts-interactive-013 — the cyclic palette is trimmed so the endpoints differ -- the spiral now runs red-orange (hue=0) to magenta (hue=max), with a color
F05-plot-animate-special-001 — the same frame 10 shows only the trail actually traversed so far; the future trajectory stays hidden until the head reaches it
Full set: 23 curated images on the Method (how independence was enforced)
What was verified beyond bug-fixing
🚩 Items needing Jeremy's sign-off
Release-time checklist (before publishing 1.0 to PyPI)
Stats
Pointers
Audit executed by Claude (Opus 4.8) with independent subagent auditors, verifiers, implementers, and reviewers — no self-reviews. Every claim above is backed by a real run recorded in the audit branch's findings/verdicts files. |
…ile/headless animation Release-review blocker #2. Two compounding bugs made animated plotting abort natively on headless macOS even under MPLBACKEND=Agg: - import-time: macOS unconditionally prepended 'MacOSX' to the backend candidates, ignoring an explicit MPLBACKEND. Now, if MPLBACKEND is set and HYPERTOOLS_BACKEND is not, hypertools honors matplotlib's own choice instead of overriding it with a GUI backend. - plot-time: any animate=True/interactive plot switched to the GUI backend. Now the switch happens only when a live figure will actually be DISPLAYED (show=True and no save_path). FuncAnimation + file export run on Agg, so saving/headless never touches a GUI toolkit. New tests/test_backend_headless.py: 4 real subprocess tests (incl. the reviewer's exact 2-D animate-under-Agg repro) asserting no _macosx/Tk/Qt/ GTK/Wx module is ever imported and the backend stays 'agg'. Updated the failed-switch state test to exercise the switch via display intent (show=True); the no-switch-on-file-export contract is the new headless test. Backend + animation suites green on Agg (252 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ot warn) Release-review blocker #1 (critical). trust=False was warning-then- unpickling, so trust=True was a warning-suppression flag, not a security boundary. Now _unpickle_bytes REFUSES (raises HypertoolsTrustError) any remote payload unless trust=True. This single chokepoint covers all three remote-pickle entry paths -- extension-based (.pkl/.geo/...), content-sniffed magic byte, and extensionless protocol-0 (ASCII) -- and the extensionless sniff path now lets the trust refusal propagate instead of relabeling it 'corrupted'. New tests/test_pickle_trust_boundary.py proves, against a real loopback server, that a malicious __reduce__ payload NEVER executes under trust=False (refusal happens before deserialization), that trust=True opts in, and that local files, numeric npz, and built-in datasets are unaffected. Updated the load-sources tests that asserted the old warn-and-continue behavior. load() trust= docstring rewritten. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ializing Release-review blocker #1 (built-in integrity). Every built-in dataset is now checked against a hard-coded SHA-256 BEFORE it is unpickled, and every cache hit is validated. A download counts as success only when its bytes match the pin (so a rate-limit HTML page served with status 200 is retried, not cached as the dataset). A persistent mismatch -- corrupt or tampered download, poisoned cache, or a changed upstream file -- is a HARD error that removes the unverified file, never a silent redownload- and-reparse. sotus (loaded via datawrangler) is exempt. Interim hashes pin the current hosted pickle files; a verified non-executable conversion bundle (npz/parquet/json.gz, all round-trip checked) has been produced for Dropbox re-hosting, after which each entry swaps to its new URL + hash and the .npz/.parquet path stops unpickling entirely. 6 offline integrity tests (stubbed downloads). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… legacy/kaleido/deps docs Release-review issues #3-#7, #9: - #3 (blocker): add dev-1.0/dev-1.0-refactor to pull_request.branches so PRs into the 1.0 line (incl. fork PRs) actually run CI -- the green matrix was push-triggered only. - #4: prune tests/screenshots (129 ignored files that graft-tests leaked into a 3.5MB sdist) + a packaging test asserting every sdist file is git-tracked or in a documented build-metadata allowlist. - #5: drop the untested Python 3.14 classifier (CI covers 3.10-3.13; dependency resolution is not a compiled-stack compatibility test). - #6: correct the legacy-geo docs -- pickle-format geos (>=0.8) load via the shim; pre-0.8 deepdish/HDF5 geos need a one-time numpy<2 conversion (with a concrete command); README + CHANGELOG. - #7: document that kaleido 1.x needs a Chrome/Chromium browser for static image export, with a troubleshooting pointer. - #9: replace the inaccurate 'small base install' wording with an honest description (the base pulls the full scientific stack); deps unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…test Release-review issue #8. All GitHub Actions are pinned to reviewed commit SHAs (with a version comment) instead of mutable @v4/@v5 major tags, so a compromised or force-moved tag cannot silently alter CI. New wheel-smoke job builds the wheel + sdist, runs twine check, installs the actual WHEEL into a fresh venv (not the editable source checkout), and runs a public-API smoke test from outside the repo (scripts/wheel_smoke_test.py asserts it imported from site-packages, config.ini shipped, and a real plot/reduce/cluster pipeline runs) -- catching packaging gaps that a pip install -e . run cannot. Verified locally end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📋 Audit working notes (moved out of the source tree)Per the 2026-07 release review (issue #10), the audit's working notes and Reconciliation summary (708 findings){
"fixed": 465,
"already_fixed": 51,
"skipped": 54,
"escalated": 18,
"dropped": 5,
"env_resolved": 12,
"UNRESOLVED": 98,
"deferred": 5
}Master plan (PLAN.md)HyperTools 1.0 Release Audit — Master PlanStarted: 2026-07-11 (Fri) · Branch: Mission (Jeremy's bar, verbatim)
Goal: complete the audit → implement ALL fixes → post comprehensive report to PR #272 → update the PR → all CI tests green. Ground rules (binding)
Phases
Audit unitsFunctions (24): F01 plot-static-core · F02 plot-hue · F03 plot-pipeline-integration · F04 plot-animate-window · F05 plot-animate-special (spin/chemtrails/precog/bullettime) · F06 plot-backends (plotly parity) · F07 plot-density-surface · F08 plot-inputs (DFs/MultiIndex/text/NaN/weird shapes) · F09 plot-save-return · F10 plot-remaining-kwargs sweep · F11 reduce+describe · F12 align · F13 cluster · F14 manip+normalize · F15 analyze · F16 predict · F17 impute · F18 load-hosted (+legacy geo unpickle) · F19 load-external (538/kaggle/URL/local) · F20 save round-trips · F21 apply_model+Pipeline · F22 io-streaming+LSL (real stream) · F23 core/config/exceptions · F24 colors/interactive/fonts helpers Docs (14): D01 README (every block run verbatim) · D02 sphinx build+warnings+thumbnails+autodoc coverage · D03–D06 gallery examples (4 batches × ~14, ALL 54 run for real) · D07–D10 tutorials (4 batches × 4, ALL 16 executed end-to-end) · D11 docstring examples: plot pkg · D12 docstring examples: everything else · D13 link validation (every URL manually fetched) · D14 docs-vs-code drift (signatures, version strings, extras) Cross-cutting (8): X1 API consistency + full export census · X2 error-message quality (deliberate misuse everywhere) · X3 performance/reliability (timings, memory growth, import time) · X4 warning hygiene (catalog all runtime warnings) · X5 packaging (wheel/sdist/extras/fresh-venv) · X6 code org: plot pkg · X7 code org: rest · X8 all 28 open ContextLab issues cross-checked vs 1.0 ("all desired functionality present") Red-team method (every auditor)
Finding schema: Verification & fix protocol (no self-review)
Evidence & report protocol
Resume protocol (if context lost)Read Working ledger (LEDGER.md, final state)Audit Ledger — HyperTools 1.0 (2026-07-11)Working truth for the release audit. Update after every phase transition, wave completion, and fix commit. Phase status
Key facts
Environment fixes (not code findings)
Wave-1 findings tally (16 units, 224 filed; pre-verification)
Criticals filed: F18-001 (load('sotus') returns broken sklearn Pipeline — Drive id duplicated with nips_model; canary CONFIRMED), F06-001 ($HYPERTOOLS_BACKEND env var crashes import), F08-001 (plain list-of-lists matrix crashes plot with nonsensical color= error; == F01-004), F12-001 (trim_and_pad silently scrambles row order for non-RangeIndex DataFrames). Unit status: F01 18f · F02 14f · F03 15f · F04 12f · F05 15f · F06 12f · F07 8f · F08 17f · F09 15f · F10 17f · F11 18f · F12 10f · F18 9f · F20 10f · orphan-valid: F15 16f, F16 18f. Full detail: findings/*.json. Auditor-quality canaries: both pre-warm seeds (sotus Pipeline bug, sklearn version warnings) independently found by F18 ✓. F03 auditor also proved pipeline order normalize→reduce→align via exact coordinate equality and caught repo CLAUDE.md's Data Flow section listing the wrong order. Wave-2 update (2026-07-11 ~02:50)Cumulative: 29 units workflow-completed + 3 orphan-valid JSONs (D01-readme, D05-gallery-data-text, D10-tutorials-embeddings-lsl) = 32/46 units on disk, 466 findings filed (10 critical, ~72 major). Fix commits
5A verification: 616 passed / 2 skipped (expected guards) pre-commit. Integration checks DONE 2026-07-12: corpus='sotus' → (2,50) topic vectors ✓; hyperalign n_itr → TypeError with did-you-mean ✓; impute([])/predict([]) → clear ValueErrors ✓. Partial edits from spend-capped agents (A1/A4/A6/A7/B1) were REVERTED before these commits; those agents re-ran fresh on this base. Wave-5A COMPLETE (8/8, 178 findings fixed)New: A1-io 40 (sotus speeches restored via dw corpus — verified 29 docs; CSV sep fix; format-aware atomic save(); repair-on-load for stale model pickles), A4-predict-impute 37 (Kalman em_vars: impute sweep r=0.997/0.996/0.997/0.996 vs pre-fix 0.995/0.777/0/0; predict sine r=0.984; PPCA default r 0.125→0.977), A6-core-packaging 26 (config.ini IN wheel — real build verified; no venv droppings; np.seterr side effect gone), A7-cluster-reduce 21 fixed + 18 plot.py-side escalations. Wave-5B COMPLETE (4/4, 127 findings fixed; full-suite gate running)B1 62 fixed (data-faithful static lines incl. X3-002; ro- fmt colors; list-of-lists ONE dataset; kwarg did-you-mean validation; plot() Examples doctests; cyclic-palette 5/6-trim for continuous hue). B2 24 (NaN-hue neutral color, Series index, singleton category, palette lists/cliff, colorbar names). B3 34 (chemtrails future-leak fixed; hue+animate animates on mpl; per-dataset frame grid to longest; figure-leak fixed; apng clobber fixed; pathlib; ffmpeg errors wrapped). B4 7 (plotly surface shows enclosed points — Playwright-verified; degenerate-density warnings; kwarg validation). MAINTAINER SIGN-OFF FLAG for PR report: continuous-hue cyclic palettes (hls/husl default) now sample 5/6 of the hue circle so endpoints are distinguishable (was: both ends red, dist 0.03→0.6). CHANGES DEFAULT LOOK of continuous-hue plots. Implemented + tested + documented; Jeremy should confirm he likes it. New controller/5C items from 5B escalations
Post-5B plot.py escalation batch (B5) — dispatch after B4 landsFrom A7: F13-001/002/003/004/005/007/009/010/016/020/021/022 (plot.py cluster integration: FeatureAgglomeration guard, n_clusters exemption grammar, random_state threading, bundle k mismatch, small-int-hue categorical palette, cluster=False, spec-kwargs precedence + dict KeyError, LDA/NMF caveat, class/instance specs, k-default docs, legend numeric sort). From A1: F22-004 (stream kwarg whitelist warn), F22-010 (plot.py:1003 stale geometry ref). From A4: F17-006 remainder (format_data.py:262 + plot.py stale PPCA comments). Controller items (mine, after 5B — no agent owns these files)
Wave 5B/B5/controller COMMITTED
Wave 5C COMPLETE + COMMITTED (100 fixed, 68 verified already-fixed)
Wave 5D RUNNING (
|
…no history rewrite) Release-review issue #10. The tracked tree carried ~128MB of audit evidence (notes/audit-1.0-2026-07/, re-added by a later 'git add -A' after the pre-merge removal, since its .gitignore lived inside the removed dir) and ~39MB of generated sphinx-gallery output (docs/auto_examples/). Both are removed from the tree and gitignored: - audit working notes/evidence -> moved to PR #272 comments (plan, ledger, reconciliation summary); full per-finding JSONs/verdicts/ evidence remain in the audit/release-1.0-2026-07 branch history. - docs/auto_examples/ is generated from examples/*.py at docs-build time (sphinx-gallery); the SOURCE examples stay tracked. Tracked files 2426 -> 680. History is NOT rewritten (per maintainer decision); this only stops carrying the bytes forward. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
✅ Release-review resolution (all 3 blockers + 7 issues)Thanks for the review — it caught real gaps the audit missed. All items are fixed on Blockers
Issues
Datasets (re-hosting, your decision)I built a verified non-executable conversion bundle — 15 datasets as Still your call
|
… — completes blocker #1 Release-review blocker #1 (dataset re-hosting, Jeremy's chosen path). The 15 DATA datasets are now hosted on Dropbox as non-executable .npz / .parquet / .json.gz and read with allow_pickle=False -- the built-in data path never unpickles. EXAMPLE_DATA points at the new URLs; _REHOSTED maps each to its reconstruction (npz list/array, parquet, or json.gz text corpus); _EXAMPLE_DATA_SHA256 pins the converted-file hashes and every download/cache-hit is verified before read. Verified: each Dropbox upload's SHA-256 matches the file built locally; each dataset reconstructs to the EXACT value hyp.load returned from its former pickle (values + dtype + columns; datasaurus DataFrames keep cols ['x','y'], its unused per-frame index is not preserved). The old Google Drive pickle ids stay live so hypertools <1.0 keeps loading. The fitted sklearn *_model Pipelines remain Drive pickles, hash-verified before unpickling (skops re-hosting is a follow-up). +2 tests: re-hosted npz refuses object arrays (allow_pickle=False proof); every re-hosted dataset is on the non-pickle path. Full suite 2357 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
✅ Datasets re-hosted — blocker #1 fully closedThe 15 built-in DATA datasets now load from Dropbox as non-executable files ( |
Addresses Jeremy's second release review (2026-07-18). Blocker #1 (sotus re-host) is code-ready pending its Dropbox upload; everything else lands here. - #2 (blocker) headless docs build: RTD now provisions a pinned Chrome for kaleido (.readthedocs.yaml post_install: plotly_get_chrome), a new `docs-clean` CI job builds the docs from a pristine `git archive` with `sphinx -W -E -a` (asserting docs/auto_examples is untracked) so a missing browser or any gallery-execution failure fails before it reaches RTD, and conf.py's fallback comment no longer overstates its coverage (it only guards a missing plotly import, not a missing Chrome). - #3 datasaurus indexes: hyp.load returns raw data, so each frame's original contiguous global-row-range index is part of the public result -- restore it from an in-package constant (_DATASAURUS_INDEX_STARTS) instead of a fresh RangeIndex. Add an immutable compatibility baseline (tests/data/rehosted_compat_baseline.json) + equivalence test covering every re-hosted dataset (values/index/columns/dtype/ordering), proven equal frame-by-frame to the pre-1.0 original. - #4 trust docstrings: load_source/_parse_payload now state that remote unpickling is REFUSED (raises HypertoolsTrustError), not warned. - #5 changelog: drop "small ... base install"; match the corrected README. - #6 atomic + concurrency-safe downloads: stream into a private temp file, verify its SHA-256, then os.replace into the cache (atomic rename); add a best-effort per-dataset filelock. A reader never sees a partial/unverified file, and an interrupted download leaves no corrupt cache entry. Real thread/atomicity tests added. - #7 merge strategy: CONTRIBUTING.md documents squash-merge (no history rewrite) so the branch's large-binary history never enters dev-1.0. - #8 sdist smoke: the release-qualification job now installs + smoke-tests the sdist in its own fresh venv too, not only the wheel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>












HyperTools 1.0 — architecture refactor + bug hunt
This PR merges the completed HyperTools 1.0 class-based refactor into
dev-1.0, together with a broad bug hunt (open-issue triage + two animation-rendering fixes you reported + a legend-clipping fix) and the CI fixes needed to get all platforms green.1. Architecture refactor (Plans 1–8)
Reorganized the working
dev-1.0code into the class-based structure, on modern deps, withDataGeometryremoved from the public API:core(eval-freeapply_model),external(vendored PPCA + brainiak SRM/DetSRM/RSRM),manip(Normalize/ZScore/Smooth/Resample),reduce/align/cluster(generic sklearn dispatch by name),io,plot(matplotlib + plotly backends). Oldtools/names remain as shims.DataGeometryremoved from the public API (kept only as a hidden unpickle shim so legacy hosted.geodatasets still load).plot()returns a Figure /(fig, ani);plot(..., return_model=True)returns{'fig','xform_data','models','animation'};load()returns raw data.2. Reported animation fixes
Animated bounding box "crowded / cut off on the right." Animated 3D plots zoomed the cube in too far. Fix:
set_box_aspectzoom 1.25→1.125 + full-canvas axes. Min cube-to-edge margin over the fullsave_movierotation: right 80→96px, bottom 51→72px.Duplicate animation-legend entries (GH #207). Faint "trail" artists carried the dataset label, so each label appeared twice. Fix: trails tagged
_nolegend_; legend is now the static union of in-focus datasets. Verified['first','second'].Clipped static gallery legends. Wide legends (long labels / many entries) clipped off the right edge. Root cause: the fit routine measured the legend under seaborn's (narrower) font, but the figure is saved downstream under the default (wider) font. Fix:
_fit_right_legendnow measures the rasterized pixels under default rcParams and widens the figure (keeping the plot's size) until the legend has a margin. Regeneratedplot_legend/plot_PPCA/plot_missing_data; pixel-based regression test added.3. Open-issue triage → close-on-merge
The triage below has since been fully executed (2026-07-07): all 45 addressed/obsolete issues are CLOSED with per-issue run-code evidence comments; the 22 remaining feature requests carry research comments +
low/medium/high effortlabels; 6 issues were migrated from the jeremymanning fork (#273–#278, fork tracker now empty); and 6 residual gaps found during re-verification were fixed on this branch (#94, #141, #199, #206, #209, #244). See the audit summary comment. Originally: triaged all 67 open issues against this branch with real repros (seenotes/issues-to-close-on-merge.md): 31 addressed/obsolete + 16 fixed or implemented on this branch (incl. §5's #169/#132 and §6's seven features) = 47 to close on merge; 20 stay open (feature requests / design decisions), each documented.Bugs fixed from triage (all regression-tested):
import hypertoolsmutated globalrcParams['pdf.fonttype']manage_backend's snapshot/restoreget_projcrash on 2D labeled plotsget_proj; match annotate/update tuple shapes for 2D vs 3Dn_clustersn_clustersonly when the model's signature accepts it; register those clusterersshow=Falseleaked the figure into pyplotplt.close(fig)for static figures (skipped when the user passedax, and for animated figures, whose timer must stay alive)format_dataaligns named columns BY NAME to the first dataset's order (warns on reorder); mismatched column sets raise a clear ValueErrorwiki_modelkeyreduce=<class/instance>→UnboundLocalErrormodel_paramsin the custom-estimator branch4. CI fixes (get all platforms green)
The first CI run surfaced two platform issues (unrelated to the features above); both fixed:
os.getenv('HOME')at import time to build its data dir;HOMEis unset on Windows, soos.path.join(None, …)crashed dw's (and hypertools') import. Fixed hypertools-side by settingHOME=expanduser('~')before importing dw; filed upstream as Import crashes on Windows: config datadir evals os.getenv('HOME') which is None data-wrangler#32, fixed in dw 0.5.1 (released; verifiedimport datawranglerworks withHOMEunset). Thepydata-wranglerpin is bumped to>=0.5.1; the one-lineHOMEguard stays as belt-and-suspenders for environments still on 0.5.0.plt.close()(the disabling the figure doesn't work as intended #148 fix), so tests/callbacks that readfig.canvas.renderer/buffer_rgba()failed. Fixed by guarding the renderer inupdate_positionand rendering the affected tests through an explicit Agg canvas. (savefigafter close still works, so users are unaffected.)plt.close()destroyed theFuncAnimation's real Tk timer and any later draw of the returned figure crashed ('NoneType' object has no attribute 'start'). Animated figures are now exempt from theshow=Falseclose — the disabling the figure doesn't work as intended #148 complaint was about static figures, and animations need their timer alive for playback.plt.get_fignums(), which the disabling the figure doesn't work as intended #148 close empties; it now uses the figure(s) returned byplot()(13/13 cases pass).5. New:
hyp.predict+hyp.impute(resolves GH #169)Two new modules in the established class-based style (base class + one file per model + funnel dispatcher), integrated into
hyp.plot/hyp.analyzelike cluster/align:hyp.predict(data, model=..., t=...)— timeseries forecasting:Kalman,GaussianProcess,AutoRegressor(any sklearn regressor, recursive multi-step),ARIMA,Laplace(skaters ensemble),Chronos(HuggingFace foundation model, realchronos-t5-tinytest).tfollows Use Kalman filter to fill in missing data #169's spec (int steps, or a datetime on time-indexed data — including past-date truncation). One forecast per input dataset, same dimensions, continued index.hyp.impute(data, model=...)— missing data:PPCA(default; clean interface over the vendored implementation —format_data's fill now routes through it, behavior-preserving),SimpleImputer/KNNImputer/IterativeImputer, andKalman, which fills rows where every feature is NaN — the exact gap Use Kalman filter to fill in missing data #169 describes (PPCA cannot).return_model=Trueon both →(result, fitted)matchingapply_model's convention; the fitted model can be passed back asmodel=on new data and is applied without re-estimation (verified: fit on A, forecast/impute B).hyp.plot(data, predict='Kalman', t=30)overlays one dashed, low-opacity, same-color forecast tail per dataset (2D + 3D, both backends, no legend duplication, frame always contains the forecasts).impute=selects the missing-data model in the plot/analyze pipeline.[predict]extra (pykalman, statsmodels, skaters) and[predict-hf](chronos-forecasting); GaussianProcess/AutoRegressor/sklearn imputers work on the base install; friendlyImportErrors otherwise (fresh-venv verified). yfinance is not a dependency — the tutorial self-installs it.stock_forecasting.ipynb— scrapes 2y of real Yahoo Finance prices for 4 tickers, backtests all models against a 30-day holdout with an honest MAE/MAPE table (spoiler: ARIMA/Kalman ≈ the naive baseline, as efficient-market theory predicts — the tutorial says so).projectile_kalman.ipynb— a real NBA SportVU jump-shot arc (25 Hz optical tracking): Kalman imputation of 5 fully-occluded frames recovers them to RMSE 0.20 ft vs the recorded truth; forecasting the arc's final 20 frames from the first 30 lands within MAE 4.2 ft.DotProduct + RBF + WhiteKernel— the old stationary RBF reverted forecasts to the training mean beyond the data (drift −0.026/pt vs observed +0.0019/pt: reversed); the linear term extrapolates trends (+0.002..+0.010/pt: continues). Before/after renders + measurements in this comment.plot_predict(helical forecasts) +plot_impute(PPCA-vs-Kalman panels on the Use Kalman filter to fill in missing data #169 case); API reference sections added.6. Seven long-standing feature requests (GH #95, #100, #108, #109, #127, #142, #177, #191)
All seven implemented in the 1.0 design language, in both rendering backends, each with numeric + screenshot evidence in this comment:
colorbar=True/dict — continuous hue (same palette as the lines, real value range) and discrete groups (segmented, labeled); coexists with legends without clipping.surface=True/dict — smooth, lit convex-hull surfaces (3D, Blinn-Phong + plotly vertex shading) and smooth filled shapes (2D), dict-controlled properties, animatable (per-frame hull recompute); the axes cube auto-expands to contain the hull. Newanimate_surface_morphgallery demo.density=True/dict — subtle per-group KDE clouds (off by default; 2D alpha-ramp, 3D iso-surfaces/go.Volumewith small-cluster auto-boost); enhancement: replace matplotlib with ipyvolume #191's ipyvolume ask is superseded by the plotly backend.chemtrails/precog/bullettimeaccept per-dataset lists (mixed trail styles in one animation); spin/serial warn instead of silently ignoring.colors=/linestyles=/markers=were silent no-ops without their singular twins — fixed.hyp.load#177hyp.loadcompleted: Drive large-file interstitial (verified against a real 498MB public file),.xlsx/.xls, Google Sheets→CSV, remote-pickle trust policy.Every graphical feature was adversarially screenshot-reviewed by fresh agents across a {2D,3D}×{mpl,plotly}×{static,animated} grid; their findings drove 6 further fix commits (plotly WebGL surface artifacts, bounding-box containment, MultiIndex colorbars, legend fitting, 3D density visibility, trail-mode warnings). New gallery examples:
plot_surface,plot_density,plot_colorbar,plot_multiindex,animate_trails_mix,animate_surface_morph.Maintainer-feedback rounds (tight hulls + morph, constant rotation speed + plotly parity + lighting controls, axes-clipping + gif corruption + full-sample morphs, multibyte text support, GH #205): hulls now hug the observations (hull-hugging smoothing: post-Taubin pull-back to the hull; cube-cloud oversize 1.63×→1.13×, ≥99% containment; axes box sized from actual meshes incl. mid-morph union bound, both backends) and morphing is a first-class animation style —
animate='morph'(Hungarian point-cloud morphs between datasets, tagged-list form for static backdrops) with per-segmentrotationslists ([1, 0.25, 2, ...]= per hold/transition). Both shape-morph gallery demos collapsed to singlehyp.plotcalls. Morph rotation speed is constant (segment duration ∝ rotation count); plotly marker sizes are empirically calibrated to matplotlib (15.1px → 5.0px for markersize=6) and volumetric shading retuned to matplotlib's subtlety; every surface color/lighting/shading knob is verified effective on both backends (incl. newlightdir; silent no-op keys removed). The "cut off bounding box" report was traced to two real rendering bugs, both fixed: 3-D scene artists were clipped at matplotlib's aspect-shrunk square viewport (now unclipped in every animation path), and animation GIFs saved at reduced dpi were corrupted by a matplotlib writer resize through the interactive-backend window (saves now dpi-safe). Morph animations use every dataset's full sample set (duplicate-to-largest, duplicates hidden at holds; hold frames are a 100% pixel match to static plots). GH #205 fixed: full multibyte (CJK) text support in both backends — automatic covering-font detection (excluding placeholder fonts like LastResort) + afont=kwarg (family/path/FontProperties) applied to labels/legends/colorbars/titles; plotly also gained reallabels=annotations (was a silent no-op); CI provisions CJK fonts on all platforms with anti-tofu pixel tests.7. Round 17 — every non-deferred open issue addressed (GH #103 #116 #123 #130 #138 #153 #154 #159 #161 #162 #174 #187 #198 #227 #273 #274 #275 #276 #277 #278)
Following the per-issue "current status (1.0 update)" triage, this round implements all 20 non-deferred open issues (the 5 marked defer/don't-implement were left untouched). Highlights: a unified cross-module API (every dispatcher takes
manip=/normalize=/reduce=/align=/cluster=/return_model=, canonical order documented as a flowchart indocs/pipeline_order.rst) + publichyp.Pipelinewithpipeline=reuse (#138 #153 #227 #161 #174); manip list-chaining and the story-trajectories animation —animate='window'/focused=/duration=(#274 #275, post-align inter-subject correlation −0.004→0.33, jumps 18.96→0.73);label_alpha=/axis labels/animate=dict/2-D animations (#103 #154 #123); six torch autoencoder reducers, sklearn/seaborn/538/kaggle loaders, LSL streaming, gensim text wrappers (#162 #273 #116 #130 #198, all opt-in extras); and a full docs pass — docstring coverage 131→0 with an enforcement test, completeapi.rst, 7 new tutorials, regenerated README media (#276 #278 #159 #187 #277).Executed as 19 review-gated tasks; the reviews caught and fixed five real reuse-contract bugs (silent pipeline refit,
Aligner/SRM/Resampletransform replaying fit-time data). Per-issue evidence comments (code + exact new API + numeric/screenshot proof) are posted on all 20 issues; see the round-17 summary comment for details.Testing
1271 passed, 0 failed(+492 tests across all rounds: surface/density/colorbar/multiindex/trails/meshutil/load/color-alias/morph-animation/hull-tightness suites); 6 plotly→kaleido image-export tests deselected locally only — they deadlock Chromium in this sandbox but run fine in CI.8c40499a-- run 28903254424make htmlsucceeds); gallery regenerated with the 6 new example pages (including two captured animations).🤖 Generated with Claude Code