Skip to content

Commit c995346

Browse files
Register eigmax/eigmin as symbolic matrix atoms
eigmax(X)/eigmin(X) on a symbolic matrix crashed with "MethodError: no method matching eigvals!(::Matrix{Num})": they have DCP rules (symmetric_domain(), Convex/Concave) but, unlike logdet and eigsummax, no @register_symbolic, so the call fell through to numeric LinearAlgebra.eigmax and tried to compute eigenvalues of a Matrix{Num}. Register both for Matrix{Num} so the call builds an unevaluated symbolic atom and the existing rules fire. This also repairs the previously-broken gDCP eigmax path (add_gdcprule with no backing registration). Collision- safe: eigmax/eigmin return a scalar and a symbolic is not <: Number, so no BasicSymbolic{SymReal} scalar method is overwritten (hasmethod goes false->true, purely additive) — the same pattern as logdet/eigsummax. Verified: clean from-disk precompile, eigmax(X)->Convex, eigmin(X)-> Concave, numeric eigmax/eigmin unchanged. Part of #121. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7d44d89 commit c995346

3 files changed

Lines changed: 22 additions & 0 deletions

File tree

src/atoms.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,17 @@ Symbolics.@register_symbolic invprod(x::AbstractVector)
7272

7373
add_dcprule(invprod, array_domain(HalfLine{Real, :open}()), Positive, Convex, Decreasing)
7474

75+
# eigmax/eigmin of a symbolic matrix must build an unevaluated symbolic atom so
76+
# the curvature pass can dispatch on it; without registration they fall through
77+
# to numeric `LinearAlgebra.eigmax`, which computes `eigvals!(::Matrix{Num})` and
78+
# throws. Unlike the matrix `log`/`inv`/`sqrt` atoms, these return a scalar and a
79+
# symbolic is not `<: Number`, so the emitted method is purely additive (no scalar
80+
# `BasicSymbolic{SymReal}` method is overwritten) — the same safe pattern as
81+
# `logdet` and `eigsummax`.
82+
Symbolics.@register_symbolic LinearAlgebra.eigmax(X::Matrix{Num})
7583
add_dcprule(eigmax, symmetric_domain(), AnySign, Convex, AnyMono)
7684

85+
Symbolics.@register_symbolic LinearAlgebra.eigmin(X::Matrix{Num})
7786
add_dcprule(eigmin, symmetric_domain(), AnySign, Concave, AnyMono)
7887

7988
"""

test/qa/qa.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const SA = SymbolicAnalysis
1212
const SYMBOLIC_OWN = Any[
1313
Base.:*, Base.log, Base.sqrt,
1414
SA.LinearAlgebra.inv, SA.LinearAlgebra.logdet,
15+
SA.LinearAlgebra.eigmax, SA.LinearAlgebra.eigmin,
1516
SA.Symbolics.arguments, SA.Symbolics.hasmetadata, SA.Symbolics.promote_symtype,
1617
SA.SymbolicUtils.promote_shape,
1718
SA.Manifolds.distance, SA.LogExpFunctions.xlogx,

test/test.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,15 @@ ex = propagate_curvature(propagate_sign(ex))
178178
ex = maximum(exp.(z)) |> unwrap
179179
ex = propagate_curvature(propagate_sign(ex))
180180
@test getcurvature(ex) == SymbolicAnalysis.Convex
181+
182+
# eigmax/eigmin of a symbolic matrix must build symbolic atoms (Convex/Concave),
183+
# not fall through to numeric eigmax and crash on eigvals!(::Matrix{Num}).
184+
@variables X[1:3, 1:3]
185+
186+
ex = eigmax(X) |> unwrap
187+
ex = propagate_curvature(propagate_sign(ex))
188+
@test getcurvature(ex) == SymbolicAnalysis.Convex
189+
190+
ex = eigmin(X) |> unwrap
191+
ex = propagate_curvature(propagate_sign(ex))
192+
@test getcurvature(ex) == SymbolicAnalysis.Concave

0 commit comments

Comments
 (0)