@@ -34,7 +34,7 @@ A4 = A2 .|> ComplexF32
3434b4 = b2 .| > ComplexF32
3535x4 = x2 .| > ComplexF32
3636
37- A5_ = A - 0.01 Tridiagonal (ones (n,n)) + sparse ([1 ], [8 ], 0.5 , n,n)
37+ A5_ = A - 0.01 Tridiagonal (ones (n, n)) + sparse ([1 ], [8 ], 0.5 , n, n)
3838A5 = sparse (transpose (A5_) * A5_)
3939x5 = zeros (n)
4040u5 = ones (n)
@@ -46,7 +46,7 @@ prob3 = LinearProblem(A3, b3; u0 = x3)
4646prob4 = LinearProblem (A4, b4; u0 = x4)
4747prob5 = LinearProblem (A5, b5)
4848
49- cache_kwargs = (;abstol = 1e-8 , reltol = 1e-8 , maxiter = 30 )
49+ cache_kwargs = (; abstol = 1e-8 , reltol = 1e-8 , maxiter = 30 )
5050
5151function test_interface (alg, prob1, prob2)
5252 A1, b1 = prob1. A, prob1. b
7979
8080function test_tolerance_update (alg, prob, u)
8181 cache = init (prob, alg)
82- LinearSolve. update_tolerances! (cache; reltol = 1e-2 , abstol= 1e-8 )
82+ LinearSolve. update_tolerances! (cache; reltol = 1e-2 , abstol = 1e-8 )
8383 u1 = copy (solve! (cache). u)
8484
85- LinearSolve. update_tolerances! (cache; reltol = 1e-8 , abstol= 1e-8 )
85+ LinearSolve. update_tolerances! (cache; reltol = 1e-8 , abstol = 1e-8 )
8686 u2 = solve! (cache). u
8787
8888 @test norm (u2 - u) < norm (u1 - u)
@@ -303,30 +303,86 @@ end
303303 ρ = 0.95
304304 A_tri = SymTridiagonal (ones (k) .+ ρ^ 2 , - ρ * ones (k- 1 ))
305305 b = rand (k)
306-
306+
307307 # Test with explicit LDLtFactorization
308308 prob_tri = LinearProblem (A_tri, b)
309309 sol = solve (prob_tri, LDLtFactorization ())
310310 @test A_tri * sol. u ≈ b
311-
311+
312312 # Test that default algorithm uses LDLtFactorization for SymTridiagonal
313313 default_alg = LinearSolve. defaultalg (A_tri, b, OperatorAssumptions (true ))
314314 @test default_alg isa LinearSolve. DefaultLinearSolver
315315 @test default_alg. alg == LinearSolve. DefaultAlgorithmChoice. LDLtFactorization
316-
316+
317317 # Test that the factorization is cached and reused
318318 cache = init (prob_tri, LDLtFactorization ())
319319 sol1 = solve! (cache)
320320 @test A_tri * sol1. u ≈ b
321321 @test ! cache. isfresh # Cache should not be fresh after first solve
322-
322+
323323 # Solve again with same matrix to ensure cache is reused
324324 cache. b = rand (k) # Change RHS
325325 sol2 = solve! (cache)
326326 @test A_tri * sol2. u ≈ cache. b
327327 @test ! cache. isfresh # Cache should still not be fresh
328328 end
329329
330+ @testset " Tridiagonal cache not mutated (issue #825)" begin
331+ # Test that solving with Tridiagonal does not mutate cache.A
332+ # See https://github.com/SciML/LinearSolve.jl/issues/825
333+ k = 6
334+ lower = ones (k - 1 )
335+ diag = - 2 * ones (k)
336+ upper = ones (k - 1 )
337+ A_tri = Tridiagonal (lower, diag, upper)
338+ b = rand (k)
339+
340+ # Store original matrix values for comparison
341+ A_orig = Tridiagonal (copy (lower), copy (diag), copy (upper))
342+
343+ # Test that default algorithm uses DirectLdiv! for Tridiagonal on Julia 1.11+
344+ default_alg = LinearSolve. defaultalg (A_tri, b, OperatorAssumptions (true ))
345+ @static if VERSION >= v " 1.11"
346+ @test default_alg isa DirectLdiv!
347+ else
348+ @test default_alg isa LinearSolve. DefaultLinearSolver
349+ @test default_alg. alg == LinearSolve. DefaultAlgorithmChoice. LUFactorization
350+ end
351+
352+ # Test with default algorithm
353+ prob_tri = LinearProblem (A_tri, b)
354+ cache = init (prob_tri)
355+
356+ # Verify solution is correct
357+ sol1 = solve! (cache)
358+ @test A_orig * sol1. u ≈ b
359+
360+ # Verify cache.A is not mutated
361+ @test cache. A ≈ A_orig
362+
363+ # Verify multiple solves give correct answers
364+ b2 = rand (k)
365+ cache. b = b2
366+ sol2 = solve! (cache)
367+ @test A_orig * sol2. u ≈ b2
368+
369+ # Cache.A should still be unchanged
370+ @test cache. A ≈ A_orig
371+
372+ # Verify solve! allocates minimally after first solve (warm-up)
373+ # The small allocation (48 bytes) is from the return type construction,
374+ # same as other factorization methods like LUFactorization
375+ @static if VERSION >= v " 1.11"
376+ # Warm up
377+ for _ in 1 : 3
378+ solve! (cache)
379+ end
380+ # Test minimal allocations (same as LUFactorization)
381+ allocs = @allocated solve! (cache)
382+ @test allocs <= 64 # Allow small overhead from return type
383+ end
384+ end
385+
330386 test_algs = [
331387 LUFactorization (),
332388 QRFactorization (),
680736 prob3 = LinearProblem (op1, b1; u0 = x1)
681737 prob4 = LinearProblem (op2, b2; u0 = x2)
682738
683- @test LinearSolve. defaultalg (op1, x1). alg === LinearSolve. DefaultAlgorithmChoice. DirectLdiv!
684- @test LinearSolve. defaultalg (op2, x2). alg === LinearSolve. DefaultAlgorithmChoice. DirectLdiv!
739+ @test LinearSolve. defaultalg (op1, x1). alg ===
740+ LinearSolve. DefaultAlgorithmChoice. DirectLdiv!
741+ @test LinearSolve. defaultalg (op2, x2). alg ===
742+ LinearSolve. DefaultAlgorithmChoice. DirectLdiv!
685743 @test LinearSolve. defaultalg (op3, x1). alg ===
686744 LinearSolve. DefaultAlgorithmChoice. KrylovJL_GMRES
687745 @test LinearSolve. defaultalg (op4, x2). alg ===
800858 reinit! (cache; A = B1, b = b1)
801859 u = solve! (cache)
802860 @test norm (u - u0, Inf ) < 1.0e-8
803-
804861end
805862
806863@testset " ParallelSolves" begin
818875 for i in 1 : 2
819876 @test sol[i] ≈ U[i]
820877 end
821-
878+
822879 Threads. @threads for i in 1 : 2
823880 sol[i] = solve (LinearProblem (A_sparse, B[i]), KLUFactorization ())
824881 end
0 commit comments