Skip to content

Migrate to pgmpy>=1.1.2,<1.2: torch becomes optional (closes #131) - #135

Merged
erdogant merged 5 commits into
erdogant:masterfrom
dbolser:feature/pgmpy-1x-migration
Aug 14, 2026
Merged

Migrate to pgmpy>=1.1.2,<1.2: torch becomes optional (closes #131)#135
erdogant merged 5 commits into
erdogant:masterfrom
dbolser:feature/pgmpy-1x-migration

Conversation

@dbolser

@dbolser dbolser commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

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

  • Pin: pgmpy>=1.1.2,<1.2 in pyproject.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.
  • Renamed classes: BayesianNetworkDiscreteBayesianNetwork, MarkovNetworkDiscreteMarkovNetwork (the old names are tombstones in 1.x whose __init__ raises). Score classes BicScore/K2Score/… → BIC/K2/BDeu/BDs/AIC.
  • Parameter learning: model.fit() now takes an initialized estimator instance (DiscreteBayesianEstimator(prior_type=..., ...)) instead of a class plus kwargs.
  • Hill-climb search: black_list/white_list/fixed_edges kwargs were removed from HillClimbSearch.estimate(); they map onto the new ExpertKnowledge object (forbidden_edges/search_space/required_edges). The bnlearn API is unchanged.
  • Constraint search (PC): 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 scores: 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_CPD on MarkovNetworks: pgmpy 1.x removed to_bayesian_model(); there is no conversion left to print CPDs from, so this now warns and returns empty instead of raising.
  • Old saved models: as agreed, no load-time shim — but the failure mode needed handling: 0.x pickles reference pgmpy.models.BayesianNetwork.BayesianNetwork, which 1.x still exposes as a bare tombstone, so pickle.load succeeds 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 (or pip install "bnlearn<0.14"), and returns None. Regression test included.

What's deliberately not in here

  • No switch to the new sklearn-style pgmpy.causal_discovery estimators — the estimator-style classes remain supported until pgmpy 1.3, the <1.2 pin covers us, and that interface is still settling. Noted in a comment for whenever it's worth revisiting.
  • No requires-python cap 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

dbolser and others added 5 commits August 14, 2026 14:50
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

@erdogant erdogant left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

@erdogant
erdogant merged commit 129dabb into erdogant:master Aug 14, 2026
10 checks passed
@dbolser
dbolser deleted the feature/pgmpy-1x-migration branch August 14, 2026 16:03
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants