Repository navigation
Validate static integer shapes in VirtualBrownianTree and UnsafeBrownianPath (Fixes #707) - #787
Open
akatsukiwork0320 wants to merge 3 commits into
Conversation
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.
Fixes #707.
Problem & Root Cause
Both
VirtualBrownianTreeandUnsafeBrownianPathstoreshape: PyTree[jax.ShapeDtypeStruct] = eqx.field(static=True).jnp.int32(2)is passed insidejax.ShapeDtypeStruct(shape=(jnp.int32(2),), dtype=jnp.float32),ShapeDtypeStruct.__init__convertsshapeviatuple(shape)without canonicalizing elements to Pythonint, leaving(Array(2, dtype=int32),)inside the staticPyTreeDefmetadata of the Brownian path.path.evaluatecalled inside an outer@jax.jitfunctioneval_paththat is defined and traced twice with two separately constructed paths), the second call triggerspjitcache comparison (_infer_params_cached) between the cachedPyTreeDefand the newPyTreeDefwhile the outer JIT trace (trace_to_jaxpr_nocache) is active. ComparingArray(2) == Array(2)under an active trace produces a traced boolean (DynamicJaxprTracerwithaval=bool[], which raisesTracerBoolConversionErroron boolean conversion) rather than a concretebool, causingPyTreeDefequality to fail:ValueError: Exception raised while checking equality of metadata fields of pytree node....pjitcache lookup, though not verified with a C++ stack trace).shape=(jnp.int32(2),)orshape=(np.int64(2),)directly as a tuple failsis_tuple_of_ints(shape)(which only checkedisinstance(x, int)), leavingself.shapeun-wrapped as a tuple of integers and raising the misleading errorValueError: VirtualBrownianTree dtypes all have to be floating-point.Solution & Design Choices
diffrax/_misc.py(is_tuple_of_ints):intand NumPy integers (isinstance(x, (int, np.integer)) and not isinstance(x, bool)). This preserves support forjax.ShapeDtypeStruct((np.int64(2),), ...)and also allows direct tuples likeshape=(np.int64(2),)to work withlxi.default_floating_dtype()(respectingjax_enable_x64), while rejectingbool, JAXArrays, and JAX tracers.VirtualBrownianTree.__init__andUnsafeBrownianPath.__init__:shapeannotation acceptstuple[int | np.integer, ...] | PyTree[jax.ShapeDtypeStruct]so that direct tuples of NumPy integers match the runtime acceptance and pass the repository'sjaxtyping/beartypechecks. This contract is covered by the new regression tests.xinjtu.tree_leaves(self.shape)is ajax.ShapeDtypeStructwithis_tuple_of_ints(x.shape), raising a clearValueErrorat construction time before any JIT compilation occurs. ExistingShapeDtypeStructleaves are kept as-is (preserving attributes such asweak_type)..shapeand.dtypeother thanjax.ShapeDtypeStructshould also be accepted, the check can be relaxed tohasattr(x, "shape") and hasattr(x, "dtype") and is_tuple_of_ints(x.shape).)test/test_brownian.py):import numpy as npandtest_brownian_shape_validationcovering bothVirtualBrownianTreeandUnsafeBrownianPathfor rejectedjnp.int32/boolshapes and acceptednp.int64shapes across repeated outer@jax.jitevaluations.test/test_brownian.pypassed with the defaultjaxtyping/beartypehook:test_shape_and_dtype,test_levy_area_reverse_time, andtest_brownian_shape_validation(35 deselected; the remaining 35 statistical/spline/WHK interpolation tests intest/test_brownian.py—test_statistics,test_conditional_statistics,test_spline,test_whk_interpolation— were not completed locally (no completed passing result) and are left to CI).-o addopts=""(48 deselected).test_citation.py,test_adaptive_stepsize_controller.py,test_adjoint.py,test_solver.py,test_underdamped_langevin.py,test_sde2.py) passed (77 deselected). This was a selected regression run, not the full suite.checkandformat --checkpassed for all four changed files.type-ignoreerror indiffrax/_misc.py(reportUnnecessaryTypeIgnoreCommentat_misc.py:175on base /_misc.py:177on patched) on both the base and patched code, with no additional diagnostics in the four checked files. The type check is therefore not a clean pass (exit_code = 1on both base and patched).