Migrate to pgmpy>=1.1.2,<1.2: torch becomes optional (closes #131) - #135
Merged
Conversation
pgmpy 1.x makes torch and litellm optional extras, so this removes the ~2 GB of CUDA wheels every bnlearn install pulled via the frozen pin. - BayesianNetwork/MarkovNetwork are tombstones in 1.x; use DiscreteBayesianNetwork/DiscreteMarkovNetwork. - parameter_learning: fit() now takes an initialized estimator instance (pgmpy.parameter_estimator.DiscreteBayesianEstimator). - structure_learning: score classes renamed (BIC/K2/BDeu/BDs/AIC, from the pgmpy.estimators lineage the estimator-style search classes accept); HillClimbSearch black/white/fixed edge lists map onto ExpertKnowledge; PC's removed skeleton_to_pdag replaced with estimate(return_type='pdag') + to_dag, keeping variant='stable'. - independence_test: freeman_tuckey/neyman/cressie_read were dropped by pgmpy; reconstructed as power_divergence partials with fixed lambda_. - structure_scores: structure_score() function became the pgmpy.metrics.StructureScore metric class; historic 'bic'/'aic' names map to the disambiguated 'bic-d'/'aic-d'. - print_CPD: MarkovNetwork-to-Bayesian conversion no longer exists in pgmpy; warn and return instead. - Tests: class-name asserts made rename-proof. All 89 tests pass under pgmpy 1.1.2. Site-packages drops from 1.4 GB (with CPU-only torch; CUDA installs were far larger) to 519 MB. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KMjFQWcJ8n6jieKjAALAcR
- structure_learning.py:583 referenced best_model after the PC-search rewrite renamed it to dag, so verbose>=4 raised NameError. The line duplicated the 'DAG edges' print two lines above; remove it. (The bug predates this branch: master's line 582 has the same stale reference.) - Cap lingam<1.13 (same change as #11): lingam 1.13.0 pins scipy<=1.13.1, which has no cp313 wheels, so Python 3.13 CI fell back to building scipy from source and failed for lack of OpenBLAS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012x4Jk4dkxMWgn3SSWvN14n
…search pgmpy 1.x removed skeleton_to_pdag, so the migration reached for estimate(return_type='pdag') to get from skeleton to PDAG. But PC.estimate runs build_skeleton internally, so the preceding explicit build_skeleton call meant every constraintsearch run paid for the conditional-independence pass twice. Drop the explicit call and derive the skeleton from the PDAG instead: edge orientation never changes adjacency, so pdag.to_undirected() is the same graph build_skeleton returned -- same networkx.Graph type, same edges. This also removes the unused seperating_sets unpacking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KsNDsyZxQNcMC4fHoJJpx8
CodeRabbit flagged that these ValueErrors were plain strings, so users
saw literal '{type(model)}' placeholders. Make them f-strings and point
them at the right variables (model['model'], df).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dh1KPAuSYmLpERF2T3Drm
Old bn.save() files pickle the class path pgmpy.models.BayesianNetwork.BayesianNetwork, which pgmpy 1.x still exposes as a bare tombstone class. Unpickling therefore succeeds silently and returns an object with no graph methods beyond whatever networkx views happened to be cached at save time — failures surface later and unpredictably. Detect the tombstone in bn.load(), explain the version break, and return None, per the agreement in erdogant#131. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dh1KPAuSYmLpERF2T3Drm
This was referenced Aug 14, 2026
dbolser
added a commit
to dbolser/bnlearn
that referenced
this pull request
Aug 31, 2026
…t#79) Expose Pearl's do-operator via pgmpy's CausalInference, which became available with the pgmpy 1.x migration (erdogant#135). do={} simulates setting a variable by intervention (incoming edges cut, comparable to mutilated() in the R version of bnlearn), and combines with ordinary evidence. Interventions are labeled do(X) in the readable summary. Locked test values on the sprinkler network, derived by hand from the CPDs: P(W=1|S=1)=0.927 (observational) vs P(W=1|do(S=1))=0.945. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dh1KPAuSYmLpERF2T3Drm
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.
This implements the migration proposed in #131, following the decisions you settled there. It's a small diff in the end (~80 lines over 9 files) — your recent master commits (vendored
CITests.py, the pypickle and lingam pins) did a lot of the heavy lifting, and this PR builds directly on them.Why
pgmpy 0.1.25 imports torch unconditionally, so every bnlearn install pulls the full CUDA stack. In pgmpy 1.x torch is an optional extra, and numpy is the default backend: a fresh bnlearn venv drops from ~1.4 GB to ~519 MB with no loss of functionality.
What changed
pgmpy>=1.1.2,<1.2inpyproject.toml/requirements.txt, and the import-time version check now asks for that range — the middle ground you preferred: patch fixes flow in, pgmpy's announced 1.2/1.3 removals can't break installs.BayesianNetwork→DiscreteBayesianNetwork,MarkovNetwork→DiscreteMarkovNetwork(the old names are tombstones in 1.x whose__init__raises). Score classesBicScore/K2Score/… →BIC/K2/BDeu/BDs/AIC.model.fit()now takes an initialized estimator instance (DiscreteBayesianEstimator(prior_type=..., ...)) instead of a class plus kwargs.black_list/white_list/fixed_edgeskwargs were removed fromHillClimbSearch.estimate(); they map onto the newExpertKnowledgeobject (forbidden_edges/search_space/required_edges). The bnlearn API is unchanged.skeleton_to_pdag()is gone;estimate(return_type='pdag', variant='stable')runs the whole pipeline (variant='stable'matches the pre-1.x default). The skeleton is derived from the PDAG, so the CI tests run once instead of twice.structure_score()→pgmpy.metrics.StructureScore(...).evaluate(); the historic'bic'/'aic'names are mapped to pgmpy's new'bic-d'/'aic-d'so user code keeps working.print_CPDon MarkovNetworks: pgmpy 1.x removedto_bayesian_model(); there is no conversion left to print CPDs from, so this now warns and returns empty instead of raising.pgmpy.models.BayesianNetwork.BayesianNetwork, which 1.x still exposes as a bare tombstone, sopickle.loadsucceeds silently and returns a half-broken object that fails later in confusing ways.bn.load()now detects the tombstone, prints that the file needs re-saving (orpip install "bnlearn<0.14"), and returnsNone. Regression test included.What's deliberately not in here
pgmpy.causal_discoveryestimators — the estimator-style classes remain supported until pgmpy 1.3, the<1.2pin covers us, and that interface is still settling. Noted in a comment for whenever it's worth revisiting.requires-pythoncap mirroring pgmpy's<3.15— resolvers enforce pgmpy's bound transitively, and a local cap goes stale the moment pgmpy widens theirs.Testing
🤖 Generated with Claude Code
https://claude.ai/code/session_017dh1KPAuSYmLpERF2T3Drm