@@ -166,11 +166,27 @@ def _build_instance_pytree_funcs(cls):
166166 Classification is read from the shared ``_CLASS_FIELD_CLASSIFIERS`` dict,
167167 which is updated by every ``register_model`` call. Attributes unknown to
168168 the classifier (never declared on any walked model) default to constant —
169- safer than tracing an unknown object.
169+ safer than tracing an unknown object — with one override: an attribute
170+ whose *value* is a JAX array or tracer is always a dynamic child,
171+ whatever the classifier says. Such values arise from attributes derived
172+ inside ``__init__`` from traced parameters (e.g. an ``NFWMCRLudlowSph``
173+ computing ``scale_radius`` from a free ``mass_at_200``); as aux data they
174+ survive the flatten as raw Python references and re-enter nested traces
175+ (a ``custom_jvp`` rule's inner jvp) as stale tracers, raising
176+ ``UnexpectedTracerError``. A traced value is never safe aux.
170177 """
171178 constructor_args = _CLASS_CONSTRUCTOR_ARGS .get (cls , ())
172179 constructor_arg_set = set (constructor_args )
173180
181+ def _is_jax_value (value ):
182+ import jax
183+
184+ if isinstance (value , (jax .Array , jax .core .Tracer )):
185+ return True
186+ if isinstance (value , (tuple , list )):
187+ return any (_is_jax_value (v ) for v in value )
188+ return False
189+
174190 def _partition (instance ):
175191 classifier = _CLASS_FIELD_CLASSIFIERS .get (cls , {})
176192 ctor_dyn : list = []
@@ -180,7 +196,7 @@ def _partition(instance):
180196 for name , value in vars (instance ).items ():
181197 if name .startswith ("_" ) or name in ("cls" , "id" ):
182198 continue
183- is_dynamic = classifier .get (name , False )
199+ is_dynamic = classifier .get (name , False ) or _is_jax_value ( value )
184200 in_ctor = name in constructor_arg_set
185201 if in_ctor and is_dynamic :
186202 ctor_dyn .append ((name , value ))
0 commit comments