@@ -25,6 +25,7 @@ import LinearAlgebra: (\), AdjointFactorization,
2525using SparseArrays
2626using SparseArrays: getcolptr, AbstractSparseVecOrMat
2727export
28+ CholmodWorkspace,
2829 Dense,
2930 Factor,
3031 Sparse
@@ -1915,7 +1916,55 @@ const AbstractSparseVecOrMatInclAdjAndTrans = Union{AbstractSparseVecOrMat, AdjO
19151916 throw (ArgumentError (" self-adjoint sparse system solve not implemented for sparse rhs B," *
19161917 " consider to convert B to a dense array" ))
19171918
1918- # in-place ldiv!
1919+ """
1920+ CholmodWorkspace()
1921+
1922+ Reusable workspace for allocation-free calls to `ldiv!(x, L, b, ws)` with a
1923+ CHOLMOD [`Factor`](@ref).
1924+
1925+ `cholmod_solve2` allocates two temporary dense matrices (Y and E) on every solve.
1926+ A `CholmodWorkspace` holds those buffers across calls so that CHOLMOD reuses them
1927+ instead of reallocating. It also pre-allocates the `cholmod_dense_struct` wrappers
1928+ for `x` and `b`, eliminating all Julia-side heap allocations per solve.
1929+
1930+ The workspace is finalizer-protected: the CHOLMOD-managed Y and E buffers are freed
1931+ automatically when the workspace is garbage-collected. A single workspace can be
1932+ reused across calls with different arrays or a different number of RHS columns;
1933+ CHOLMOD will resize Y and E as needed.
1934+
1935+ # Example
1936+ ```julia
1937+ F = cholesky(A)
1938+ ws = CholmodWorkspace()
1939+ for b in rhs_list
1940+ ldiv!(x, F, b, ws)
1941+ end
1942+ ```
1943+ """
1944+ mutable struct CholmodWorkspace
1945+ dense_x:: cholmod_dense_struct
1946+ dense_b:: cholmod_dense_struct
1947+ X:: Ref{Ptr{cholmod_dense_struct}}
1948+ Y:: Ref{Ptr{cholmod_dense_struct}}
1949+ E:: Ref{Ptr{cholmod_dense_struct}}
1950+
1951+ function CholmodWorkspace ()
1952+ ws = new (
1953+ cholmod_dense_struct (), # all fields written in ldiv! before use
1954+ cholmod_dense_struct (),
1955+ Ref (Ptr {cholmod_dense_struct} (C_NULL )),
1956+ Ref (Ptr {cholmod_dense_struct} (C_NULL )),
1957+ Ref (Ptr {cholmod_dense_struct} (C_NULL ))
1958+ )
1959+ finalizer (ws) do w
1960+ w. Y[] != C_NULL && free! (w. Y[])
1961+ w. E[] != C_NULL && free! (w. E[])
1962+ end
1963+ ws
1964+ end
1965+ end
1966+
1967+ # in-place ldiv! and optional allocation-free ldiv!
19191968for TI in IndexTypes
19201969 @eval function ldiv! (x:: StridedVecOrMat{T} ,
19211970 L:: Factor{T, $TI} ,
@@ -1973,6 +2022,69 @@ for TI in IndexTypes
19732022
19742023 return x
19752024 end
2025+
2026+ @eval function ldiv! (x:: StridedVecOrMat{T} ,
2027+ L:: Factor{T, $TI} ,
2028+ b:: StridedVecOrMat{T} ,
2029+ ws:: CholmodWorkspace ) where {T<: VTypes }
2030+ if x === b
2031+ throw (ArgumentError (" output array must not be aliased with input array" ))
2032+ end
2033+ if size (L, 1 ) != size (b, 1 )
2034+ throw (DimensionMismatch (" Factorization and RHS should have the same number of rows. " *
2035+ " Factorization has $(size (L, 2 )) rows, but RHS has $(size (b, 1 )) rows." ))
2036+ end
2037+ if size (L, 2 ) != size (x, 1 )
2038+ throw (DimensionMismatch (" Factorization and solution should match sizes. " *
2039+ " Factorization has $(size (L, 1 )) columns, but solution has $(size (x, 1 )) rows." ))
2040+ end
2041+ if size (x, 2 ) != size (b, 2 )
2042+ throw (DimensionMismatch (" Solution and RHS should have the same number of columns. " *
2043+ " Solution has $(size (x, 2 )) columns, but RHS has $(size (b, 2 )) columns." ))
2044+ end
2045+ if ! issuccess (L)
2046+ s = unsafe_load (pointer (L))
2047+ if s. is_ll == 1
2048+ throw (LinearAlgebra. PosDefException (s. minor))
2049+ else
2050+ throw (LinearAlgebra. ZeroPivotException (s. minor))
2051+ end
2052+ end
2053+
2054+ # Update all fields of the reused cholmod_dense_structs on every call,
2055+ # since T, dimensions, and data pointers may all change between calls.
2056+ ws. dense_x. nrow = size (x, 1 )
2057+ ws. dense_x. ncol = size (x, 2 )
2058+ ws. dense_x. nzmax = length (x)
2059+ ws. dense_x. d = stride (x, 2 )
2060+ ws. dense_x. x = pointer (x)
2061+ ws. dense_x. z = C_NULL
2062+ ws. dense_x. xtype = xtyp (T)
2063+ ws. dense_x. dtype = dtyp (T)
2064+
2065+ ws. dense_b. nrow = size (b, 1 )
2066+ ws. dense_b. ncol = size (b, 2 )
2067+ ws. dense_b. nzmax = length (b)
2068+ ws. dense_b. d = stride (b, 2 )
2069+ ws. dense_b. x = pointer (b)
2070+ ws. dense_b. z = C_NULL
2071+ ws. dense_b. xtype = xtyp (T)
2072+ ws. dense_b. dtype = dtyp (T)
2073+
2074+ ws. X[] = Ptr {cholmod_dense_struct} (pointer_from_objref (ws. dense_x))
2075+ Bptr = Ptr {cholmod_dense_struct} (pointer_from_objref (ws. dense_b))
2076+ status = GC. @preserve x b ws begin
2077+ $ (cholname (:solve2 , TI))(
2078+ CHOLMOD_A, L,
2079+ Bptr, C_NULL ,
2080+ ws. X, C_NULL ,
2081+ ws. Y, ws. E,
2082+ getcommon ($ TI))
2083+ end
2084+ @assert ! iszero (status)
2085+
2086+ return x
2087+ end
19762088end
19772089
19782090# # Other convenience methods
0 commit comments