From 27135ad78da9ed34314e8e883f0caed387d11e42 Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 9 Jul 2025 05:31:01 -0500 Subject: [PATCH 1/9] Created col_projection.jl --- src/Solvers/col_projection.jl | 231 ++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/Solvers/col_projection.jl diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl new file mode 100644 index 00000000..771fe9ca --- /dev/null +++ b/src/Solvers/col_projection.jl @@ -0,0 +1,231 @@ +""" + col_projection <: Solver + +An implementation of a column projection solver. Specifically, it is a solver that iteratively + updates a solution by projection the solution onto a compressed columnspace of the linear + system. + +# Fields +- `S::Compressor`, a technique for forming the compressed columnspace of the linear system. +- `log::Logger`, a technique for logging the progress of the solver. +- `error::SolverError`, a method for estimating the progress of the solver. +- `sub_solver::SubSolver`, a technique to perform the projection of the solution onto the +compressed rowspace. +- `alpha::Float64`, the over-relaxation parameter. It is multiplied by the update and can +affect convergence. +""" +#------------------------------------------------------------------ +mutable struct col_projection <: Solver + alpha::Float64 + S::Compressor + log::Logger + error::SolverError + sub_solver::SubSolver +end + +#------------------------------------------------------------------ +function col_projection(; + alpha::Float64 = 1.0, + S::Compressor = SparseSign(), + log::Logger = BasicLogger(), + error::SolverError = FullResidual(), + sub_solver::SubSolver = QRSolver(), +) + +# Intialize the datatype setting unkown elements to empty versions of correct datatype +return col_projection(alpha, S, log, error, sub_solver) +end + +#------------------------------------------------------------------ +mutable struct col_projectionRecipe{T<:Number, + V<:AbstractVector, + M<:AbstractMatrix, + MV<:SubArray, + C<:CompressorRecipe, + L<:LoggerRecipe, + E<:SolverErrorRecipe, + B<:SubSolverRecipe + } <: SolverRecipe +S::C +log::L +error::E +sub_solver::B +alpha::Float64 +compressed_mat::M +compressed_vec::V +solution_vec::V +update_vec::V +mat_view::MV +residual_vec::V +end + +#------------------------------------------------------------------ +function complete_solver( + solver::col_projection, + x::AbstractVector, + A::AbstractMatrix, + b::AbstractVector +) +# Check the dimensions align + + +# Dimension checking will be performed in the complete_compressor +compressor = complete_compressor(solver.S, A, b) +logger = complete_logger(solver.log, A, b) +error = complete_error(solver.error, A, b) +# Check that required fields are in the types +@assert isdefined(error, :residual) "ErrorRecipe $(typeof(error)) does not contain the field 'residual' and is not valid for a col_projection solver." +@assert isdefined(logger, :converged) "LoggerRecipe $(typeof(logger)) does not contain the field 'converged' and is not valid for a col_projection solver." +# Assuming that max_it is defined in the logger +alpha::Float64 = solver.alpha +# We assume the user is using compressors to only decrease dimension +n_rows::Int64 = compressor.n_rows +n_cols::Int64 = compressor.n_cols +initial_size = n_rows +sample_size = n_cols +rows_a, cols_a = size(A) +# Allocate the information in the buffer using the types of A and b +compressed_mat = typeof(A)(undef, rows_a, sample_size) +compressed_vec = typeof(b)(undef, rows_a) +# Since sub_solver is applied to compressed matrices use here +sub_solver = complete_sub_solver(solver.sub_solver, compressed_mat, compressed_vec) +mat_view = view(compressed_mat, :, 1:sample_size) ###################### +solution_vec = x +update_vec = typeof(x)(undef, n_cols) +residual_vec = typeof(x)(undef, rows_a) +return col_projectionRecipe{eltype(A), + typeof(b), + typeof(A), + typeof(mat_view), + typeof(compressor), + typeof(logger), + typeof(error), + typeof(sub_solver) + }(compressor, + logger, + error, + sub_solver, + alpha, + compressed_mat, + compressed_vec, + solution_vec, + update_vec, + mat_view, + residual_vec + ) +end + +#--------------------------------------------------------------- +function rsolve!( + solver::col_projectionRecipe, + x::AbstractVector, + A::AbstractMatrix, + b::AbstractVector +) +# initialization +solver.solution_vec = x +err = 0.0 +# compute the residual b-Ax +mul!(solver.residual_vec, A, x, -1, 0) +solver.residual_vec .+= b +for i in 1:solver.log.max_it + # generate a new version of the compression matrix + update_compressor!(solver.S, A, b, x) + # based on size of new compressor update views of matrix + # this should not result in new allocations + rows_s, cols_s = size(solver.S) + solver.mat_view = view(solver.compressed_mat, :, 1:cols_s) ################## A_new is mxk + # compress the matrix and constant vector + mul!(solver.mat_view, A, solver.S) ################## A_new=AS + err = compute_error(solver.error, solver, A, b) + # Update log adds value of err to log and checks stopping + update_logger!(solver.log, err, i) + if solver.log.converged + return solver.solution_vec, solver.log[solver.log .> 0] + end + + # sub-solver needs to designed for new compressed matrix + update_sub_solver!(solver.sub_solver, solver.mat_view) + # use sub-solver to find update the solution + sub_solve!(solver.update_vec, solver.sub_solver, solver.residual_vec) ######### + # Using over-relaxation parameter, alpha, to update solution + # x += alpha * S * update_vec + mul!(solver.solution_vec, solver.S, solver.update_vec, solver.alpha, 1.0) + # Compute the residual r-ASx + mul!(solver.residual_vec, solver.mat_view, solver.solution_vec, -1.0, 1.0) ### nothing changed +end + +return solver.solution_vec, solver.log +end + +#---------------------------------------------------------------------- +""" + FullResidual <: SolverErrorRecipe + +A structure for the full residual, `b-Ax`. + +# Fields +- `residual::AbstractVector`, a container for the residual `b-Ax`. +""" + +struct LSgradient <: SolverError + +end + +mutable struct LSgradientRecipe{V<:AbstractVector} <: SolverErrorRecipe + gradient::V +end + +function complete_error(error::LSgradient, A::AbstractMatrix, b::AbstractVector) + return LSgradientRecipe{typeof(b)}(zeros(size(b,1))) +end + +function compute_error( + error::LSgradientRecipe, + solver::col_projectionRecipe, + A::AbstractMatrix, + b::AbstractVector + )::Float64 + copyto!(error.gradient, b) + mul!(error.gradient, A', solver.residual_vec, -1.0, 1.0) ####r=b-A(Sx) + + return dot(error.gradient, error.gradient) +end + +""" + CompressedLSgradientRecipe <: SolverErrorRecipe + +A structure for the compressed residual, `b-ASx`. + +# Fields +- `gradient::AbstractVector`, a container for the compressed residual, `b-ASx`. +- `gradient_view::SubArray`, a view of the residual container to handle varying compression +sizes. +""" + +struct compressedLSgradient <: SolverError + +end + +mutable struct compressedLSgradientRecipe{V<:AbstractVector, S<:SubArray} <: SolverErrorRecipe + gradient::V + gradient_view::S +end + +function complete_error(error::compressedLSgradient, A::AbstractMatrix, b::AbstractVector) + gradient = zeros(size(b,1)) + gradient_view = view(gradient, 1:1) + return CompressedLSgradientRecipe{typeof(gradient),typeof(gradient_view)}(gradient, gradient_view) +end + +function compute_error( + error::compressedLSgradientRecipe, + solver::col_projectionRecipe, + A::AbstractMatrix, + b::AbstractVector +)::Float64 + rows_s = size(solver.S, 1) + error.gradient_view = view(error.gradient, 1:rows_s) + mul!(error.gradient_view, solver.mat_view', solver.residual_vec, -1.0, 1.0) + return dot(error.gradient_view, error.gradient_view) +end From af68c53881558fc5694ce7720448ed257aab712f Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 16 Jul 2025 10:37:14 -0500 Subject: [PATCH 2/9] Updated the col_projection.jl and testing file --- src/Solvers/col_projection.jl | 339 +++++++++++++++++++-------------- test/Solvers/col_projection.jl | 267 ++++++++++++++++++++++++++ 2 files changed, 466 insertions(+), 140 deletions(-) create mode 100644 test/Solvers/col_projection.jl diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl index 771fe9ca..2ddb4328 100644 --- a/src/Solvers/col_projection.jl +++ b/src/Solvers/col_projection.jl @@ -2,15 +2,15 @@ col_projection <: Solver An implementation of a column projection solver. Specifically, it is a solver that iteratively - updates a solution by projection the solution onto a compressed columnspace of the linear + updates a solution by projection the solution onto a compressed rowspace of the linear system. # Fields -- `S::Compressor`, a technique for forming the compressed columnspace of the linear system. +- `S::Compressor`, a technique for forming the compressed rowspace of the linear system. - `log::Logger`, a technique for logging the progress of the solver. - `error::SolverError`, a method for estimating the progress of the solver. - `sub_solver::SubSolver`, a technique to perform the projection of the solution onto the -compressed rowspace. +compressed column space. - `alpha::Float64`, the over-relaxation parameter. It is multiplied by the update and can affect convergence. """ @@ -21,23 +21,55 @@ mutable struct col_projection <: Solver log::Logger error::SolverError sub_solver::SubSolver + function col_projection(alpha, compressor, log, error, sub_solver) + if typeof(compressor.cardinality) != Right + @warn "Compressor has cardinality `Left` but col_projection\ + compresses from the `Right`." + end + + new(alpha, compressor, log, error, sub_solver) + end + end -#------------------------------------------------------------------ function col_projection(; alpha::Float64 = 1.0, - S::Compressor = SparseSign(), + S::Compressor = SparseSign(cardinality=Right()), log::Logger = BasicLogger(), error::SolverError = FullResidual(), sub_solver::SubSolver = QRSolver(), ) - -# Intialize the datatype setting unkown elements to empty versions of correct datatype -return col_projection(alpha, S, log, error, sub_solver) + return col_projection( + alpha, + S, + log, + error, + sub_solver + ) end #------------------------------------------------------------------ -mutable struct col_projectionRecipe{T<:Number, +""" + col_projectionRecipe{ + T<:Number, + V<:AbstractVector, + M<:AbstractMatrix, + MV<:SubArray, + C<:CompressorRecipe, + L<:LoggerRecipe, + E<:SolverErrorRecipe, + B<:SubSolverRecipe + } <: SolverRecipe +A mutable structure containing all information relevant to the col_projection solver. It + is formed by calling the function `complete_solver` on `col_projection` solver, which + includes all the user controlled parameters, and the linear system matrix `A` and constant + vector `b`. + +# Fields + +""" +mutable struct col_projectionRecipe{ + T<:Number, V<:AbstractVector, M<:AbstractMatrix, MV<:SubArray, @@ -46,17 +78,16 @@ mutable struct col_projectionRecipe{T<:Number, E<:SolverErrorRecipe, B<:SubSolverRecipe } <: SolverRecipe -S::C -log::L -error::E -sub_solver::B -alpha::Float64 -compressed_mat::M -compressed_vec::V -solution_vec::V -update_vec::V -mat_view::MV -residual_vec::V + S::C + log::L + error::E + sub_solver::B + alpha::Float64 + compressed_mat::M + solution_vec::V + update_vec::V + mat_view::MV + residual_vec::V end #------------------------------------------------------------------ @@ -66,53 +97,85 @@ function complete_solver( A::AbstractMatrix, b::AbstractVector ) -# Check the dimensions align - - -# Dimension checking will be performed in the complete_compressor -compressor = complete_compressor(solver.S, A, b) -logger = complete_logger(solver.log, A, b) -error = complete_error(solver.error, A, b) -# Check that required fields are in the types -@assert isdefined(error, :residual) "ErrorRecipe $(typeof(error)) does not contain the field 'residual' and is not valid for a col_projection solver." -@assert isdefined(logger, :converged) "LoggerRecipe $(typeof(logger)) does not contain the field 'converged' and is not valid for a col_projection solver." -# Assuming that max_it is defined in the logger -alpha::Float64 = solver.alpha -# We assume the user is using compressors to only decrease dimension -n_rows::Int64 = compressor.n_rows -n_cols::Int64 = compressor.n_cols -initial_size = n_rows -sample_size = n_cols -rows_a, cols_a = size(A) -# Allocate the information in the buffer using the types of A and b -compressed_mat = typeof(A)(undef, rows_a, sample_size) -compressed_vec = typeof(b)(undef, rows_a) -# Since sub_solver is applied to compressed matrices use here -sub_solver = complete_sub_solver(solver.sub_solver, compressed_mat, compressed_vec) -mat_view = view(compressed_mat, :, 1:sample_size) ###################### -solution_vec = x -update_vec = typeof(x)(undef, n_cols) -residual_vec = typeof(x)(undef, rows_a) -return col_projectionRecipe{eltype(A), - typeof(b), - typeof(A), - typeof(mat_view), - typeof(compressor), - typeof(logger), - typeof(error), - typeof(sub_solver) - }(compressor, - logger, - error, - sub_solver, - alpha, - compressed_mat, - compressed_vec, - solution_vec, - update_vec, - mat_view, - residual_vec - ) + # Check the dimensions align + + # Dimension checking will be performed in the complete_compressor + compressor = complete_compressor(solver.S, A, b) + logger = complete_logger(solver.log, A, b) + error = complete_error(solver.error, A, b) + # Check that required fields are in the types + if !isdefined(error, :residual) + throw( + ArgumentError( + "ErrorRecipe $(typeof(error)) does not contain the \ + field 'residual' and is not valid for a col_projection solver." + ) + ) + end + + if !isdefined(logger, :converged) + throw( + ArgumentError( + "LoggerRecipe $(typeof(logger)) does not contain \ + the field 'converged' and is not valid for a col_projection solver." + ) + ) + end + # Assuming that max_it is defined in the logger + alpha::Float64 = solver.alpha + # We assume the user is using compressors to only decrease dimension + n_rows::Int64 = compressor.n_rows + n_cols::Int64 = compressor.n_cols + initial_size = n_rows + sample_size = n_cols + rows_a, cols_a = size(A) + # Allocate the information in the buffer using the types of A and b + compressed_mat = typeof(A)(undef, rows_a, sample_size) + residual_vec = typeof(b)(undef, rows_a) + # Since sub_solver is applied to compressed matrices use here + sub_solver = complete_sub_solver(solver.sub_solver, compressed_mat, residual_vec) + mat_view = view(compressed_mat, :, 1:sample_size) + solution_vec = x + update_vec = typeof(x)(undef, n_cols) + return col_projectionRecipe{eltype(A), + typeof(b), + typeof(A), + typeof(mat_view), + typeof(compressor), + typeof(logger), + typeof(error), + typeof(sub_solver) + }(compressor, + logger, + error, + sub_solver, + alpha, + compressed_mat, + solution_vec, + update_vec, + mat_view, + residual_vec + ) +end + +function col_proj_update!(solver::ColProjRecipe) + # one-dimensional subarray + scaling = solver.alpha * dot(solver.mat_view, solver.residual_vec) + scaling /= dot(solver.mat_view, solver.mat_view) + # x_new = x_old - alpha * S * update_vec + mul!(solver.solution_vec, solver.compressor, scaling, -1.0, 1.0) + # recompute the residual + mul!(solver.residual_vec, solver.mat_view, scaling, -1.0, 1.0) +end + +function col_proj_update_block!(solver::ColProjRecipe) + # update the subsolver and solve for update vector + update_sub_solver!(solver.sub_solver, solver.mat_view) + ldiv!(solver.update_vec, solver.sub_solver, solver.residual_vec) + # x_new = x_old - alpha * S * update_vec + mul!(solver.solution_vec, solver.compressor, solver.update_vec, -solver.alpha, 1.0) + # recomputet the Residual + mul!(solver.residual_vec, solver.mat_view, solver.update_vec, -solver.alpha, 1.0) end #--------------------------------------------------------------- @@ -122,62 +185,58 @@ function rsolve!( A::AbstractMatrix, b::AbstractVector ) -# initialization -solver.solution_vec = x -err = 0.0 -# compute the residual b-Ax -mul!(solver.residual_vec, A, x, -1, 0) -solver.residual_vec .+= b -for i in 1:solver.log.max_it - # generate a new version of the compression matrix - update_compressor!(solver.S, A, b, x) - # based on size of new compressor update views of matrix - # this should not result in new allocations - rows_s, cols_s = size(solver.S) - solver.mat_view = view(solver.compressed_mat, :, 1:cols_s) ################## A_new is mxk - # compress the matrix and constant vector - mul!(solver.mat_view, A, solver.S) ################## A_new=AS - err = compute_error(solver.error, solver, A, b) - # Update log adds value of err to log and checks stopping - update_logger!(solver.log, err, i) - if solver.log.converged - return solver.solution_vec, solver.log[solver.log .> 0] - end + # initialization + reset_logger!(solver.log) + solver.solution_vec = x + # compute the residual b-Ax + mul!(solver.residual_vec, A, solver.solution_vec) + solver.residual_vec .-= b - # sub-solver needs to designed for new compressed matrix - update_sub_solver!(solver.sub_solver, solver.mat_view) - # use sub-solver to find update the solution - sub_solve!(solver.update_vec, solver.sub_solver, solver.residual_vec) ######### - # Using over-relaxation parameter, alpha, to update solution - # x += alpha * S * update_vec - mul!(solver.solution_vec, solver.S, solver.update_vec, solver.alpha, 1.0) - # Compute the residual r-ASx - mul!(solver.residual_vec, solver.mat_view, solver.solution_vec, -1.0, 1.0) ### nothing changed -end + for i in 1:solver.log.max_it + err = compute_error(solver.error, solver, A, b) + # Update log adds value of err to log and checks stopping + update_logger!(solver.log, err, i) + if solver.log.converged + return solver.solution_vec, solver.log[solver.log .> 0] + end -return solver.solution_vec, solver.log + # generate a new version of the compression matrix + update_compressor!(solver.S, A, b, x) + # based on size of new compressor update views of matrix + # this should not result in new allocations + rows_s, cols_s = size(solver.S) + solver.mat_view = view(solver.compressed_mat, :, 1:cols_s) + # compress the matrix + mul!(solver.mat_view, A, solver.S) + + # Solve the undetermined sketched linear system and update the solution + if size(solver.mat_view, 2) == 1 + col_proj_update!(solver) + else + col_proj_update_block!(solver) + end + end + + return solver.solution_vec, solver.log end #---------------------------------------------------------------------- """ - FullResidual <: SolverErrorRecipe + LSgradient <: SolverErrorRecipe -A structure for the full residual, `b-Ax`. +A structure for the full gradient of ||b-Ax||_2^2, `A'(b-Ax)`. # Fields -- `residual::AbstractVector`, a container for the residual `b-Ax`. +- `gradient::AbstractVector`, a container for the full gradient. """ - -struct LSgradient <: SolverError - -end +struct LSgradient <: SolverError end mutable struct LSgradientRecipe{V<:AbstractVector} <: SolverErrorRecipe gradient::V end function complete_error(error::LSgradient, A::AbstractMatrix, b::AbstractVector) - return LSgradientRecipe{typeof(b)}(zeros(size(b,1))) + return LSgradientRecipe{typeof(b)}(zeros(size(A,2))) end function compute_error( @@ -186,46 +245,46 @@ function compute_error( A::AbstractMatrix, b::AbstractVector )::Float64 - copyto!(error.gradient, b) - mul!(error.gradient, A', solver.residual_vec, -1.0, 1.0) ####r=b-A(Sx) + # copyto!(error.gradient, b) + # coompute A'r + mul!(error.gradient, A', solver.residual_vec) return dot(error.gradient, error.gradient) end -""" - CompressedLSgradientRecipe <: SolverErrorRecipe +# """ +# CompressedLSgradientRecipe <: SolverErrorRecipe +# A structure for the compressed residual, ``. -A structure for the compressed residual, `b-ASx`. - -# Fields -- `gradient::AbstractVector`, a container for the compressed residual, `b-ASx`. -- `gradient_view::SubArray`, a view of the residual container to handle varying compression -sizes. -""" +# # Fields +# - `gradient::AbstractVector`, a container for the compressed residual, `S'A'r`. +# - `gradient_view::SubArray`, a view of the residual container to handle varying compression +# sizes. +# """ -struct compressedLSgradient <: SolverError +# struct compressedLSgradient <: SolverError -end +# end -mutable struct compressedLSgradientRecipe{V<:AbstractVector, S<:SubArray} <: SolverErrorRecipe - gradient::V - gradient_view::S -end +# mutable struct compressedLSgradientRecipe{V<:AbstractVector, S<:SubArray} <: SolverErrorRecipe +# gradient::V +# gradient_view::S +# end -function complete_error(error::compressedLSgradient, A::AbstractMatrix, b::AbstractVector) - gradient = zeros(size(b,1)) - gradient_view = view(gradient, 1:1) - return CompressedLSgradientRecipe{typeof(gradient),typeof(gradient_view)}(gradient, gradient_view) -end +# function complete_error(error::compressedLSgradient, A::AbstractMatrix, b::AbstractVector) +# gradient = zeros(size(b,1)) +# gradient_view = view(gradient, 1:1) +# return CompressedLSgradientRecipe{typeof(gradient),typeof(gradient_view)}(gradient, gradient_view) +# end -function compute_error( - error::compressedLSgradientRecipe, - solver::col_projectionRecipe, - A::AbstractMatrix, - b::AbstractVector -)::Float64 - rows_s = size(solver.S, 1) - error.gradient_view = view(error.gradient, 1:rows_s) - mul!(error.gradient_view, solver.mat_view', solver.residual_vec, -1.0, 1.0) - return dot(error.gradient_view, error.gradient_view) -end +# function compute_error( +# error::compressedLSgradientRecipe, +# solver::col_projectionRecipe, +# A::AbstractMatrix, +# b::AbstractVector +# )::Float64 +# row_s = size(solver.S, 1) +# error.gradient_view = view(error.gradient, 1:row_s) +# mul!(error.gradient_view, solver.mat_view', solver.residual_vec, -1.0, 1.0) # r = +# return dot(error.gradient_view, error.gradient_view) +# end diff --git a/test/Solvers/col_projection.jl b/test/Solvers/col_projection.jl new file mode 100644 index 00000000..de876881 --- /dev/null +++ b/test/Solvers/col_projection.jl @@ -0,0 +1,267 @@ +module col_projectionTest +using Test, RLinearAlgebra, LinearAlgebra +import RLinearAlgebra: complete_compressor, update_compressor! +import LinearAlgebra: mul!, norm +import Random: randn! +using ..FieldTest +using ..ApproxTol + +# Define the test structures +########################## +# Compressors +########################## +mutable struct ColTestCompressor <: Compressor + cardinality::Cardinality + compression_dim::Int64 +end + +ColTestCompressor() = ColTestCompressor(Right(), 5) + +mutable struct ColTestCompressorRecipe <: CompressorRecipe + cardinality::Cardinality + n_rows::Int64 + n_cols::Int64 + op::AbstractMatrix +end + +function RLinearAlgebra.complete_compressor( + comp::ColTestCompressor, + A::AbstractMatrix, + b::AbstractVector +) + n_rows = size(A, 2) + n_cols = comp.compression_dim + # Make a gaussian compressor + op = randn(n_rows, n_cols) ./ sqrt(n_rows) + return ColCompressorRecipe(comp.cardinality, n_rows, n_cols, op) +end + +function RLinearAlgebra.update_compressor!( + comp::ColCompressorRecipe, + x::AbstractVector, + A::AbstractMatrix, + b::AbstractVector +) + randn!(comp.op) + comp.op ./= sqrt(comp.n_rows) +end + +# Define a mul function for the test compressor +function RLinearAlgebra.mul!( + C::AbstractArray, + A::AbstractArray, + S::Main.col_projectionTest.ColCompressorRecipe, + alpha::Number, + beta::Number +) + mul!(C, A, S.op, alpha, beta) +end + +########################## +# Error Method +########################## +mutable struct ColTestError <: RLinearAlgebra.SolverError + g::Real +end + +mutable struct ColTestErrorRecipe <: RLinearAlgebra.SolverErrorRecipe + residual::Vector{Number} +end + +ColTestError() = ColTestError(1.0) + +function RLinearAlgebra.complete_error( + error::ColTestError, + solver::col_projection, + A::AbstractMatrix, + b::AbstractVector +) + return ColTestErrorRecipe(zeros(typeof(error.g), size(A, 1))) +end + +function RLinearAlgebra.compute_error(error::ColTestErrorRecipe, solver, A, b) + error.residual = A * solver.solution_vec - b + return norm(error.residual) +end + +############################## +# Residual-less Error Recipe +############################## +# mutable struct KTestErrorNoRes <: RLinearAlgebra.SolverError end + +# mutable struct KTestErrorRecipeNoRes <: RLinearAlgebra.SolverErrorRecipe end + +# function RLinearAlgebra.complete_error( +# error::KTestErrorNoRes, +# solver::Kaczmarz, +# A::AbstractMatrix, +# b::AbstractVector +# ) +# return KTestErrorRecipeNoRes() +# end + +############################ +# Loggers +############################ +mutable struct ColTestLog <: Logger + max_it::Int64 + g::Number +end + +ColTestLog() = ColTestLog(5, 1.0) + +ColTestLog(max_it) = ColTestLog(max_it, 1.0) + +mutable struct ColTestLogRecipe <: LoggerRecipe + max_it::Int64 + hist::Vector{Real} + thresh::Float64 + converged::Bool +end + +function RLinearAlgebra.complete_logger(logger::ColTestLog) + return ColTestLogRecipe( + logger.max_it, + zeros(typeof(logger.g), logger.max_it), + logger.g, + false + ) +end + +function RLinearAlgebra.update_logger!(logger::ColTestLogRecipe, err::Real, i::Int64) + logger.hist[i] = err + logger.converged = err < logger.thresh ? true : false +end + +function RLinearAlgebra.reset_logger!(logger::ColTestLogRecipe) + fill!(logger.hist, 0.0) +end + +############################## +# Converged-less Logger +############################## +mutable struct ColTestLogNoCov <: Logger end + +mutable struct ColTestLogRecipeNoCov <: LoggerRecipe end + +function RLinearAlgebra.complete_logger(logger::ColTestLogNoCov) + return ColTestLogRecipeNoCov() +end + +############################## +# SubSolver +############################## +mutable struct ColTestSubSolver <: SubSolver end + +mutable struct ColTestSubSolverRecipe <: SubSolverRecipe + A::AbstractMatrix +end + +function RLinearAlgebra.complete_sub_solver( + solver::ColTestSubSolver, + A::AbstractMatrix, + b::AbstractVector +) + return ColTestSubSolverRecipe(A) +end + +function RLinearAlgebra.update_sub_solver!(solver::ColTestSubSolverRecipe, A::AbstractMatrix) + solver.A = A +end + +function RLinearAlgebra.ldiv!( + x::AbstractVector, + S::Main.col_projectionTest.ColTestSubSolverRecipe, + b::AbstractVector, +) + ldiv!(x, factorize(S.A), b) +end + +##################################### +# Testing the functions +##################################### +@testset "col_projection" begin + n_rows = 3 + n_cols = 10 + A = rand(n_rows, n_cols) + xsol = rand(n_cols) + b = A * xsol + + @testset "col_projection Technique" begin + @test supertype(col_projection) == Solver + + # test fieldnames and types + @test fieldnames(col_projection) == (:alpha, :compressor, :log, :error, :sub_solver) + @test fieldtypes(col_projection) == (Float64, Compressor, Logger, SolverError, SubSolver) + + # test default constructor + + let solver = col_projection() + @test solver.alpha == 1.0 + @test typeof(solver.compressor) == SparseSign + @test typeof(solver.compressor.cardinality) == Right + @test typeof(solver.log) == BasicLogger + @test typeof(solver.error) == FullResidual + @test typeof(solver.sub_solver) == QRSolver + end + + # test constructor + let solver = col_projection( + alpha = 2.0, + compressor = ColTestCompressor(), + log = ColTestLog(), + error = ColTestError(), + sub_solver = ColTestSubSolver() + ) + + @test solver.alpha == 2.0 + @test typeof(solver.compressor) == ColTestCompressor + @test typeof(solver.log) == ColTestLog + @test typeof(solver.error) == ColTestError + @test typeof(solver.sub_solver) == ColTestSubSolver + end + + # Test that error gets returned with left compressor + @test_logs (:warn, + "Compressor has cardinality `Left` but col_projection\ + compresses from the `Right`." + ) col_projection( + alpha = 2.0, + compressor = ColTestCompressor(Left(), 5), + log = ColTestLog(), + error = ColTestError(), + sub_solver = ColTestSubSolver() + ) + end + + @testset "col_projectionRecipe" begin + @test supertype(col_projectionRecipe) == SolverRecipe + + # test fieldnames and types + @test fieldnames(col_projectionRecipe) == ( + :compressor, + :log, + :error, + :sub_solver, + :alpha, + :compressed_mat, + :solution_vec, + :update_vec, + :mat_view, + :residual_vec + ) + @test fieldtypes(col_projectionRecipe) == ( + CompressorRecipe, + LoggerRecipe, + SolverErrorRecipe, + SubSolverRecipe, + Float64, + AbstractArray, + AbstractVector, + AbstractVector, + SubArray, + AbstractVector, + ) + end + +end \ No newline at end of file From 23b017aaa77967a7d16eccd94f82d7e16afa385a Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 23 Jul 2025 04:52:43 -0500 Subject: [PATCH 3/9] Updated test file --- src/RLinearAlgebra.jl | 1 + src/Solvers.jl | 1 + src/Solvers/col_projection.jl | 27 ++-- test/Solvers/col_projection.jl | 234 +++++++++++++++++++++++++++++++-- 4 files changed, 235 insertions(+), 28 deletions(-) diff --git a/src/RLinearAlgebra.jl b/src/RLinearAlgebra.jl index cfd4324f..43bb138b 100644 --- a/src/RLinearAlgebra.jl +++ b/src/RLinearAlgebra.jl @@ -33,6 +33,7 @@ export Uniform, UniformRecipe # Export Solver types and functions export Solver, SolverRecipe export complete_solver, update_solver!, rsolve! +export col_projection, col_projectionRecipe # Export Logger types and functions export Logger, LoggerRecipe diff --git a/src/Solvers.jl b/src/Solvers.jl index ffe0aaa1..08c82d95 100644 --- a/src/Solvers.jl +++ b/src/Solvers.jl @@ -150,3 +150,4 @@ include("Solvers/ErrorMethods.jl") ############################# # The Solver Routine Files ############################ +include("Solvers/col_projection.jl") \ No newline at end of file diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl index 2ddb4328..28be83f9 100644 --- a/src/Solvers/col_projection.jl +++ b/src/Solvers/col_projection.jl @@ -17,7 +17,7 @@ affect convergence. #------------------------------------------------------------------ mutable struct col_projection <: Solver alpha::Float64 - S::Compressor + compressor::Compressor log::Logger error::SolverError sub_solver::SubSolver @@ -34,14 +34,14 @@ end function col_projection(; alpha::Float64 = 1.0, - S::Compressor = SparseSign(cardinality=Right()), + compressor::Compressor = SparseSign(cardinality=Right()), log::Logger = BasicLogger(), error::SolverError = FullResidual(), sub_solver::SubSolver = QRSolver(), ) return col_projection( alpha, - S, + compressor, log, error, sub_solver @@ -53,7 +53,7 @@ end col_projectionRecipe{ T<:Number, V<:AbstractVector, - M<:AbstractMatrix, + M<:AbstractArray, MV<:SubArray, C<:CompressorRecipe, L<:LoggerRecipe, @@ -71,7 +71,7 @@ A mutable structure containing all information relevant to the col_projection so mutable struct col_projectionRecipe{ T<:Number, V<:AbstractVector, - M<:AbstractMatrix, + M<:AbstractArray, MV<:SubArray, C<:CompressorRecipe, L<:LoggerRecipe, @@ -97,12 +97,11 @@ function complete_solver( A::AbstractMatrix, b::AbstractVector ) - # Check the dimensions align # Dimension checking will be performed in the complete_compressor - compressor = complete_compressor(solver.S, A, b) - logger = complete_logger(solver.log, A, b) - error = complete_error(solver.error, A, b) + compressor = complete_compressor(solver.compressor, A, b) + logger = complete_logger(solver.log) + error = complete_error(solver.error, solver, A, b) # Check that required fields are in the types if !isdefined(error, :residual) throw( @@ -158,23 +157,23 @@ function complete_solver( ) end -function col_proj_update!(solver::ColProjRecipe) +function col_proj_update!(solver::col_projectionRecipe) # one-dimensional subarray scaling = solver.alpha * dot(solver.mat_view, solver.residual_vec) scaling /= dot(solver.mat_view, solver.mat_view) # x_new = x_old - alpha * S * update_vec - mul!(solver.solution_vec, solver.compressor, scaling, -1.0, 1.0) + mul!(solver.solution_vec, solver.S, scaling, -1.0, 1.0) # recompute the residual mul!(solver.residual_vec, solver.mat_view, scaling, -1.0, 1.0) end -function col_proj_update_block!(solver::ColProjRecipe) +function col_proj_update_block!(solver::col_projectionRecipe) # update the subsolver and solve for update vector update_sub_solver!(solver.sub_solver, solver.mat_view) ldiv!(solver.update_vec, solver.sub_solver, solver.residual_vec) # x_new = x_old - alpha * S * update_vec - mul!(solver.solution_vec, solver.compressor, solver.update_vec, -solver.alpha, 1.0) - # recomputet the Residual + mul!(solver.solution_vec, solver.S, solver.update_vec, -solver.alpha, 1.0) + # recomputet the residual mul!(solver.residual_vec, solver.mat_view, solver.update_vec, -solver.alpha, 1.0) end diff --git a/test/Solvers/col_projection.jl b/test/Solvers/col_projection.jl index de876881..2132af2a 100644 --- a/test/Solvers/col_projection.jl +++ b/test/Solvers/col_projection.jl @@ -33,11 +33,11 @@ function RLinearAlgebra.complete_compressor( n_cols = comp.compression_dim # Make a gaussian compressor op = randn(n_rows, n_cols) ./ sqrt(n_rows) - return ColCompressorRecipe(comp.cardinality, n_rows, n_cols, op) + return ColTestCompressorRecipe(comp.cardinality, n_rows, n_cols, op) end function RLinearAlgebra.update_compressor!( - comp::ColCompressorRecipe, + comp::ColTestCompressorRecipe, x::AbstractVector, A::AbstractMatrix, b::AbstractVector @@ -50,7 +50,7 @@ end function RLinearAlgebra.mul!( C::AbstractArray, A::AbstractArray, - S::Main.col_projectionTest.ColCompressorRecipe, + S::Main.col_projectionTest.ColTestCompressorRecipe, alpha::Number, beta::Number ) @@ -87,18 +87,18 @@ end ############################## # Residual-less Error Recipe ############################## -# mutable struct KTestErrorNoRes <: RLinearAlgebra.SolverError end +mutable struct ColTestErrorNoRes <: RLinearAlgebra.SolverError end -# mutable struct KTestErrorRecipeNoRes <: RLinearAlgebra.SolverErrorRecipe end +mutable struct ColTestErrorRecipeNoRes <: RLinearAlgebra.SolverErrorRecipe end -# function RLinearAlgebra.complete_error( -# error::KTestErrorNoRes, -# solver::Kaczmarz, -# A::AbstractMatrix, -# b::AbstractVector -# ) -# return KTestErrorRecipeNoRes() -# end +function RLinearAlgebra.complete_error( + error::ColTestErrorNoRes, + solver::col_projection, + A::AbstractMatrix, + b::AbstractVector +) + return ColTestErrorRecipeNoRes() +end ############################ # Loggers @@ -239,7 +239,7 @@ end # test fieldnames and types @test fieldnames(col_projectionRecipe) == ( - :compressor, + :S, :log, :error, :sub_solver, @@ -264,4 +264,210 @@ end ) end + @testset "col_projection: Complete Solver" begin + # test error method with no residual error + let A = A, + xsol = xsol, + b = b, + comp_dim = 2, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(n_cols) + + comp = ColTestCompressor(Right(), comp_dim) + log = ColTestLog() + err = ColTestErrorNoRes() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + @test_throws ArgumentError( + "ErrorRecipe $(typeof(ColTestErrorRecipeNoRes())) does not contain the \ + field 'residual' and is not valid for a col_projection solver." + ) complete_solver(solver, x, A, b) + end + + # test logger method with no converged field + let A = A, + xsol = xsol, + b = b, + comp_dim = 2, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(n_cols) + + comp = ColTestCompressor(Right(), comp_dim) + log = ColTestLogNoCov() + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + @test_throws ArgumentError( + "LoggerRecipe $(typeof(ColTestLogRecipeNoCov())) does not contain \ + the field 'converged' and is not valid for a col_projection solver." + ) complete_solver(solver, x, A, b) + end + + let A = A, + xsol = xsol, + b = b, + comp_dim = 2, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(n_cols) + + comp = ColTestCompressor(Right(), comp_dim) + log = ColTestLog() + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + solver_rec = complete_solver(solver, x, A, b) + + # test types of the contents of the solver + @test typeof(solver_rec) == col_projectionRecipe{ + Float64, + Vector{Float64}, + Matrix{Float64}, + SubArray{Float64, 2, Matrix{Float64}, Tuple{Base.Slice{Base.OneTo{Int64}}, UnitRange{Int64}}, true}, + Main.col_projectionTest.ColTestCompressorRecipe, + Main.col_projectionTest.ColTestLogRecipe, + Main.col_projectionTest.ColTestErrorRecipe, + Main.col_projectionTest.ColTestSubSolverRecipe + } + @test typeof(solver_rec.S) == ColTestCompressorRecipe + @test typeof(solver_rec.log) == ColTestLogRecipe + @test typeof(solver_rec.error) == ColTestErrorRecipe + @test typeof(solver_rec.sub_solver) == ColTestSubSolverRecipe + @test typeof(solver_rec.alpha) == Float64 + @test typeof(solver_rec.compressed_mat) == Matrix{Float64} + @test typeof(solver_rec.solution_vec) == Vector{Float64} + @test typeof(solver_rec.update_vec) == Vector{Float64} + @test typeof(solver_rec.mat_view) <: SubArray + @test typeof(solver_rec.residual_vec) == Vector{Float64} + + # Test sizes of vectors and matrices + @test size(solver_rec.S) == (n_cols, comp_dim) + @test size(solver_rec.compressed_mat) == (n_rows, comp_dim) + @test size(solver_rec.update_vec) == (comp_dim,) + + # test values of entries + solver_rec.alpha == alpha + solver_rec.solution_vec == x + solver_rec.update_vec == zeros(n_cols) + end + + end + # @testset "col_projection: Column Projection Update" begin + # # Begin with a test of an update when the block size is 1 + # for type in [Float32, Float64, ComplexF32, ComplexF64] + # let A = rand(type, n_rows, n_cols), + # xsol = ones(type, n_cols), + # b = A * xsol, + # comp_dim = 1, + # alpha = 1.0, + # n_rows = size(A, 1), + # n_cols = size(A, 2), + # x = zeros(type, n_cols) + + # comp = ColTestCompressor(Right(), comp_dim) + # log = ColTestLog() + # err = ColTestError() + # sub_solver = ColTestSubSolver() + # solver = col_projection( + # compressor = comp, + # log = log, + # error = err, + # sub_solver = sub_solver, + # alpha = alpha + # ) + + # solver_rec = complete_solver(solver, x, A, b) + + # # Sketch the matrix and vector + # As = A * solver_rec.compressor + # solver_rec.mat_view = view(As, :, 1:comp_dim) + # solver_rec.solution_vec = deepcopy(x) + # solver_rec.residual_vec = b - As + # solver.solution_vec = x + + # # compute comparison update + # sc = dot(solver.mat_view, residual_vec) / dot(As, As) * alpha + # test_sol = x - solver.S * sc + + # # compute the update + # RLinearAlgebra.col_projection_update!(solver_rec) + # @test solver_rec.solution_vec ≈ test_sol + # end + + # end + + # end + + # @testset "col_projection: Block Column Projection Update" begin + # # Begin with a test of an update when the block size is 2 + # for type in [Float32, Float64, ComplexF32, ComplexF64] + # let A = rand(type, n_rows, n_cols), + # xsol = ones(type, n_cols), + # b = A * xsol, + # comp_dim = 1, + # alpha = 1.0, + # n_rows = size(A, 1), + # n_cols = size(A, 2), + # x = zeros(type, n_cols) + + # comp = ColTestCompressor(Left(), comp_dim) + # log = ColTestLog() + # err = ColTestError() + # sub_solver = ColTestSubSolver() + # solver = col_projection( + # compressor = comp, + # log = log, + # error = err, + # sub_solver = sub_solver, + # alpha = alpha + # ) + + # solver_rec = complete_solver(solver, x, A, b) + + # # Sketch the matrix and vector + # sA = solver_rec.compressor * A + # solver_rec.vec_view = view(sb, 1:comp_dim) + # solver_rec.mat_view = view(sA, 1:comp_dim, :) + # solver_rec.solution_vec = deepcopy(x) + + # # compute comparison update + # test_sol = x + As \ (sb - sA * x) + + # # compute the update + # RLinearAlgebra.col_update_block!(solver_rec) + # @test solver_rec.solution_vec ≈ test_sol + # end + + # end + + #end + +end + end \ No newline at end of file From 1309441a772b03123c77554b1ba0ca0f95ab0c83 Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 20 Aug 2025 01:13:18 -0500 Subject: [PATCH 4/9] Added test for rsolve! --- docs/src/api/solvers.md | 9 + src/Solvers/SubSolvers.jl | 18 ++ src/Solvers/col_projection.jl | 165 ++++++++++++----- test/Solvers/col_projection.jl | 325 ++++++++++++++++++++++++--------- 4 files changed, 381 insertions(+), 136 deletions(-) diff --git a/docs/src/api/solvers.md b/docs/src/api/solvers.md index 05f9c1a1..7cef2b76 100644 --- a/docs/src/api/solvers.md +++ b/docs/src/api/solvers.md @@ -12,7 +12,9 @@ SolverRecipe ## Solver Structures ```@docs +col_projection +col_projectionRecipe ``` ## Exported Functions @@ -21,3 +23,10 @@ complete_solver rsolve! ``` + +## Internal Functions +```@docs +RLinearAlgebra.col_proj_update! + +RLinearAlgebra.col_proj_update_block! +``` \ No newline at end of file diff --git a/src/Solvers/SubSolvers.jl b/src/Solvers/SubSolvers.jl index b0d5de0f..cd70221a 100644 --- a/src/Solvers/SubSolvers.jl +++ b/src/Solvers/SubSolvers.jl @@ -20,6 +20,7 @@ sub_solver_arg_list = Dict{Symbol,String}( :sub_solver_recipe => "`solver::SubSolverRecipe`, a fully initialized realization for a linear sub-solver.", :A => "`A::AbstractArray`, a coefficient matrix or vector.", + :b => "`b::AbstractArray`, a constant matrix or vector.", ) sub_solver_output_list = Dict{Symbol,String}( @@ -53,6 +54,23 @@ function complete_sub_solver(solver::SubSolver, A::AbstractArray) ) end +""" + complete_sub_solver(solver::SubSolver, A::AbstractArray, b::AbstractArray) + +$(sub_solver_method_description[:complete_sub_solver]) + +# Arguments +- $(sub_solver_arg_list[:sub_solver]) +- $(sub_solver_arg_list[:A]) +- $(sub_solver_arg_list[:b]) + +# Returns +- $(sub_solver_output_list[:sub_solver_recipe]) +""" +function complete_sub_solver(solver::SubSolver, A::AbstractArray, b::AbstractArray) + complete_sub_solver(solver, A) +end + """ update_sub_solver!(solver::SubSolverRecipe, A::AbstractArray) diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl index 28be83f9..0ede93ca 100644 --- a/src/Solvers/col_projection.jl +++ b/src/Solvers/col_projection.jl @@ -5,14 +5,66 @@ An implementation of a column projection solver. Specifically, it is a solver th updates a solution by projection the solution onto a compressed rowspace of the linear system. +# Mathmatical Description + +Let `A` be an `m \\times n` matrix and consider the consistent linear system `Ax = b`. +When `n > m`, this system has infinitely many solutions, forming an affine subspace: + +``x \\in {x \\in \\mathbb{R}^n : Ax = b}``. + +Column projection methods iteratively refine the estimate `x` by solving compressed normal +equations via a sketching matrix `S`. Letting ``\\tilde{A} = A S``, and the initial residual +being ``Ax - b`. + +Then the update is: + +``\\Delta x = (\\tilde{A}^\\top \\tilde{A})^{-1} \\tilde{A}^\\top r`` + +``x_{+} = x - \\alpha S \\Delta x`` + +In the scalar sketch case (i.e., one column), the update simplifies to: + +``x_{+} = x - \\alpha S \\cdot \\frac{\\langle A S, r \\rangle}{\\| A S \\|^2}`` + +where `\\alpha` is an over-relaxation parameter. The sketching matrix `S` can be random (e.g. +SparseSign, Sampling) or deterministic. + +The residual is updated by: + +``r_{+} = r - \\tilde{A} \\Delta x`` + # Fields -- `S::Compressor`, a technique for forming the compressed rowspace of the linear system. +- `alpha::Float64`, the over-relaxation parameter. It is multiplied by the update and can +affect convergence. +- `compressor::Compressor`, a technique for forming the compressed column space of the linear system. - `log::Logger`, a technique for logging the progress of the solver. - `error::SolverError`, a method for estimating the progress of the solver. - `sub_solver::SubSolver`, a technique to perform the projection of the solution onto the -compressed column space. + compressed column space. + +# Constructor + col_projection(; + alpha::Float64 = 1.0, + compressor::Compressor = SparseSign(cardinality=Right()), + log::Logger = BasicLogger(), + error::SolverError = FullResidual(), + sub_solver::SubSolver = QRSolver(), + ) +## Keywords - `alpha::Float64`, the over-relaxation parameter. It is multiplied by the update and can -affect convergence. + affect convergence. By default this value is 1. +- `compressor::Compressor`, a technique for forming the compressed column space of the + linear system. By default it's SparseSign compressor. +- `log::Logger`, a technique for logging the progress of the solver. By default it's the + basic logger. +- `error::SolverError`, a method for estimating the progress of the solver. By default it's + the FullResidual error. +- `sub_solver::SubSolver`, a technique to perform the projection of the solution onto the + compressed rowspace. When the `compression_dim = 1` this is not used. + +## Returns +- A `col_projection` object. + """ #------------------------------------------------------------------ mutable struct col_projection <: Solver @@ -60,13 +112,30 @@ end E<:SolverErrorRecipe, B<:SubSolverRecipe } <: SolverRecipe + A mutable structure containing all information relevant to the col_projection solver. It is formed by calling the function `complete_solver` on `col_projection` solver, which includes all the user controlled parameters, and the linear system matrix `A` and constant vector `b`. # Fields - +- `S::CompressorRecipe`, a technique for forming the compressed column space of the + linear system. +- `log::LoggerRecipe`, a technique for logging the progress of the solver. +- `error::SolverErrorRecipe`, a method for estimating the progress of the solver. +- `sub_solver::SubSolverRecipe`, a technique to perform the projection of the solution + onto the compressed column space. +- `alpha::Float64`, the over-relaxation parameter. It is multiplied by the update and can + affect convergence. +- `compressed_mat::AbstractMatrix`, a matrix container for storing the compressed matrix. + Will be set to be the largest possible block size. +- `solution_vec::AbstractVector`, a vector container for storing the current solution + to the linear system. +- `update_vec::AbstractVector`, a vector container for storing the update to the solution. +- `mat_view::SubArray`, a container for storing a view of the compressed matrix container. + Using views here allows for variable block sizes. +- `residual_vec::AbstractVector`, a vector container for storing the residual at each + iteration. """ mutable struct col_projectionRecipe{ T<:Number, @@ -157,16 +226,53 @@ function complete_solver( ) end +""" + col_proj_update!(solver::col_projectionRecipe) + +A function that performs the column projection update when the compression dimension + is one. If ``a`` is the resulting compression of the transpose of the coefficient matrix, + and ``r`` is the current residual, then we perform the update: + +``x = x - \\alpha S \\frac{\\langle a, r \\rangle}{\\|a\\|_2^2}``, + + where `S` is the compression operator and `a = A S`. + +# Arguments +- `solver::col_projectionRecipe`, the solver information required for performing the update. + +# Outputs +- returns `nothing` +""" function col_proj_update!(solver::col_projectionRecipe) # one-dimensional subarray scaling = solver.alpha * dot(solver.mat_view, solver.residual_vec) scaling /= dot(solver.mat_view, solver.mat_view) + scaling_vec = fill(scaling, 1) # x_new = x_old - alpha * S * update_vec - mul!(solver.solution_vec, solver.S, scaling, -1.0, 1.0) + mul!(solver.solution_vec, solver.S, scaling_vec, -1.0, 1.0) # recompute the residual - mul!(solver.residual_vec, solver.mat_view, scaling, -1.0, 1.0) + mul!(solver.residual_vec, solver.mat_view, scaling_vec, -1.0, 1.0) + return nothing end +""" + col_proj_update_block!(solver::col_projectionRecipe) + +A function that performs the column projection update when the compression dimension + is greater than 1. In the block case, where the compressed matrix is + ``\\tilde A = A S`` and the residual is ``r = b - A x``, we perform the update: + +``x = x - \\alpha S (\\tilde A^\\top \\tilde A)^\\dagger \\tilde A^\\top r``, + + where `S` is the compression operator and the update projects the solution onto the + column space of the matrix `A`. + +# Arguments +- `solver::col_projectionRecipe`, the solver information required for performing the update. + +# Outputs +- returns `nothing` +""" function col_proj_update_block!(solver::col_projectionRecipe) # update the subsolver and solve for update vector update_sub_solver!(solver.sub_solver, solver.mat_view) @@ -175,9 +281,9 @@ function col_proj_update_block!(solver::col_projectionRecipe) mul!(solver.solution_vec, solver.S, solver.update_vec, -solver.alpha, 1.0) # recomputet the residual mul!(solver.residual_vec, solver.mat_view, solver.update_vec, -solver.alpha, 1.0) + return nothing end -#--------------------------------------------------------------- function rsolve!( solver::col_projectionRecipe, x::AbstractVector, @@ -193,14 +299,15 @@ function rsolve!( for i in 1:solver.log.max_it err = compute_error(solver.error, solver, A, b) + println(err) # Update log adds value of err to log and checks stopping update_logger!(solver.log, err, i) if solver.log.converged - return solver.solution_vec, solver.log[solver.log .> 0] + return nothing end # generate a new version of the compression matrix - update_compressor!(solver.S, A, b, x) + update_compressor!(solver.S, x, A, b) # based on size of new compressor update views of matrix # this should not result in new allocations rows_s, cols_s = size(solver.S) @@ -214,9 +321,10 @@ function rsolve!( else col_proj_update_block!(solver) end + end - return solver.solution_vec, solver.log + return nothing end #---------------------------------------------------------------------- @@ -250,40 +358,3 @@ function compute_error( return dot(error.gradient, error.gradient) end - -# """ -# CompressedLSgradientRecipe <: SolverErrorRecipe -# A structure for the compressed residual, ``. - -# # Fields -# - `gradient::AbstractVector`, a container for the compressed residual, `S'A'r`. -# - `gradient_view::SubArray`, a view of the residual container to handle varying compression -# sizes. -# """ - -# struct compressedLSgradient <: SolverError - -# end - -# mutable struct compressedLSgradientRecipe{V<:AbstractVector, S<:SubArray} <: SolverErrorRecipe -# gradient::V -# gradient_view::S -# end - -# function complete_error(error::compressedLSgradient, A::AbstractMatrix, b::AbstractVector) -# gradient = zeros(size(b,1)) -# gradient_view = view(gradient, 1:1) -# return CompressedLSgradientRecipe{typeof(gradient),typeof(gradient_view)}(gradient, gradient_view) -# end - -# function compute_error( -# error::compressedLSgradientRecipe, -# solver::col_projectionRecipe, -# A::AbstractMatrix, -# b::AbstractVector -# )::Float64 -# row_s = size(solver.S, 1) -# error.gradient_view = view(error.gradient, 1:row_s) -# mul!(error.gradient_view, solver.mat_view', solver.residual_vec, -1.0, 1.0) # r = -# return dot(error.gradient_view, error.gradient_view) -# end diff --git a/test/Solvers/col_projection.jl b/test/Solvers/col_projection.jl index 2132af2a..2457a1c2 100644 --- a/test/Solvers/col_projection.jl +++ b/test/Solvers/col_projection.jl @@ -57,6 +57,17 @@ function RLinearAlgebra.mul!( mul!(C, A, S.op, alpha, beta) end +# Define a left mul function for test compressor +function RLinearAlgebra.mul!( + C::AbstractArray, + S::Main.col_projectionTest.ColTestCompressorRecipe, + A::AbstractArray, + alpha::Number, + beta::Number +) + mul!(C, S.op, A, alpha, beta) +end + ########################## # Error Method ########################## @@ -378,95 +389,231 @@ end end end - # @testset "col_projection: Column Projection Update" begin - # # Begin with a test of an update when the block size is 1 - # for type in [Float32, Float64, ComplexF32, ComplexF64] - # let A = rand(type, n_rows, n_cols), - # xsol = ones(type, n_cols), - # b = A * xsol, - # comp_dim = 1, - # alpha = 1.0, - # n_rows = size(A, 1), - # n_cols = size(A, 2), - # x = zeros(type, n_cols) - - # comp = ColTestCompressor(Right(), comp_dim) - # log = ColTestLog() - # err = ColTestError() - # sub_solver = ColTestSubSolver() - # solver = col_projection( - # compressor = comp, - # log = log, - # error = err, - # sub_solver = sub_solver, - # alpha = alpha - # ) - - # solver_rec = complete_solver(solver, x, A, b) - - # # Sketch the matrix and vector - # As = A * solver_rec.compressor - # solver_rec.mat_view = view(As, :, 1:comp_dim) - # solver_rec.solution_vec = deepcopy(x) - # solver_rec.residual_vec = b - As - # solver.solution_vec = x - - # # compute comparison update - # sc = dot(solver.mat_view, residual_vec) / dot(As, As) * alpha - # test_sol = x - solver.S * sc - - # # compute the update - # RLinearAlgebra.col_projection_update!(solver_rec) - # @test solver_rec.solution_vec ≈ test_sol - # end - - # end - - # end - - # @testset "col_projection: Block Column Projection Update" begin - # # Begin with a test of an update when the block size is 2 - # for type in [Float32, Float64, ComplexF32, ComplexF64] - # let A = rand(type, n_rows, n_cols), - # xsol = ones(type, n_cols), - # b = A * xsol, - # comp_dim = 1, - # alpha = 1.0, - # n_rows = size(A, 1), - # n_cols = size(A, 2), - # x = zeros(type, n_cols) - - # comp = ColTestCompressor(Left(), comp_dim) - # log = ColTestLog() - # err = ColTestError() - # sub_solver = ColTestSubSolver() - # solver = col_projection( - # compressor = comp, - # log = log, - # error = err, - # sub_solver = sub_solver, - # alpha = alpha - # ) - - # solver_rec = complete_solver(solver, x, A, b) - - # # Sketch the matrix and vector - # sA = solver_rec.compressor * A - # solver_rec.vec_view = view(sb, 1:comp_dim) - # solver_rec.mat_view = view(sA, 1:comp_dim, :) - # solver_rec.solution_vec = deepcopy(x) - - # # compute comparison update - # test_sol = x + As \ (sb - sA * x) - - # # compute the update - # RLinearAlgebra.col_update_block!(solver_rec) - # @test solver_rec.solution_vec ≈ test_sol - # end - - # end - - #end + + @testset "col_projection: Column Projection Update" begin + # Begin with a test of an update when the block size is 1 + for type in [Float32, Float64, ComplexF32, ComplexF64] + let A = rand(type, n_rows, n_cols), + xsol = ones(type, n_cols), + b = A * xsol, + comp_dim = 1, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(type, n_cols) + + comp = ColTestCompressor(Right(), comp_dim) + log = ColTestLog() + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + solver_rec = complete_solver(solver, x, A, b) + + # Sketch the matrix and vector + As = A * solver_rec.S + solver_rec.mat_view = view(As, :, 1:comp_dim) + solver_rec.solution_vec = deepcopy(x) + solver_rec.residual_vec .= b .- As + + # compute comparison update + sc = dot(solver_rec.mat_view, solver_rec.residual_vec) / dot(solver_rec.mat_view, solver_rec.mat_view) * alpha + # lift sc to a 1-dimensinoal vec + sc_vec = fill(sc, 1) + #test_sol = x - solver_rec.S * sc + test_sol = deepcopy(x) + RLinearAlgebra.mul!(test_sol, solver_rec.S, sc_vec, -1.0, 1.0) + + # compute the update + RLinearAlgebra.col_proj_update!(solver_rec) + @test solver_rec.solution_vec ≈ test_sol + end + + end + + end + + @testset "col_projection: Block Column Projection Update" begin + # Begin with a test of an update when the block size is 2 + for type in [Float32, Float64, ComplexF32, ComplexF64] + let A = rand(type, n_rows, n_cols), + xsol = ones(type, n_cols), + b = A * xsol, + comp_dim = 2, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(type, n_cols) + + comp = ColTestCompressor(Right(), comp_dim) + log = ColTestLog() + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + solver_rec = complete_solver(solver, x, A, b) + + # Sketch the matrix and vector + As = A * solver_rec.S + solver_rec.mat_view = view(As, :, 1:comp_dim) + solver_rec.solution_vec = deepcopy(x) + solver_rec.residual_vec = b - A * x + + # # compute comparison update + test_sol = deepcopy(x) + #RLinearAlgebra.mul!(test_sol, solver_rec.S, sc_vec, -1.0, 1.0) + + test_update = solver_rec.mat_view \ solver_rec.residual_vec + RLinearAlgebra.mul!(test_sol, solver_rec.S, test_update, -alpha, 1) + + # compute the update + RLinearAlgebra.col_proj_update_block!(solver_rec) + # @test solver_rec.solution ≈ + @test solver_rec.solution_vec ≈ test_sol + end + + end + + end + + #################################### rsolve! to add ComplexF32 and ComplexF64 ################################ + @testset "col_projection: rsolve!" begin + # test when the block size is one maxit stop + for type in [Float16, Float32, Float64] + let A = rand(type, n_rows, n_cols), + xsol = ones(type, n_cols), + b = A * xsol, + comp_dim = 1, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + + x = zeros(type, n_cols) + x_st = deepcopy(x) + comp = ColTestCompressor(Right(), comp_dim) + # check after 10 iterations + log = ColTestLog(10, 0.0) + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + solver_rec = complete_solver(solver, x, A, b) + result = rsolve!(solver_rec, x, A, b) + # test that the residual decrease is acceptable + @test norm(b - A * x) < norm(b - A * x_st) + end + + # test when the block size is greater than 1 maxit stop + let A = rand(type, n_rows, n_cols), + xsol = ones(type, n_cols), + b = A * xsol, + comp_dim = 2, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(type, n_cols) + x_st = deepcopy(x) + + comp = ColTestCompressor(Right(), comp_dim) + #check 10 iterations + log = ColTestLog(10, 0.0) + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + solver_rec = complete_solver(solver, x, A, b) + result = rsolve!(solver_rec, x, A, b) + # test that the residual decrease is acceptable + @test norm(b - A * x) < norm(b - A * x_st) + end + + # test when the block size is one threshold stop + let A = Array(transpose(qr(transpose(rand(type, n_rows, n_cols))).Q)), + xsol = ones(type, n_cols), + b = A * xsol, + comp_dim = 1, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(type, n_cols) + x_st = deepcopy(x) + + comp = ColTestCompressor(Right(), comp_dim) + # check after 50 iterations + log = ColTestLog(50, 0.5) + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + solver_rec = complete_solver(solver, x, A, b) + result = rsolve!(solver_rec, x, A, b) + # test that the solver actually converged + @test solver_rec.log.converged + end + + # test when the block size is greter than 1 using threshold stop + let A = Array(transpose(qr(transpose(rand(type, n_rows, n_cols))).Q)), + xsol = ones(type, n_cols), + b = A * xsol, + comp_dim = 2, + alpha = 1.0, + n_rows = size(A, 1), + n_cols = size(A, 2), + x = zeros(type, n_cols) + x_st = deepcopy(x) + + comp = ColTestCompressor(Right(), comp_dim) + #check 50 iterations + log = ColTestLog(50, 0.5) + err = ColTestError() + sub_solver = ColTestSubSolver() + solver = col_projection( + compressor = comp, + log = log, + error = err, + sub_solver = sub_solver, + alpha = alpha + ) + + solver_rec = complete_solver(solver, x, A, b) + result = rsolve!(solver_rec, x, A, b) + # test that the solver actually converged + @test solver_rec.log.converged + end + + end + + end end From 142815b92114cbf1366c6dc3f15dff100aa1bed7 Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 20 Aug 2025 01:16:26 -0500 Subject: [PATCH 5/9] Deleted LSgradient for now --- src/Solvers/col_projection.jl | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl index 0ede93ca..d1aba021 100644 --- a/src/Solvers/col_projection.jl +++ b/src/Solvers/col_projection.jl @@ -326,35 +326,3 @@ function rsolve!( return nothing end - -#---------------------------------------------------------------------- -""" - LSgradient <: SolverErrorRecipe - -A structure for the full gradient of ||b-Ax||_2^2, `A'(b-Ax)`. - -# Fields -- `gradient::AbstractVector`, a container for the full gradient. -""" -struct LSgradient <: SolverError end - -mutable struct LSgradientRecipe{V<:AbstractVector} <: SolverErrorRecipe - gradient::V -end - -function complete_error(error::LSgradient, A::AbstractMatrix, b::AbstractVector) - return LSgradientRecipe{typeof(b)}(zeros(size(A,2))) -end - -function compute_error( - error::LSgradientRecipe, - solver::col_projectionRecipe, - A::AbstractMatrix, - b::AbstractVector - )::Float64 - # copyto!(error.gradient, b) - # coompute A'r - mul!(error.gradient, A', solver.residual_vec) - - return dot(error.gradient, error.gradient) -end From 62337aae923acda927dff6f994c9ec3a1cb8c997 Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 20 Aug 2025 01:24:22 -0500 Subject: [PATCH 6/9] Update subsolver_abstract_types.jl --- test/Solvers/SubSolvers/subsolver_abstract_types.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Solvers/SubSolvers/subsolver_abstract_types.jl b/test/Solvers/SubSolvers/subsolver_abstract_types.jl index d52a1317..ac4236b0 100644 --- a/test/Solvers/SubSolvers/subsolver_abstract_types.jl +++ b/test/Solvers/SubSolvers/subsolver_abstract_types.jl @@ -17,6 +17,7 @@ end x = rand(2) @test_throws ArgumentError complete_sub_solver(TestSubSolver(), A) + @test_throws ArgumentError complete_sub_solver(TestSubSolver(), A, b) @test_throws ArgumentError update_sub_solver!(TestSubSolverRecipe(), A) @test_throws ArgumentError ldiv!(x, TestSubSolverRecipe(), b) end From 5674f532ff21563d37a0eb61c0700beb4487433c Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 20 Aug 2025 01:32:15 -0500 Subject: [PATCH 7/9] Deleted the line between documentation and function --- src/Solvers/col_projection.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl index d1aba021..a91cbc30 100644 --- a/src/Solvers/col_projection.jl +++ b/src/Solvers/col_projection.jl @@ -64,9 +64,7 @@ affect convergence. ## Returns - A `col_projection` object. - """ -#------------------------------------------------------------------ mutable struct col_projection <: Solver alpha::Float64 compressor::Compressor From aeb0e7ca404166d008d14e03d26a9e866f0c9052 Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 27 Aug 2025 04:56:07 -0500 Subject: [PATCH 8/9] Added LSgradient.jl and its testing --- docs/src/api/solver_errors.md | 3 + src/RLinearAlgebra.jl | 1 + src/Solvers/ErrorMethods.jl | 1 + src/Solvers/ErrorMethods/LSgradient.jl | 41 ++++++++++++ src/Solvers/col_projection.jl | 2 +- test/Solvers/ErrorMethods/LSgradient.jl | 84 +++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/Solvers/ErrorMethods/LSgradient.jl create mode 100644 test/Solvers/ErrorMethods/LSgradient.jl diff --git a/docs/src/api/solver_errors.md b/docs/src/api/solver_errors.md index 7ce61cdb..3e59bc74 100644 --- a/docs/src/api/solver_errors.md +++ b/docs/src/api/solver_errors.md @@ -16,6 +16,9 @@ FullResidual FullResidualRecipe +LSgradient + +LSgradientRecipe ``` ## Exported Functions diff --git a/src/RLinearAlgebra.jl b/src/RLinearAlgebra.jl index 58393286..7fa590ca 100644 --- a/src/RLinearAlgebra.jl +++ b/src/RLinearAlgebra.jl @@ -55,6 +55,7 @@ export QRSolver, QRSolverRecipe export SolverError, SolverErrorRecipe export complete_error, compute_error export FullResidual, FullResidualRecipe +export LSgradient, LSgradientRecipe # Export ApproximatorError types and functions export ApproximatorError, ApproximatorErrorRecipe diff --git a/src/Solvers/ErrorMethods.jl b/src/Solvers/ErrorMethods.jl index 93506d21..e5aa16d6 100644 --- a/src/Solvers/ErrorMethods.jl +++ b/src/Solvers/ErrorMethods.jl @@ -116,3 +116,4 @@ end # Include error method files include("ErrorMethods/full_residual.jl") +include("ErrorMethods/LSgradient.jl") diff --git a/src/Solvers/ErrorMethods/LSgradient.jl b/src/Solvers/ErrorMethods/LSgradient.jl new file mode 100644 index 00000000..1fd80026 --- /dev/null +++ b/src/Solvers/ErrorMethods/LSgradient.jl @@ -0,0 +1,41 @@ +""" + LSgradient <: SolverError + +A `SolverError` structure for computing the least-squares gradient, + ``\\nabla f(x) = A' (A x - b)`` + +# Fields +- None +""" +struct LSgradient <: SolverError end + +""" + LSgradientRecipe <: SolverErrorRecipe +A `SolverErrorRecipe` structure for computing the gradient of least-squares objective. + +# Fields +- `gradient::AbstractVector`, `A'r`. +""" +mutable struct LSgradientRecipe{V<:AbstractVector} <: SolverErrorRecipe + gradient::V +end + +function complete_error( + error::LSgradient, + solver::Solver, + A::AbstractMatrix, + b::AbstractVector +) + gradient = zeros(size(A,2)) + return LSgradientRecipe{typeof(b)}(gradient) +end + +function compute_error( + error::LSgradientRecipe, + solver::SolverRecipe, + A::AbstractMatrix, + b::AbstractVector +)::Float64 + mul!(error.gradient, A', solver.residual_vec, 1.0, 0.0) # grad = A'r + return norm(error.gradient) +end diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl index a91cbc30..695691eb 100644 --- a/src/Solvers/col_projection.jl +++ b/src/Solvers/col_projection.jl @@ -297,7 +297,7 @@ function rsolve!( for i in 1:solver.log.max_it err = compute_error(solver.error, solver, A, b) - println(err) + #println(err) # Update log adds value of err to log and checks stopping update_logger!(solver.log, err, i) if solver.log.converged diff --git a/test/Solvers/ErrorMethods/LSgradient.jl b/test/Solvers/ErrorMethods/LSgradient.jl new file mode 100644 index 00000000..22f846bb --- /dev/null +++ b/test/Solvers/ErrorMethods/LSgradient.jl @@ -0,0 +1,84 @@ +module gradient_error +using Test, RLinearAlgebra, Random +import LinearAlgebra: mul!, norm +using ..FieldTest +using ..ApproxTol +Random.seed!(1232) + +mutable struct TestSolver <: Solver end + +mutable struct TestSolverRecipe <: SolverRecipe + residual_vec::AbstractVector +end + +@testset "LS Gradient" begin + @testset "LS Gradient: SolverError" begin + # Verify Supertype + @test supertype(LSgradient) == SolverError + + # Verify fieldnames and types + @test fieldnames(LSgradient) == () + @test fieldtypes(LSgradient) == () + # Verify the internal constructor + + end + + @testset "LS Gradient: SolverErrorRecipe" begin + # Verify Supertype + @test supertype(LSgradientRecipe) == SolverErrorRecipe + + # Verify fieldnames and types + @test fieldnames(LSgradientRecipe) == (:gradient,) + @test fieldtypes(LSgradientRecipe) == (AbstractVector,) + end + + @testset "Residual: Complete error" begin + for type in [Float32, Float64, ComplexF32, ComplexF64] + let n_rows = 4, + n_cols = 6, + A = rand(type, n_rows, n_cols), + b = rand(type, n_rows), + x = rand(type, n_cols), + r = A*x - b, + solver_rec = TestSolverRecipe(r), + error_rec = complete_error(LSgradient(), TestSolver(), A, b) + + # Test the type + @test typeof(error_rec) == LSgradientRecipe{typeof(b)} + # Test type of residual vector + @test eltype(error_rec.gradient) == type + # Test residual vector to be all zeros + @test error_rec.gradient == zeros(type, n_cols) + end + + end + + end + + @testset "Residual: Compute Error" begin + for type in [Float32, Float64] + let n_rows = 4, + n_cols = 6, + A = rand(type, n_rows, n_cols), + b = rand(type, n_rows), + x = rand(type, n_cols), + r = A*x - b, + solver_rec = TestSolverRecipe(r), + solver = TestSolver(), + error_rec = complete_error(LSgradient(), TestSolver(), A, b) + + # compute the error value + err_val = compute_error(error_rec, solver_rec, A, b) + # compute the gradient + res = A' * r + # compute norm squared of residual + @test norm(res) ≈ err_val + end + + end + + end + +end + +end From fa3186191a689009c74d7ff9658a3ce1b4bbb4b4 Mon Sep 17 00:00:00 2001 From: Tongtong Jin Date: Wed, 17 Sep 2025 04:59:53 -0500 Subject: [PATCH 9/9] switched the default solver error from FullResidual to LSgradient --- src/Solvers/col_projection.jl | 2 +- test/Solvers/col_projection.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Solvers/col_projection.jl b/src/Solvers/col_projection.jl index 695691eb..a6946032 100644 --- a/src/Solvers/col_projection.jl +++ b/src/Solvers/col_projection.jl @@ -86,7 +86,7 @@ function col_projection(; alpha::Float64 = 1.0, compressor::Compressor = SparseSign(cardinality=Right()), log::Logger = BasicLogger(), - error::SolverError = FullResidual(), + error::SolverError = LSgradient(), sub_solver::SubSolver = QRSolver(), ) return col_projection( diff --git a/test/Solvers/col_projection.jl b/test/Solvers/col_projection.jl index 2457a1c2..44d5adb5 100644 --- a/test/Solvers/col_projection.jl +++ b/test/Solvers/col_projection.jl @@ -212,7 +212,7 @@ end @test typeof(solver.compressor) == SparseSign @test typeof(solver.compressor.cardinality) == Right @test typeof(solver.log) == BasicLogger - @test typeof(solver.error) == FullResidual + @test typeof(solver.error) == LSgradient @test typeof(solver.sub_solver) == QRSolver end