Skip to content

Commit 87232e8

Browse files
Merge pull request #97 from JuliaComputing/os/fix-inline-linear-solve
fix edge case in inline linear solve
2 parents 3d10ed7 + ae78443 commit 87232e8

4 files changed

Lines changed: 72 additions & 5 deletions

File tree

lib/ModelingToolkitTearing/src/reassemble.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,7 +787,8 @@ function __reduce_linear_system!(A::StateSelection.CLIL.SparseMatrixCLIL{Num, In
787787
cst += coeff * other_cst
788788
end
789789

790-
aliases[var] = SparseArrays.sparsevec(new_I, new_V, length(coeffs))
790+
# `sparsevec` sums duplicate indices but keeps explicit zeros; drop them.
791+
aliases[var] = SparseArrays.dropzeros!(SparseArrays.sparsevec(new_I, new_V, length(coeffs)))
791792
constants[var] = cst
792793
end
793794

src/math/sparsematrixclil.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,24 @@ zero!(a::SparseVector) = (empty!(a.nzind); empty!(a.nzval))
9191
zero!(a::CLILVector) = zero!(a.vec)
9292
SparseArrays.dropzeros!(a::CLILVector) = SparseArrays.dropzeros!(a.vec)
9393

94+
# Remove explicitly-stored zeros from each row, in place.
95+
function SparseArrays.dropzeros!(S::SparseMatrixCLIL)
96+
for r in eachindex(S.row_vals)
97+
cols = S.row_cols[r]
98+
vals = S.row_vals[r]
99+
j = 0
100+
for k in eachindex(vals)
101+
iszero(vals[k]) && continue
102+
j += 1
103+
cols[j] = cols[k]
104+
vals[j] = vals[k]
105+
end
106+
resize!(cols, j)
107+
resize!(vals, j)
108+
end
109+
return S
110+
end
111+
94112
struct NonZeros{T <: AbstractArray}
95113
v::T
96114
end

src/utils.jl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,24 +340,30 @@ function get_new_mm(
340340
push!(final_row_cols, new_row_col_i[indices[1]])
341341
push!(final_row_vals, new_row_val_i[indices[1]])
342342
for i in Iterators.drop(eachindex(indices), 1)
343-
if new_row_col_i[indices[i]] == new_row_col_i[indices[i - 1]]
343+
col = new_row_col_i[indices[i]]
344+
# Compare against the last retained column, not the previous sorted
345+
# entry: a prior cancellation may have `pop!`ed the matching entry.
346+
if !isempty(final_row_cols) && col == final_row_cols[end]
344347
final_row_vals[end] += new_row_val_i[indices[i]]
345348
if iszero(final_row_vals[end])
346349
pop!(final_row_cols)
347350
pop!(final_row_vals)
348351
end
349352
else
350-
push!(final_row_cols, new_row_col_i[indices[i]])
353+
push!(final_row_cols, col)
351354
push!(final_row_vals, new_row_val_i[indices[i]])
352355
end
353356
end
354-
357+
355358
push!(new_row_cols, final_row_cols)
356359
push!(new_row_vals, final_row_vals)
357360
push!(new_nzrows, old_to_new_eq[eq])
358361
end
359362

360-
return typeof(mm)(new_nparentrows, count(!iszero, old_to_new_var), new_nzrows, new_row_cols, new_row_vals)
363+
# Drop explicit zeros (e.g. from an alias that cancelled to zero) so they
364+
# don't survive as phantom structural nonzeros.
365+
new_mm = typeof(mm)(new_nparentrows, count(!iszero, old_to_new_var), new_nzrows, new_row_cols, new_row_vals)
366+
return SparseArrays.dropzeros!(new_mm)
361367
end
362368

363369
struct BadMMAliasError <: Exception

test/runtests.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,46 @@ include("carpanzano_tearing.jl")
2929
@test mm2.ncols == 2
3030
aliases = Dict(1 => sparse([0, 0, 2, 1]))
3131
@test_throws SSel.BadMMAliasError SSel.get_new_mm(aliases, old_to_new_eq, old_to_new_var, mm)
32+
33+
# Three entries collapse onto the same column, and the first two cancel to
34+
# zero. The remaining duplicate must survive as a fresh entry rather than
35+
# being folded into the (now popped) accumulator.
36+
# Row `[2 1 1]`: var1 retained, var2 -> -2*var1, var3 -> 3*var1.
37+
# Contributions to new col 1: 2 (direct) + 1*(-2) + 1*3 = 3.
38+
mm = SSel.CLIL.SparseMatrixCLIL([2 1 1])
39+
old_to_new_eq = [1]
40+
old_to_new_var = [1, 0, 0]
41+
aliases = Dict(2 => sparse([-2, 0, 0]), 3 => sparse([3, 0, 0]))
42+
mm2 = SSel.get_new_mm(aliases, old_to_new_eq, old_to_new_var, mm)
43+
@test mm2.nzrows == [1]
44+
@test mm2.row_cols == [[1]]
45+
@test mm2.row_vals == [[3]]
46+
@test mm2.ncols == 1
47+
48+
# Same cancellation, but with a distinct retained column already present
49+
# before the cancelling group. The surviving duplicate must not leak into
50+
# the earlier column.
51+
# Row `[7 2 1 1]`: var1 -> col1, var2 -> col2, var3 -> -2*var2, var4 -> 5*var2.
52+
# col1 = 7, col2 = 2 + 1*(-2) + 1*5 = 5.
53+
mm = SSel.CLIL.SparseMatrixCLIL([7 2 1 1])
54+
old_to_new_eq = [1]
55+
old_to_new_var = [1, 2, 0, 0]
56+
aliases = Dict(3 => sparse([0, -2, 0, 0]), 4 => sparse([0, 5, 0, 0]))
57+
mm2 = SSel.get_new_mm(aliases, old_to_new_eq, old_to_new_var, mm)
58+
@test mm2.nzrows == [1]
59+
@test mm2.row_cols == [[1, 2]]
60+
@test mm2.row_vals == [[7, 5]]
61+
@test mm2.ncols == 2
62+
63+
# An alias carrying an explicit stored zero (as `sparsevec` produces when
64+
# duplicate indices cancel) must not leak in as a phantom structural nonzero.
65+
# Row references var2 (removed, aliased to 0*var1) and var3 (retained); there
66+
# is no direct var1 term, so the only contribution to col 1 is the zero.
67+
mm = SSel.CLIL.SparseMatrixCLIL([0 1 1])
68+
old_to_new_eq = [1]
69+
old_to_new_var = [1, 0, 2]
70+
aliases = Dict(2 => sparsevec([1, 1], [2, -2], 3))
71+
mm2 = SSel.get_new_mm(aliases, old_to_new_eq, old_to_new_var, mm)
72+
@test mm2.row_cols == [[2]]
73+
@test mm2.row_vals == [[1]]
3274
end

0 commit comments

Comments
 (0)