From 6210342e2587c97559fd70b209280aab0517da1d Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Wed, 5 Aug 2026 09:38:32 -0400 Subject: [PATCH] fix: pin the unassigned type parameter of the observed dependency graph `observed_dependency_graph` relied on `Matching(v)` inferring `Unassigned` as the unassigned type parameter from a `Vector{Union{Unassigned, Int}}`. BipartiteGraphs v0.1.11 changed that inference to yield `Matching{Union{Unassigned, Int}}`, which no longer matches the concrete field type of `ObservedGraphCache` and makes populating the cache throw a `convert` `MethodError`. Construct `Matching{Unassigned}` explicitly and share the concrete graph type between the constructor and the cache field so they cannot drift. Co-Authored-By: Chris Rackauckas --- lib/ModelingToolkitBase/src/utils.jl | 22 ++++++++++++++----- .../test/code_generation.jl | 11 ++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/ModelingToolkitBase/src/utils.jl b/lib/ModelingToolkitBase/src/utils.jl index 2d23d4dd15..ba5e67a767 100644 --- a/lib/ModelingToolkitBase/src/utils.jl +++ b/lib/ModelingToolkitBase/src/utils.jl @@ -1201,14 +1201,27 @@ function is_numeric_symtype(T::Type) return T <: Number || T <: AbstractArray && is_numeric_symtype(eltype(T)) end +""" +The concrete type of the graph returned by [`observed_dependency_graph`](@ref). +""" +const ObservedDependencyGraphT = DiCMOBiGraph{ + false, Int, BipartiteGraph{Int, Nothing}, + Matching{Unassigned, Vector{Union{Unassigned, Int}}}, +} + """ $(TYPEDSIGNATURES) Return the `DiCMOBiGraph` denoting the dependencies between observed equations `eqs`. """ -function observed_dependency_graph(sys::AbstractSystem, eqs::Vector{Equation}) +function observed_dependency_graph( + sys::AbstractSystem, eqs::Vector{Equation} + )::ObservedDependencyGraphT graph, assigns = observed2graph(sys, eqs, getproperty.(eqs, (:lhs,))) - matching = complete(Matching(Vector{Union{Unassigned, Int}}(assigns))) + # The unassigned type parameter is given explicitly instead of letting `Matching` + # infer it from the eltype, since that inference is not part of a stable contract + # and has differed between `BipartiteGraphs` versions. + matching = complete(Matching{Unassigned}(Vector{Union{Unassigned, Int}}(assigns))) return DiCMOBiGraph{false}(graph, matching) end @@ -1219,10 +1232,7 @@ function should_invalidate_mutable_cache_entry(::Type{ObservedGraphCacheKey}, pa end struct ObservedGraphCache - graph::DiCMOBiGraph{ - false, Int, BipartiteGraph{Int, Nothing}, - Matching{Unassigned, Vector{Union{Unassigned, Int}}}, - } + graph::ObservedDependencyGraphT obsvar_to_idx::Dict{Any, Int} end diff --git a/lib/ModelingToolkitBase/test/code_generation.jl b/lib/ModelingToolkitBase/test/code_generation.jl index 202ed8653b..eb9161ed65 100644 --- a/lib/ModelingToolkitBase/test/code_generation.jl +++ b/lib/ModelingToolkitBase/test/code_generation.jl @@ -158,3 +158,14 @@ end @mtkcomplete sys = System([D(y) ~ 2y + sum(x)], t, [y], []; observed = [x ~ [y, y + 1, y + 2]]) @test ModelingToolkitBase.observed_equations_used_by(sys, [x[1]]) == [1] end + +@testset "`observed_dependency_graph` result is cacheable" begin + @variables x(t) y(t) z(t) + @mtkcompile sys = System([D(x) ~ z, y ~ 2x + 1, z ~ 3y], t) + obs = ModelingToolkitBase.observed(sys) + graph = ModelingToolkitBase.observed_dependency_graph(sys, obs) + @test graph isa fieldtype(ModelingToolkitBase.ObservedGraphCache, :graph) + # this populates the observed graph cache, which requires the above type to match + @test ModelingToolkitBase.observed_equations_used_by(sys, [equations(sys)[1].rhs]) == + [1, 2] +end