Skip to content

collect_vars! bug on 1.10 when loading DynamicQuantities #4211

@isaacsas

Description

@isaacsas

This following report was generated by Claude Code, but I've verified that the bug manifests on 1.10 and not 1.11, and only occurs when DynamicQuantities is loaded as stated. This is causing test failures in the Catalyst tests as we update to MTK 11.

Issue: collect_vars! fails to discover parameters in defaults on Julia 1.10 when DynamicQuantities is loaded

Summary

On Julia 1.10 (but not 1.11), ModelingToolkitBase.collect_vars! fails to discover parameters used in default values of variables when DynamicQuantities is loaded.

Minimal Reproducer

# test_minimal.jl
using ModelingToolkitBase: @parameters
using Symbolics: @variables, unwrap, Operator
using DataStructures: OrderedSet
import ModelingToolkitBase as MT
using DynamicQuantities  # THIS TRIGGERS THE BUG on 1.10

const SymbolicT = MT.SymbolicT

@variables t
@parameters X0
@variables X(t) = X0

us = OrderedSet{SymbolicT}()
ps = OrderedSet{SymbolicT}()
MT.collect_vars!(us, ps, unwrap(X), unwrap(t), Operator; depth=0)

println("Julia ", VERSION)
println("params: ", collect(ps))
println("Expected: [X0], Got: ", collect(ps))
# Julia 1.10: params = []   (BUG)
# Julia 1.11: params = [X0] (CORRECT)

Root Cause Analysis

  1. The bug only manifests when DynamicQuantities is loaded - Without it, collect_vars! works correctly on Julia 1.10.

  2. collect_var! works correctly when called directly - The bug is in how collect_vars! internally calls collect_var!, not in collect_var! itself.

  3. Workaround: Call collect_var! first - If collect_var! is called directly on any expression before collect_vars! is called, subsequent collect_vars! calls work correctly:

# Workaround: "warm up" by calling collect_var! first
dummy_us = OrderedSet{SymbolicT}()
dummy_ps = OrderedSet{SymbolicT}()
MT.collect_var!(dummy_us, dummy_ps, some_expr, iv; depth=0)

# Now collect_vars! works correctly
MT.collect_vars!(us, ps, expr, iv, Operator; depth=0)
  1. Not a world age issue - Base.invokelatest does not fix the problem.

Proposed Fixes

Add a dummy collect_var! call in the extension's __init__ function to trigger proper method compilation:

function __init__()
    # Existing code...
    MTK.t = let
        only(@independent_variables t [unit = DQ.u"s"])
    end
    SymbolicUtils.hashcons(unwrap(MTK.t), true)
    MTK.D = Differential(MTK.t)

    # NEW: Workaround for Julia 1.10 compiler bug
    if VERSION < v"1.11"
        # Force collect_var! compilation to fix collect_vars!
        @variables _dummy_t
        @parameters _dummy_p
        @variables _dummy_x(_dummy_t) = _dummy_p
        _us = OrderedSet{SymbolicT}()
        _ps = OrderedSet{SymbolicT}()
        MTK.collect_var!(_us, _ps, unwrap(_dummy_x), unwrap(_dummy_t); depth=0)
    end
end

Environment

Project.toml on 1.10

Status `~/.julia/dev/Catalyst/Project.toml`
⌅ [861a8166] Combinatorics v1.0.2
  [864edb3b] DataStructures v0.19.3
  [2b5f629d] DiffEqBase v6.199.0
  [ffbed154] DocStringExtensions v0.9.5
  [7c1d4256] DynamicPolynomials v0.6.4
  [06fc5a27] DynamicQuantities v1.11.0
  [4e289a0a] EnumX v1.0.6
  [86223c79] Graphs v1.13.4
  [ccbc3e58] JumpProcesses v9.21.0
  [b964fa9f] LaTeXStrings v1.4.0
  [23fbe1c1] Latexify v0.16.10
  [1914dd2f] MacroTools v0.5.16
  [7771a370] ModelingToolkitBase v1.6.3
  [d96e819e] Parameters v0.12.3
  [189a3867] Reexport v1.2.2
  [ae029012] Requires v1.3.1
  [7e49a35a] RuntimeGeneratedFunctions v0.5.16
  [0bca4576] SciMLBase v2.134.0
  [efcf1570] Setfield v1.1.2
  [d1185830] SymbolicUtils v4.13.1
  [0c5d862f] Symbolics v7.8.0
  [1986cc42] Unitful v1.27.0
  [37e2e46d] LinearAlgebra
  [2f01184e] SparseArrays v1.10.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions