HyperTools 1.0: architecture refactor + bug hunt#272
Open
jeremymanning wants to merge 151 commits into
Open
Conversation
Reorganize dev-2.0 into the jeremymanning fork's structure (base class per area, folder per module, one file per child class); remove DataGeometry; adopt datawrangler for the wrangling core (hybrid); keep dev-2.0's plotting/animation/streaming/coloring. Classic API names + module aliases; polars support via dw; strangler migration keeping tests green per commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bite-sized TDD plan: declare pydata-wrangler dep + text extra, probe dw 0.4.0 API surface and behavior (stack/unstack, funnel over numpy/pandas/ polars/list, sklearn text embed), reconcile py3.13/CI, establish the data-wrangler issue-coordination workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…w API) Controller recon resolved the environment blocker and confirmed dw 0.4.0's surface: standardize on .venv (py3.12), pin pandas<3 (dw#30), use verified dw.wrangle text call, and smoke-check against the 242-pass baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r#30) HyperTools 2.0 step 0: adopt datawrangler for the wrangling core. Adds pydata-wrangler>=0.4.0, a hypertools[text] extra -> pydata-wrangler[hf] (opt-in transformer embeddings), and a temporary pandas<3 ceiling because dw 0.4.0 type detection breaks on pandas 3.0 (data-wrangler#30). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canonical list of dw symbols the 2.0 refactor depends on. Missing symbols become filed data-wrangler issues + xfail-with-link, not silent green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Proves the real round-trips Plans 2-6 depend on: MultiIndex stack/unstack, funnel generalization over numpy/pandas/list/polars, and sklearn text embedding via dw.wrangle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the refactor branch to CI triggers, documents the py3.13/dw status, and starts the data-wrangler issue-coordination log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…strangler
Move exceptions to core (shim _shared); add eval-free unpack_model + RobustDict;
central config.ini via dw configurator; relocate apply_model to core.model as
source of truth (tools shim) + accept fork {model,args,kwargs} dict form.
Behavior-preserving; existing suite stays green. Deep arrays->DataFrames dw
conversion deferred to per-module plans.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…form
tools/apply_model.py becomes a shim; core.model is now the source of truth.
Adds {model,args,kwargs} spec support alongside {model,params}. Behavior
otherwise identical; existing apply_model tests unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nalysis 276 tests green. Captures the manip design questions surfaced from reading the fork sources: arrays vs DataFrames, Smooth=savgol-not-gaussian, Resample needs core.get, Normalize semantics reconciliation, fork bugs to validate/fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move vendored ppca/srm to external/ (shim _externals); build Manipulator base + Normalize/ZScore/Smooth/Resample (DataFrame/dw-based, fork ports validated+fixed) + hyp.manip dispatcher (funnel-wrapped, applies Manipulator directly, not via array-based core.apply_model). hyp.normalize compat left untouched. Adds core.get. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…al; shim _externals Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fork ports validated/fixed: np.clip bounds, core.shared.get import, Series dtypes. Gaussian-smooth mode still owed (Plan 6, weights pipeline). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Normalize/ZScore/Smooth) The axis==1 branch of each transformer self-called the module-level transformer name, which is the @dw.decorate.apply_stacked-decorated function. Because apply_stacked unconditionally re-stacks its input (adding a synthetic 'ID' row-index level even for a single DataFrame), transposing that already-stacked frame leaked the ID level into the columns, and the fitted params (keyed by the original, pre-stacking row labels) could no longer be looked up -- raising "key of type tuple not found and not a MultiIndex" for ZScore(axis=1), Normalize(axis=1), and Smooth(axis=1). Fix: keep @dw.decorate.apply_stacked only on the inner, always-axis==0 per-column core (renamed _transform_stacked); make the public transformer an undecorated dispatcher that transposes the raw, not-yet-stacked data and recurses into itself for the axis==1 case, so the stacking machinery never sees a transposed frame. resample.py's transformer/fitter carry no such decorator and were unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ndas 3.0) dw 0.5.0 fixes data-wrangler#30 (pandas-3 type detection). Ceiling lifted: pandas>=2.2.0 (no upper), pydata-wrangler>=0.5.0, text extra [hf]>=0.5.0. CI gains a pinned-pandas-3 acceptance gate (ubuntu/py3.12). Validated: full suite 293 passed on dw0.5.0/pandas3.0.3/numpy2.3.5 (== 2.3.3 baseline, no regressions). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…m tools Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tools/procrustes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot carried) RobustSharedResponseModel omitted (external.brainiak vendors SRM+DetSRM only). test_rsrm_not_exported uses `from hypertools.align import srm` because the classic hyp.align callable shadows the hypertools.align attribute (chained-attribute import form unsupported by design; see Plan 7 top-level API item). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move hypertools/tools/cluster.py to hypertools/cluster/cluster.py, fix the format_data import to go through tools.format_data, and add hypertools/cluster/__init__.py exposing cluster/models/mixture_models. Recreate hypertools/tools/cluster.py as a re-export shim so core.model._build_registry (which imports models/mixture_models from hypertools.tools.cluster) keeps resolving. Classic hyp.cluster is unchanged (still resolves via the tools/ shim per __init__.py). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 7, 2026
Open
Open
…ated 6, tagged 22) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Member
Author
Full issue audit executed: both trackers verified, updated, and organizedEvery open issue on ContextLab/hypertools (67) and jeremymanning/hypertools (13) was re-verified against this branch by actually running the code (no verdicts from reading source alone — each issue's comment carries copy-pasted commands + output, and rendered-image checks where the question was visual). Outcomes: ContextLab (67 issues → 28 remain open, all organized)
jeremymanning fork (13 issues → 0 remain)
Verification
The ContextLab tracker is now the single organized backlog for deciding what else goes into 1.0: 28 open issues, every one labeled and documented. 🤖 Generated with Claude Code |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.Testing
947 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.make htmlsucceeds); gallery regenerated with the 6 new example pages (including two captured animations).🤖 Generated with Claude Code