Repository navigation
Fix dims coercion to list in model_from_fgraph - #8465
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #8465 +/- ##
=======================================
Coverage 91.95% 91.95%
=======================================
Files 128 128
Lines 21276 21276
=======================================
Hits 19564 19564
Misses 1712 1712
🚀 New features to boost your workflow:
|
ricardoV94
left a comment
There was a problem hiding this comment.
I started and then stopped. My posterior is beta(1, 0), that all new tests are overkill, and changing the assertion on the original tests suffices. We can't add 20 tests everytime we fix something like list -> tuple ...
| np.testing.assert_allclose(ip["y_cholesky-cov-packed__"], new_ip["y_cholesky-cov-packed__"]) | ||
|
|
||
|
|
||
| def test_clone_model_preserves_dims(): |
| ], | ||
| ids=["omitted", "single_str", "single_tuple", "two_dims"], | ||
| ) | ||
| def test_dims_forms_roundtrip(dims, shape): |
| "kind", | ||
| ["free_rv", "observed_rv", "deterministic", "potential", "data"], | ||
| ) | ||
| def test_dims_preserved_for_every_var_kind(kind): |
add_named_variable is declared to take a tuple and was passed a list. It stores dims verbatim, so the list reached named_vars_to_dims and the round trip stopped agreeing with itself: >>> model_from_fgraph(fgraph_from_model(m)[0]).named_vars_to_dims == m.named_vars_to_dims False # before True # after Every path built on the pair inherits it, so Model.copy(), copy.copy, copy.deepcopy, pm.do and pm.observe all disagreed with their input too. The tests were part of the defect. They asserted the round trip's own output, ["test_dim"], rather than comparing to the original model, which is why this went unnoticed. They now compare against the original, and that is the assertion that catches the type. Invisible to CI because both files sit in the FAILING mypy baseline. No consumer depends on a list here: backends/arviz.py and backends/mcbackend.py normalise with list(), printing.py and variational/opvi.py ignore the type, and testing.py never reads dims. So this is a contract fix, not a crash fix.
0e663fc to
f17b181
Compare
|
Agreed. Cut it down to the fix plus the assertion changes: Kept out as unrelated: the Say the word if you want the adjacent |
|
Thanks for the review @ricardoV94 ! |
A bundle is a directory holding manifest.json and graph.cloudpickle, and
load returns a real pm.Model. The model and the inference data are stored
separately: the bundle records a DataRef under a fixed set of keys, so a
consumer finds the samples without knowing your conventions, and the draws
stay readable by anything that speaks zarr. Embedded data is recorded
relative to the bundle, so a directory can be copied and still find its own.
save_model writes a local directory, which is the common case. The two steps
are public, so build_manifest and serialize_graph hand back a dict and bytes,
and the same bundle can be written to an object store, a registry, or a
database row and read back through ModelBundle.from_parts. Nothing here
touches a filesystem, so the package pulls in no storage library.
audit() reports what will not round trip rather than letting it fail later.
Three things it caught while being written:
* _customdist_blockers only inspected observed RVs, so a free CustomDist
with random= and no explicit logp= saved happily into a model whose
compile_logp raises. pm.sample rejects that model too, so it is easy to
reach by saving something never sampled.
* validate() reported ok with an empty check set when no reference_logp was
recorded, because ok was all({}). A bundle that verified nothing printed
PASS. It now computes logp even with no reference to compare against, so
ok=True always means something was checked.
* cloudpickle_required promised a portability guarantee it did not give: a
class defined in __main__ pickles by reference, so the flag was False for
a graph that still needed its classes elsewhere. Renamed to
stdlib_pickle_fails, which is what it measures.
Embedding defaults to zarr. Rather than silently defaulting a missing suffix
to zarr and failing later, the format is inferred from the suffix when the
caller does not name one, and the error says what to do about it.
test_restored_model_preserves_dims is marked xfail(strict=False): it needs
pymc/model/fgraph.py to hand add_named_variable a tuple as its signature
declares, fixed by pymc-devs/pymc#8465. It xpasses once that lands.
add_named_variableis declared to take a tuple and is passed a listpymc/model/core.py#L2196pymc/model/fgraph.py#L381ModelVarcoerces on the way in regardless,pymc/model/fgraph.py#L37, so thegraph never held a list:
Consequence: the round trip stops agreeing with itself
fgraph_from_modelandmodel_from_fgraphare a pair, so every path built onclone_modelinherits the mismatch:Model.copy(),copy.copy,copy.deepcopypm.dopm.observeInvisible to CI: both files sit in the
FAILINGmypy baseline,scripts/run_mypy.py#L25.No consumer breaks on a list
pymc/backends/arviz.py#L226list(dvals), normalizespymc/backends/mcbackend.py#L57list(...), normalizespymc/printing.py#L430.get(), ignores typepymc/variational/opvi.py#L1174.get(), ignores typepymc/model_graph.py#L355inguard, then iteratespymc/testing.pyassert_equivalent_modelnever reads dimsSo this is a contract fix, not a crash fix.
The tests were the defect
Assertions hardcoded the round trip's output instead of comparing to the original:
tests/model/test_fgraph.py#L65== m_old.named_vars_to_dimstests/model/transform/test_conditioning.py#L136== m_old.named_vars_to_dimstests/model/transform/test_conditioning.py#L234== m.named_vars_to_dimstests/model/transform/test_conditioning.py#L241== m.named_vars_to_dimsAdded
tests/model/test_fgraph.py#L404,#L428,#L451,#L475,#L498,#L520for eachisinstancebranch, each dims spelling,clone_model, ModelVarsubclasses, and the unhandled fallthrough. 13 fail without the one-line change.
Incidental
pymc/model/fgraph.py#L378raisedTypeErrornaming theVariable, so itreported a
TensorVariableand said nothing about whichOpwas unrecognised.Now names the
Op.Not fixed
pymc/model/fgraph.py#L258reads.get(name, ())whilepymc/model/fgraph.py#L37defaults
dims=(), so omitted and explicitdims=()collapse in the graph.Strict xfail at
tests/model/test_fgraph.py#L545. Needs a sentinel or a secondprop on
ModelVar, which touches__props__and all fivemodel_*_rvconstructors. Separately,
else Nonetoelse ()trades one silent loss foranother.