From 30b1dd2dfe753f9a986c8a2df7271be2cc0e5e4d Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 11 Jul 2025 20:28:02 +0100 Subject: [PATCH 01/14] added abstract types for CUR --- src/Approximators.jl | 48 +++++++++++++++++++++++++++++++++++++++++++ src/RLinearAlgebra.jl | 1 + 2 files changed, 49 insertions(+) diff --git a/src/Approximators.jl b/src/Approximators.jl index c2ae651c..72350b58 100644 --- a/src/Approximators.jl +++ b/src/Approximators.jl @@ -56,6 +56,54 @@ randomized rangefinder. """ abstract type RangeApproximatorRecipe <: ApproximatorRecipe end +""" + CUR + +An abstract type for the structures that contain the user-controlled parameters +corresponding to the Approximator methods that produce an CUR decomposition of a matrix A. +""" +abstract type CUR <: Approximator end + +""" + CURRecipe + +An abstract type for the structures that contain the user-controlled parameters, +linear system information, and preallocated memory for methods +corresponding to the Approximator methods that produce a CUR decomposition of a matrix A. +""" +abstract type CURRecipe <: ApproximatorRecipe end + +""" + CURCore + +An abstract type for the structures that contain the user controlled parameters for methods +that compute the core matrix in a CUR decomposition. +""" +abstract type CURCore end + +""" + CURCoreRecipe + +An abstract type for the structures that contain the preallocated memory and user controlled +parameters for methods that compute the core matrix in a CUR decomposition. +""" +abstract type CURCoreRecipe end + +""" + Selector + +An abstract type containing user controlled parameters for a technique that select indices +from a matrix. +""" +abstract type Selector end + +""" + SelectorRecipe + +An abstract type containing user controlled parameters and preallocated memory for a +technique that selects indices from matrix. +""" +abstract type SelectorRecipe end ################################### # Docstring Components ################################### diff --git a/src/RLinearAlgebra.jl b/src/RLinearAlgebra.jl index cfd4324f..dd58cc35 100644 --- a/src/RLinearAlgebra.jl +++ b/src/RLinearAlgebra.jl @@ -16,6 +16,7 @@ export Approximator, ApproximatorRecipe, ApproximatorAdjoint export rapproximate, rapproximate!, complete_approximator export RangeApproximator, RangeApproximatorRecipe export RangeFinder, RangeFinderRecipe +export CUR, CURRecipe # Export Compressor types and functions export Compressor, CompressorRecipe, CompressorAdjoint From 006379a2514b5dc8b7e958618dbfd91b69ab1459 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 14 Jul 2025 13:34:29 +0100 Subject: [PATCH 02/14] added selector abstract types --- docs/make.jl | 3 +- src/Approximators.jl | 56 ++----- src/Approximators/Selectors.jl | 145 ++++++++++++++++++ src/RLinearAlgebra.jl | 4 +- test/Approximators/selector_abstract_types.jl | 85 ++++++++++ 5 files changed, 244 insertions(+), 49 deletions(-) create mode 100644 src/Approximators/Selectors.jl create mode 100644 test/Approximators/selector_abstract_types.jl diff --git a/docs/make.jl b/docs/make.jl index 2a8d2030..6a75c806 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -33,7 +33,8 @@ makedocs( "Approximators" => [ "Approximators Overview" => "api/approximators.md", "Approximator Sub-routines" => [ - "ApproximatorErrors" => "api/approximator_errors.md" + "ApproximatorErrors" => "api/approximator_errors.md", + "Selectors" => "api/selectors.md" ], ], ], diff --git a/src/Approximators.jl b/src/Approximators.jl index 72350b58..3694eee8 100644 --- a/src/Approximators.jl +++ b/src/Approximators.jl @@ -56,54 +56,7 @@ randomized rangefinder. """ abstract type RangeApproximatorRecipe <: ApproximatorRecipe end -""" - CUR - -An abstract type for the structures that contain the user-controlled parameters -corresponding to the Approximator methods that produce an CUR decomposition of a matrix A. -""" -abstract type CUR <: Approximator end - -""" - CURRecipe - -An abstract type for the structures that contain the user-controlled parameters, -linear system information, and preallocated memory for methods -corresponding to the Approximator methods that produce a CUR decomposition of a matrix A. -""" -abstract type CURRecipe <: ApproximatorRecipe end - -""" - CURCore - -An abstract type for the structures that contain the user controlled parameters for methods -that compute the core matrix in a CUR decomposition. -""" -abstract type CURCore end - -""" - CURCoreRecipe -An abstract type for the structures that contain the preallocated memory and user controlled -parameters for methods that compute the core matrix in a CUR decomposition. -""" -abstract type CURCoreRecipe end - -""" - Selector - -An abstract type containing user controlled parameters for a technique that select indices -from a matrix. -""" -abstract type Selector end - -""" - SelectorRecipe - -An abstract type containing user controlled parameters and preallocated memory for a -technique that selects indices from matrix. -""" -abstract type SelectorRecipe end ################################### # Docstring Components ################################### @@ -120,6 +73,7 @@ approx_arg_list = Dict{Symbol,String}( :A => "`A::AbstractMatrix`, a target matrix for approximation.", :compressor_recipe => "`S::CompressorRecipe`, a fully initialized realization for a compression method for a specific matrix or collection of matrices and vectors.", + ) approx_output_list = Dict{Symbol,String}( @@ -130,8 +84,10 @@ approx_output_list = Dict{Symbol,String}( approx_method_description = Dict{Symbol,String}( :complete_approximator => "A function that generates an `ApproximatorRecipe` given arguments.", + :update_approximator => "A function that updates the `ApproximatorRecipe` in place given the arguments.", + :rapproximate => "A function that computes a low-rank approximation of the matrix `A` using the information in the provided `Approximator` data structure.", :complete_approximator_error => "A function that generates an `ApproximatorErrorRecipe` @@ -209,6 +165,7 @@ function complete_approximator(approximator::Approximator, A::AbstractMatrix) ) end + ################################### # rapproximate Interface ################################### @@ -477,3 +434,8 @@ end ############################################ include("Approximators/RangeApproximators/rangefinder.jl") include("Approximators/RangeApproximators/helpers/power_its.jl") + +############################################ +# Include the selector files +############################################ +include("Approximators/Selectors.jl") \ No newline at end of file diff --git a/src/Approximators/Selectors.jl b/src/Approximators/Selectors.jl new file mode 100644 index 00000000..fa226ade --- /dev/null +++ b/src/Approximators/Selectors.jl @@ -0,0 +1,145 @@ +################################### +# Abstract Types +################################### +""" + Selector + +An abstract type containing user controlled parameters for a technique that select indices +from a matrix. +""" +abstract type Selector end + +""" + SelectorRecipe + +An abstract type containing user controlled parameters and preallocated memory for a +technique that selects indices from matrix. +""" +abstract type SelectorRecipe end + +select_arg_list = Dict{Symbol,String}( + :selector => "`selector::Selector`, a data structure containing the user-defined + parameters associated with a particular selection method.", + :selector_recipe => "`selector_recipe::SelectorRecipe`, a fully initialized realization + for a selector method for a particular matrix.", + :idx => "`idx::vector`, a vector where selected indices will be placed.", + :n_idx => "`n_idx::Int64`, the number of indices to be selected.", + :start_idx => "`start_idx::Int64`. the starting location in `idx` where the indices + will be placed.", + :A => "`A::AbstractMatrix`, a target matrix for approximation.", +) +select_output_list = Dict{Symbol,String}( + :selector_recipe => "A `SelectorRecipe` object.", +) +select_method_description = Dict{Symbol,String}( + :update_selector => "A function that updates the `SelectorRecipe` in place given the + arguments.", + :complete_selector => "A function that generates a `SelectorRecipe` given + arguments.", + :select_indices => "A function that selects indices from a matrix `A` using a specific + `SelectorRecipe`. It updates the vector `idx` in place with `n_idx` new indices starting + at index `start_idx`." +) +################################## +# Complete Selector Interface +################################## +""" + complete_selector(selector::Selector, A::AbstractMatrix) + +$(select_method_description[:complete_selector]) + +# Arguments +- $(select_arg_list[:selector]) +- $(select_arg_list[:A]) + +# Outputs +- $(select_output_list[:selector_recipe]) +""" +function complete_selector(selector::Selector, A::AbstractMatrix) + return throw( + ArgumentError( + "No method `complete_selector` exists for selector of type\ + $(typeof(selector)) and matrix of type $(typeof(A))." + ) + ) +end + +################################## +# update_selector! +################################## +""" + update_selector!(selector::SelectorRecipe) + +$(select_method_description[:update_selector]) + +# Arguments +- $(select_arg_list[:selector_recipe]) + +# Outputs +- $(select_output_list[:selector_recipe]) +""" +function update_selector!(selector::SelectorRecipe) + return throw( + ArgumentError( + "No method `update_selector!` exists for selector of type\ + $(typeof(selector))." + ) + ) +end + +""" + update_selector!(selector::SelectorRecipe, A::AbstractMatrix) + +$(select_method_description[:update_selector]) + +# Arguments +- $(select_arg_list[:selector_recipe]) +- $(select_arg_list[:A]) + +# Outputs +- $(select_output_list[:selector_recipe]) +""" +function update_selector!(selector::SelectorRecipe, A::AbstractMatrix) + return update_selector!(selector) +end + +#################################### +# select_indices! +#################################### +""" + select_indices!( + selector::SelectorRecipe, + A::AbstractMatrix, + idx::AbstractVector, + n_idx::Int64, + start_idx::Int64 + ) + +$(select_method_description[:select_indices]) + +# Arguments +- $(select_arg_list[:selector_recipe]) +- $(select_arg_list[:A]) +- $(select_arg_list[:idx]) +- $(select_arg_list[:n_idx]) +- $(select_arg_list[:start_idx]) + +# Outputs +- Returns `nothing` +""" +function select_indices!( + selector::SelectorRecipe, + A::AbstractMatrix, + idx::AbstractVector, + n_idx::Int64, + start_idx::Int64 +) + return throw( + ArgumentError( + "No method `select_indices` exists for selector of type $(typeof(selector)),\ + matrix of type $(typeof(A)), idx of type $(typeof(idx)), n_idx of type \ + $(typeof(n_idx)), and start_idx of type $(typeof(start_idx))." + ) + ) +end + diff --git a/src/RLinearAlgebra.jl b/src/RLinearAlgebra.jl index dd58cc35..8bac8830 100644 --- a/src/RLinearAlgebra.jl +++ b/src/RLinearAlgebra.jl @@ -16,7 +16,6 @@ export Approximator, ApproximatorRecipe, ApproximatorAdjoint export rapproximate, rapproximate!, complete_approximator export RangeApproximator, RangeApproximatorRecipe export RangeFinder, RangeFinderRecipe -export CUR, CURRecipe # Export Compressor types and functions export Compressor, CompressorRecipe, CompressorAdjoint @@ -56,4 +55,7 @@ export FullResidual, FullResidualRecipe export ApproximatorError, ApproximatorErrorRecipe export complete_approximator_error, compute_approximator_error, compute_approximator_error! +# Export Selector types and functions +export Selector, SelectorRecipe +export complete_selector, update_selector!, select_indices! end #module diff --git a/test/Approximators/selector_abstract_types.jl b/test/Approximators/selector_abstract_types.jl new file mode 100644 index 00000000..5cde26f5 --- /dev/null +++ b/test/Approximators/selector_abstract_types.jl @@ -0,0 +1,85 @@ +module selector_abstract_types +using Test, RLinearAlgebra +import RLinearAlgebra: complete_selector, update_selector!, select_indices! + + +############################# +# Initial Testing Parameters +############################# +struct TestSelector <: Selector end +mutable struct TestSelectorRecipe <: SelectorRecipe + code::Int64 +end + +######################################## +# Tests for Selector Abstract Types +######################################## +@testset "Selector Abstract Types" begin + @test isdefined(Main, :Selector) + @test isdefined(Main, :SelectorRecipe) +end + +@testset "Selector Completion Errors" begin + A = ones(2,2) + + @test_throws ArgumentError complete_selector(TestSelector(), A) +end + +############################# +# Update Testing Parameters +############################# +complete_selector(selector::TestSelector, A::AbstractMatrix) = + TestSelectorRecipe(1) + +@testset "Selector Completion" begin + A =ones(2, 2) + + select = complete_selector(TestSelector(), A) + @test select isa TestSelectorRecipe + @test select.code == 1 +end + +############################### +# Test Updating selector errors +############################### + +@testset "Selector Update Errors" begin + A = ones(2,2) + + select = complete_selector(TestSelector(), A) + @test_throws ArgumentError update_selector!(select, A) + @test_throws ArgumentError update_selector!(select) + +end + +############################### +# Test update_selector +############################### + +function update_selector!(selector::TestSelectorRecipe, A::AbstractMatrix) + selector.code = 2 +end + +@testset "Selector Updating" begin + A = ones(2, 2) + + select = complete_selector(TestSelector(), A) + update_selector!(select, A) + @test select isa TestSelectorRecipe + @test select.code == 2 +end + +################################# +# Test selection errors +################################# +@testset "Select Indices" begin + A = ones(2,2) + select = complete_selector(TestSelector(), A) + idx = zeros(Int64, 2) + n_idx = 2 + start_idx = 1 + + @test_throws ArgumentError select_indices!(select, A, idx, n_idx, start_idx) +end + +end From 9a00dade295567d0bf983d73560f74785ebfa9d6 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 14 Jul 2025 13:52:59 +0100 Subject: [PATCH 03/14] adding forgotten selectors markdown --- docs/src/api/selectors.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/src/api/selectors.md diff --git a/docs/src/api/selectors.md b/docs/src/api/selectors.md new file mode 100644 index 00000000..f904f1ae --- /dev/null +++ b/docs/src/api/selectors.md @@ -0,0 +1,30 @@ +# Selectors +```@contents +Pages = ["selectors.md"] +``` + +## Abstract Types +```@docs +Selector + +SelectorRecipe +``` + +## Selector Structures +```@docs + +``` + +## Exported Functions +```@docs +complete_selector + +update_selector! + +select_indices! +``` + +## Internal Functions +```@docs + +``` From 99b004e33e54035e9bebdc204954dcd9d981b216 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 6 Oct 2025 12:27:35 +0100 Subject: [PATCH 04/14] added code for an LU with partial pivoting selector --- docs/src/api/selectors.md | 3 + src/Approximators.jl | 2 +- src/Approximators/Selectors.jl | 6 +- .../selection_techniques/lupp.jl | 62 ++++++++++ src/RLinearAlgebra.jl | 3 +- .../selection_techniques/lupp.jl | 112 ++++++++++++++++++ test/Approximators/selector_abstract_types.jl | 2 +- test/runtests.jl | 1 + 8 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 src/Approximators/selection_techniques/lupp.jl create mode 100644 test/Approximators/selection_techniques/lupp.jl diff --git a/docs/src/api/selectors.md b/docs/src/api/selectors.md index f904f1ae..81458dd6 100644 --- a/docs/src/api/selectors.md +++ b/docs/src/api/selectors.md @@ -12,6 +12,9 @@ SelectorRecipe ## Selector Structures ```@docs +RLinearAlgebra.LUPP + +LUPPRecipe ``` diff --git a/src/Approximators.jl b/src/Approximators.jl index 3694eee8..02b8cbfe 100644 --- a/src/Approximators.jl +++ b/src/Approximators.jl @@ -438,4 +438,4 @@ include("Approximators/RangeApproximators/helpers/power_its.jl") ############################################ # Include the selector files ############################################ -include("Approximators/Selectors.jl") \ No newline at end of file +include("Approximators/Selectors.jl") diff --git a/src/Approximators/Selectors.jl b/src/Approximators/Selectors.jl index fa226ade..8e7f5a1d 100644 --- a/src/Approximators/Selectors.jl +++ b/src/Approximators/Selectors.jl @@ -128,9 +128,9 @@ $(select_method_description[:select_indices]) - Returns `nothing` """ function select_indices!( + idx::AbstractVector, selector::SelectorRecipe, A::AbstractMatrix, - idx::AbstractVector, n_idx::Int64, start_idx::Int64 ) @@ -142,4 +142,6 @@ function select_indices!( ) ) end - + +# Include the selector files +include("selection_techniques/lupp.jl") diff --git a/src/Approximators/selection_techniques/lupp.jl b/src/Approximators/selection_techniques/lupp.jl new file mode 100644 index 00000000..faf26d8b --- /dev/null +++ b/src/Approximators/selection_techniques/lupp.jl @@ -0,0 +1,62 @@ +""" + LUPP <: Selector + +A `Selector` that implements LU with partial pivoting for selecting column indices from a +matrix. + +# Fields +- None +""" +mutable struct LUPP <: Selector + +end + +""" + LUPPRecipe <: SelectorRecipe + +A `SelectorRecipe` that contains all the necessary preallocations for selecting column +indices from a matrix using LU with partial pivoting. + +# Fields +- None +""" +mutable struct LUPPRecipe <: SelectorRecipe + +end + +function complete_selector(ingredients::LUPP) + return LUPPRecipe() +end + +function update_selector!(selector::LUPPRecipe) + return nothing +end + +function select_indices!( + idx::AbstractVector, + selector::LUPPRecipe, + A::AbstractMatrix, + n_idx::Int64, + start_idx::Int64 +) + if n_idx > size(A, 2) + throw( + DimensionMismatch( + "`n_idx` cannot be larger than the number of columns in `A`." + ) + ) + end + + if start_idx + n_idx - 1 > size(idx, 1) + throw( + DimensionMismatch( + "`start_idx` + `n_idx` - 1 cannot be larger than the lenght of `idx`." + ) + ) + end + + # because LUPP selects rows and selectors select columns we need to pivot on A' + p = lu!(A').p + idx[start_idx:start_idx + n_idx - 1] = p[1:n_idx] + return nothing +end diff --git a/src/RLinearAlgebra.jl b/src/RLinearAlgebra.jl index 8bac8830..de1d375c 100644 --- a/src/RLinearAlgebra.jl +++ b/src/RLinearAlgebra.jl @@ -1,7 +1,7 @@ module RLinearAlgebra import Base.:* import Base: transpose, adjoint -import LinearAlgebra: Adjoint, axpby!, dot, ldiv!, lmul!, lq!, lq, LQ, mul!, norm, qr! +import LinearAlgebra: Adjoint, axpby!, dot, ldiv!, lmul!, lq!, lq, LQ, lu!, mul!, norm, qr! import StatsBase: sample!, ProbabilityWeights, wsample! import Random: bitrand, rand!, randn! import SparseArrays: SparseMatrixCSC, sprandn @@ -57,5 +57,6 @@ export complete_approximator_error, compute_approximator_error, compute_approxim # Export Selector types and functions export Selector, SelectorRecipe +export LUPP, LUPPRecipe export complete_selector, update_selector!, select_indices! end #module diff --git a/test/Approximators/selection_techniques/lupp.jl b/test/Approximators/selection_techniques/lupp.jl new file mode 100644 index 00000000..5505d8e4 --- /dev/null +++ b/test/Approximators/selection_techniques/lupp.jl @@ -0,0 +1,112 @@ +module LUPP_tests +using Test +using ..FieldTest +using ..ApproxTol +using RLinearAlgebra +import LinearAlgebra: mul! + +@testset "LUPP Tests" begin + @testset "LUPP" begin + supertype(LUPP) == Selector + + # test fieldnames and types + fieldnames(LUPP) == () + fieldtypes(LUPP) == () + + # Test Constructor + let + sel = LUPP() + @test typeof(sel) == LUPP + end + + end + + @testset "LUPPRecipe" begin + supertype(LUPPRecipe) == SelectorRecipe + # test fieldnames and types + fieldnames(LUPPRecipe) == () + fieldtypes(LUPPRecipe) == () + + + # Test Constructor + let + sel = LUPPRecipe() + @test typeof(sel) == LUPPRecipe + end + + end + + @testset "LUPP: Complete Selector" begin + let sel = LUPP() + sel_rec = complete_selector(sel) + @test typeof(sel_rec) == LUPPRecipe + end + + end + + @testset "LUPP: Update Selector" begin + let sel_rec = complete_selector(LUPP()) + update_selector!(sel_rec) + @test typeof(sel_rec) == LUPPRecipe + end + + end + + @testset "LUPP: Select Indices" begin + A = [0 1 0; + 0 0 2; + 3 0 0] + # test selecting only one index + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 2, + n_idx = 1 + + select_indices!(idx, LUPPRecipe(), A, n_idx, start_idx) + @test idx[2] == 2 + end + + # test selecting three indices + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 3 + + select_indices!(idx, LUPPRecipe(), A, n_idx, start_idx) + @test idx == [2; 3; 1] + end + + # test the error checking + let A = A, + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 4 + + @test_throws DimensionMismatch select_indices!( + idx, + LUPPRecipe(), + A, + n_idx, + start_idx + ) + end + + let A = A, + idx = zeros(Int64, 3), + start_idx = 3, + n_idx = 2 + + @test_throws DimensionMismatch select_indices!( + idx, + LUPPRecipe(), + A, + n_idx, + start_idx + ) + end + + end + +end + +end diff --git a/test/Approximators/selector_abstract_types.jl b/test/Approximators/selector_abstract_types.jl index 5cde26f5..8c607560 100644 --- a/test/Approximators/selector_abstract_types.jl +++ b/test/Approximators/selector_abstract_types.jl @@ -79,7 +79,7 @@ end n_idx = 2 start_idx = 1 - @test_throws ArgumentError select_indices!(select, A, idx, n_idx, start_idx) + @test_throws ArgumentError select_indices!(idx, select, A, n_idx, start_idx) end end diff --git a/test/runtests.jl b/test/runtests.jl index 09b5ef92..afb0dc2e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,6 +13,7 @@ directs = "./", "Approximators/", "Approximators/RangeApproximator/", + "Approximators/selection_techniques/", "Solvers/", "Solvers/ErrorMethods/", "Solvers/Loggers/", From 450506928174689d3b824bddf4741763ca91ba0c Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 6 Oct 2025 12:50:24 +0100 Subject: [PATCH 05/14] intialized the qr with column pivoting selection --- docs/src/api/selectors.md | 3 + src/Approximators/Selectors.jl | 1 + .../selection_techniques/qrcp.jl | 62 ++++++++++ src/RLinearAlgebra.jl | 4 +- .../selection_techniques/qrcp.jl | 112 ++++++++++++++++++ 5 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 src/Approximators/selection_techniques/qrcp.jl create mode 100644 test/Approximators/selection_techniques/qrcp.jl diff --git a/docs/src/api/selectors.md b/docs/src/api/selectors.md index 81458dd6..791b679d 100644 --- a/docs/src/api/selectors.md +++ b/docs/src/api/selectors.md @@ -16,6 +16,9 @@ RLinearAlgebra.LUPP LUPPRecipe +QRCP + +QRCPRecipe ``` ## Exported Functions diff --git a/src/Approximators/Selectors.jl b/src/Approximators/Selectors.jl index 8e7f5a1d..975189d1 100644 --- a/src/Approximators/Selectors.jl +++ b/src/Approximators/Selectors.jl @@ -145,3 +145,4 @@ end # Include the selector files include("selection_techniques/lupp.jl") +include("selection_techniques/qrcp.jl") diff --git a/src/Approximators/selection_techniques/qrcp.jl b/src/Approximators/selection_techniques/qrcp.jl new file mode 100644 index 00000000..59951ac8 --- /dev/null +++ b/src/Approximators/selection_techniques/qrcp.jl @@ -0,0 +1,62 @@ +""" + QRCP <: Selector + +A `Selector` that implements QR with column norm pivoting for selecting column indices from +a matrix. + +# Fields +- None +""" +mutable struct QRCP <: Selector + +end + +""" + QRCPRecipe <: SelectorRecipe + +A `SelectorRecipe` that contains all the necessary preallocations for selecting column +indices from a matrix using QR with column norm pivoting. + +# Fields +- None +""" +mutable struct QRCPRecipe <: SelectorRecipe + +end + +function complete_selector(ingredients::QRCP) + return QRCPRecipe() +end + +function update_selector!(selector::QRCPRecipe) + return nothing +end + +function select_indices!( + idx::AbstractVector, + selector::QRCPRecipe, + A::AbstractMatrix, + n_idx::Int64, + start_idx::Int64 +) + if n_idx > size(A, 2) + throw( + DimensionMismatch( + "`n_idx` cannot be larger than the number of columns in `A`." + ) + ) + end + + if start_idx + n_idx - 1 > size(idx, 1) + throw( + DimensionMismatch( + "`start_idx` + `n_idx` - 1 cannot be larger than the lenght of `idx`." + ) + ) + end + + p = qr!(A, ColumnNorm()).p + # store newly selected indices at inputed index storage points + idx[start_idx:start_idx + n_idx - 1] = p[1:n_idx] + return nothing +end diff --git a/src/RLinearAlgebra.jl b/src/RLinearAlgebra.jl index e2c4b792..5070e903 100644 --- a/src/RLinearAlgebra.jl +++ b/src/RLinearAlgebra.jl @@ -1,7 +1,7 @@ module RLinearAlgebra import Base.:* import Base: transpose, adjoint -import LinearAlgebra: Adjoint, axpby!, dot, I, ldiv!, lmul!, lq!, lq, LQ, lu! +import LinearAlgebra: Adjoint, axpby!, ColumnNorm, dot, I, ldiv!, lmul!, lq!, lq, LQ, lu! import LinearAlgebra: mul!, norm, qr!, svd import StatsBase: sample, sample!, ProbabilityWeights, wsample! import Random: bitrand, rand!, randn! @@ -64,6 +64,6 @@ export complete_approximator_error, compute_approximator_error, compute_approxim # Export Selector types and functions export Selector, SelectorRecipe -export LUPP, LUPPRecipe +export LUPP, LUPPRecipe, QRCP, QRCPRecipe export complete_selector, update_selector!, select_indices! end #module diff --git a/test/Approximators/selection_techniques/qrcp.jl b/test/Approximators/selection_techniques/qrcp.jl new file mode 100644 index 00000000..17fd55b8 --- /dev/null +++ b/test/Approximators/selection_techniques/qrcp.jl @@ -0,0 +1,112 @@ +module QRCP_tests +using Test +using ..FieldTest +using ..ApproxTol +using RLinearAlgebra +import LinearAlgebra: mul! + +@testset "QRCP Tests" begin + @testset "QRCP" begin + supertype(QRCP) == Selector + + # test fieldnames and types + fieldnames(QRCP) == () + fieldtypes(QRCP) == () + + # Test Constructor + let + sel = QRCP() + @test typeof(sel) == QRCP + end + + end + + @testset "QRCPRecipe" begin + supertype(QRCPRecipe) == SelectorRecipe + # test fieldnames and types + fieldnames(QRCPRecipe) == () + fieldtypes(QRCPRecipe) == () + + + # Test Constructor + let + sel = QRCPRecipe() + @test typeof(sel) == QRCPRecipe + end + + end + + @testset "QRCP: Complete Selector" begin + let sel = QRCP() + sel_rec = complete_selector(sel) + @test typeof(sel_rec) == QRCPRecipe + end + + end + + @testset "QRCP: Update Selector" begin + let sel_rec = complete_selector(QRCP()) + update_selector!(sel_rec) + @test typeof(sel_rec) == QRCPRecipe + end + + end + + @testset "QRCP: Select Indices" begin + A = [0 1 0; + 0 0 2; + 3 0 0] + # test selecting only one index + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 2, + n_idx = 1 + + select_indices!(idx, QRCPRecipe(), A, n_idx, start_idx) + @test idx[2] == 1 + end + + # test selecting three indices + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 3 + + select_indices!(idx, QRCPRecipe(), A, n_idx, start_idx) + @test idx == [1; 3; 2] + end + + # test the error checking + let A = A, + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 4 + + @test_throws DimensionMismatch select_indices!( + idx, + QRCPRecipe(), + A, + n_idx, + start_idx + ) + end + + let A = A, + idx = zeros(Int64, 3), + start_idx = 3, + n_idx = 2 + + @test_throws DimensionMismatch select_indices!( + idx, + QRCPRecipe(), + A, + n_idx, + start_idx + ) + end + + end + +end + +end From 43c3a22ca8aa10b515fe2f9a47d68a379a20a03c Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 6 Oct 2025 15:19:16 +0100 Subject: [PATCH 06/14] corrected spelling in a comment --- src/Approximators/selection_techniques/qrcp.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Approximators/selection_techniques/qrcp.jl b/src/Approximators/selection_techniques/qrcp.jl index 59951ac8..db184d58 100644 --- a/src/Approximators/selection_techniques/qrcp.jl +++ b/src/Approximators/selection_techniques/qrcp.jl @@ -56,7 +56,7 @@ function select_indices!( end p = qr!(A, ColumnNorm()).p - # store newly selected indices at inputed index storage points + # store newly selected indices at inputted index storage points idx[start_idx:start_idx + n_idx - 1] = p[1:n_idx] return nothing end From 0c61034b1556c85a9e11b9d6815bf7e03432c209 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 10 Oct 2025 13:04:32 +0100 Subject: [PATCH 07/14] added identity compressor --- docs/src/api/compressors.md | 13 ++-- src/Compressors.jl | 1 + src/Compressors/identity.jl | 95 ++++++++++++++++++++++++++++ src/RLinearAlgebra.jl | 3 +- test/Compressors/gaussian.jl | 2 +- test/Compressors/identity.jl | 116 +++++++++++++++++++++++++++++++++++ 6 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 src/Compressors/identity.jl create mode 100644 test/Compressors/identity.jl diff --git a/docs/src/api/compressors.md b/docs/src/api/compressors.md index dd4a6dae..53811bf5 100644 --- a/docs/src/api/compressors.md +++ b/docs/src/api/compressors.md @@ -22,16 +22,21 @@ Undef ## Compressor Structures ```@docs +FJLT + +FJLTRecipe + Gaussian GaussianRecipe -SparseSign -SparseSignRecipe +Identity -FJLT +IdentityRecipe -FJLTRecipe +SparseSign + +SparseSignRecipe ``` ## Exported Functions diff --git a/src/Compressors.jl b/src/Compressors.jl index c3ad9b35..0c55cb90 100644 --- a/src/Compressors.jl +++ b/src/Compressors.jl @@ -574,3 +574,4 @@ include("Compressors/gaussian.jl") include("Compressors/sparse_sign.jl") include("Compressors/fjlt.jl") include("Compressors/helpers/fwht.jl") +include("Compressors/identity.jl") diff --git a/src/Compressors/identity.jl b/src/Compressors/identity.jl new file mode 100644 index 00000000..36c0eb1a --- /dev/null +++ b/src/Compressors/identity.jl @@ -0,0 +1,95 @@ +""" + Identity <: Compressor + +An implementation of a compressor that returns the original matrix. + +# Fields +- None + +# Constructor + + Identity() + +# Returns +- A `Identity` object. +""" +mutable struct Identity <: Compressor end + +""" + IdentityRecipe <: CompressorRecipe + +The recipe containing all allocations and information for the `Identity` compressor. + +# Fields +- `cardinality::Cardinality`, the direction the compression matrix is intended to + be applied to a target matrix or operator. Values allowed are `Left()` or `Right()`. + By default this will be `Left()`. +- `n_rows::Int64`, the number of rows of the compression matrix. +- `n_cols::Int64`, the number of columns of the compression matrix. +""" +mutable struct IdentityRecipe <: CompressorRecipe + cardinality::Cardinality + n_rows::Int64 + n_cols::Int64 +end + +function complete_compressor(ingredients::Identity, A::AbstractMatrix) + n_rows, n_cols = size(A) + return IdentityRecipe(Left(), n_rows, n_cols) +end + +function update_compressor!(S::IdentityRecipe) + return nothing +end + +function mul!( + C::AbstractArray, + S::IdentityRecipe, + A::AbstractArray, + alpha::Number, + beta::Number +) + # change the size of the compressor to align with the size of A + # this is a special feature only for the identity compressor + # this means setting the dimension of the compressor to the active dimension (rows) + eff_dim = size(A, 1) + S.n_rows = eff_dim + S.n_cols = eff_dim + left_mul_dimcheck(C, S, A) + mul!(C, I, A, alpha, beta) + return nothing +end + +function mul!( + C::AbstractArray, + A::AbstractArray, + S::IdentityRecipe, + alpha::Number, + beta::Number +) + # change the size of the compressor to align with the size of A + # this is a special feature only for the identity compressor + # this means setting the dimension of the compressor to the active dimension (cols) + eff_dim = size(A, 2) + S.n_rows = eff_dim + S.n_cols = eff_dim + right_mul_dimcheck(C, A, S) + mul!(C, A, I, alpha, beta) + return nothing +end + +# Because we want to return A we need to set the size of C to be that of A with identity +# compressor +# S * A +function (*)(S::IdentityRecipe, A::AbstractArray) + C = zeros(size(A)) + mul!(C, S, A) + return C +end + +# A * S +function (*)(A::AbstractArray, S::IdentityRecipe) + C = zeros(size(A)) + mul!(C, A, S) + return C +end diff --git a/src/RLinearAlgebra.jl b/src/RLinearAlgebra.jl index cfd4324f..ba985e9e 100644 --- a/src/RLinearAlgebra.jl +++ b/src/RLinearAlgebra.jl @@ -1,7 +1,7 @@ module RLinearAlgebra import Base.:* import Base: transpose, adjoint -import LinearAlgebra: Adjoint, axpby!, dot, ldiv!, lmul!, lq!, lq, LQ, mul!, norm, qr! +import LinearAlgebra: Adjoint, axpby!, dot, I, ldiv!, lmul!, lq!, lq, LQ, mul!, norm, qr! import StatsBase: sample!, ProbabilityWeights, wsample! import Random: bitrand, rand!, randn! import SparseArrays: SparseMatrixCSC, sprandn @@ -23,6 +23,7 @@ export Cardinality, Left, Right, Undef export complete_compressor, update_compressor! export FJLT, FJLTRecipe export Gaussian, GaussianRecipe +export Identity, IdentityRecipe export SparseSign, SparseSignRecipe # Export Distribution types and functions diff --git a/test/Compressors/gaussian.jl b/test/Compressors/gaussian.jl index c211dd67..2751fa20 100644 --- a/test/Compressors/gaussian.jl +++ b/test/Compressors/gaussian.jl @@ -284,4 +284,4 @@ seed!(21321) end -end \ No newline at end of file +end diff --git a/test/Compressors/identity.jl b/test/Compressors/identity.jl new file mode 100644 index 00000000..0df1a2a0 --- /dev/null +++ b/test/Compressors/identity.jl @@ -0,0 +1,116 @@ +module Identity_compressor +using Test, RLinearAlgebra +import Base.* +import LinearAlgebra:mul! +import Random:seed! +using ..FieldTest +using ..ApproxTol + +seed!(21321) +@testset "Identity" begin + @testset "Identity: Compressor" begin + # Verify supertype + @test supertype(Identity) == Compressor + + # Verify that the constructor works + @test typeof(Identity()) == Identity + end + + @testset "Identity: CompressorRecipe" begin + @test_compressor IdentityRecipe + + @test fieldnames(IdentityRecipe) == (:cardinality, :n_rows, :n_cols) + @test fieldtypes(IdentityRecipe) == (Cardinality, Int64, Int64) + end + + @testset "Identity: Complete Compressor" begin + let n_rows = 10, + n_cols = 10, + A = ones(n_rows, n_cols) + + compressor_recipe = complete_compressor(Identity(), A) + + # test values and types + @test compressor_recipe.cardinality == Left() + @test compressor_recipe.n_rows == n_rows + @test compressor_recipe.n_cols == n_cols + end + + end + + @testset "Identity: Update Compressor" begin + let n_rows = 10, + n_cols = 10, + A = ones(n_rows, n_cols) + + compressor_recipe = complete_compressor(Identity(), A) + update_compressor!(compressor_recipe) + # test values and types + @test compressor_recipe.cardinality == Left() + @test compressor_recipe.n_rows == n_rows + @test compressor_recipe.n_cols == n_cols + end + + end + + @testset "Identity: Left multiplication" begin + let n_rows = 10, + n_cols = 3, + c_dim = 6, + A = rand(n_rows, n_cols), + C = rand(n_rows, n_cols), + x = rand(n_cols), + y = rand(n_cols) + + S = complete_compressor(Identity(), A) + # Test matrix multiplication from the left + # See if multiplying by S or S' always returns the matrix/vector it is being + # applied to + @test S * A ≈ A + @test S' * A ≈ A + @test S * x ≈ x + @test S' * x ≈ x + mul!(C, S, A, 2.0, 0.0) + @test C ≈ 2.0 * A + mul!(C', A', S, 2.0, 0.0) + @test C ≈ 2.0 * A + mul!(y, S, x, 2.0, 0.0) + @test y ≈ 2.0 * x + mul!(x, S', y, 2.0, 0.0) + @test x ≈ 2.0 * y + end + + end + + @testset "Identity: Right multiplication" begin + let n_rows = 10, + n_cols = 3, + c_dim = 6, + A = rand(n_rows, n_cols), + C = rand(n_rows, n_cols), + x = rand(n_cols), + y = rand(n_cols) + + S = complete_compressor(Identity(), A) + # Test matrix multiplication from the left + # See if multiplying by S or S' always returns the matrix/vector it is being + # applied to + @test A * S ≈ A + @test A * S' ≈ A + @test x' * S ≈ x' + @test x' * S' ≈ x' + mul!(C, A, S, 2.0, 0.0) + @test C ≈ 2.0 * A + mul!(C', S, A', 2.0, 0.0) + @test C ≈ 2.0 * A + mul!(y', x', S, 2.0, 0.0) + @test y ≈ 2.0 * x + mul!(x', y', S', 2.0, 0.0) + @test x ≈ 2.0 * y + end + + end + +end + +end From c6ad815cf590d1a55745295b4e7671dc77908d26 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 10 Oct 2025 13:54:12 +0100 Subject: [PATCH 08/14] updated the technique structure to include cardinality --- src/Compressors/identity.jl | 40 ++++++++++++++++++++++++------- test/Compressors/identity.jl | 46 +++++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/src/Compressors/identity.jl b/src/Compressors/identity.jl index 36c0eb1a..03d7fdb3 100644 --- a/src/Compressors/identity.jl +++ b/src/Compressors/identity.jl @@ -4,16 +4,27 @@ An implementation of a compressor that returns the original matrix. # Fields -- None +- `cardinality::Cardinality`, the direction the compression matrix is intended to + be applied to a target matrix or operator. Values allowed are `Left()` or `Right()`. # Constructor - Identity() + Identity(;cardinality=Left()) + +## Keywords +- `cardinality::Cardinality`, the direction the compression matrix is intended to + be applied to a target matrix or operator. Values allowed are `Left()` or `Right()`. -# Returns +## Returns - A `Identity` object. """ -mutable struct Identity <: Compressor end +mutable struct Identity <: Compressor + cardinality::Cardinality +end + +function Identity(;cardinality = Left()) + Identity(cardinality) +end """ IdentityRecipe <: CompressorRecipe @@ -23,11 +34,10 @@ The recipe containing all allocations and information for the `Identity` compres # Fields - `cardinality::Cardinality`, the direction the compression matrix is intended to be applied to a target matrix or operator. Values allowed are `Left()` or `Right()`. - By default this will be `Left()`. - `n_rows::Int64`, the number of rows of the compression matrix. - `n_cols::Int64`, the number of columns of the compression matrix. """ -mutable struct IdentityRecipe <: CompressorRecipe +mutable struct IdentityRecipe{C} <: CompressorRecipe where C<:Cardinality cardinality::Cardinality n_rows::Int64 n_cols::Int64 @@ -35,10 +45,24 @@ end function complete_compressor(ingredients::Identity, A::AbstractMatrix) n_rows, n_cols = size(A) - return IdentityRecipe(Left(), n_rows, n_cols) + card = ingredients.cardinality + if ingredients.cardinality == Left() + return IdentityRecipe{typeof(card)}(Left(), n_rows, n_rows) + elseif ingredients.cardinality == Right() + return IdentityRecipe{typeof(card)}(Right(), n_cols, n_cols) + end + +end + +function update_compressor!(S::IdentityRecipe{<:Left}, A::AbstractMatrix) + S.n_rows = size(A,1) + S.n_cols = size(A,1) + return nothing end -function update_compressor!(S::IdentityRecipe) +function update_compressor!(S::IdentityRecipe{<:Right}, A::AbstractMatrix) + S.n_rows = size(A,2) + S.n_cols = size(A,2) return nothing end diff --git a/test/Compressors/identity.jl b/test/Compressors/identity.jl index 0df1a2a0..ecc5376f 100644 --- a/test/Compressors/identity.jl +++ b/test/Compressors/identity.jl @@ -12,6 +12,10 @@ seed!(21321) # Verify supertype @test supertype(Identity) == Compressor + # Verify Field names + @test fieldnames(Identity) == (:cardinality,) + @test fieldtypes(Identity) == (Cardinality,) + # Verify that the constructor works @test typeof(Identity()) == Identity end @@ -24,8 +28,9 @@ seed!(21321) end @testset "Identity: Complete Compressor" begin + # test with left cardinality let n_rows = 10, - n_cols = 10, + n_cols = 3, A = ones(n_rows, n_cols) compressor_recipe = complete_compressor(Identity(), A) @@ -33,24 +38,53 @@ seed!(21321) # test values and types @test compressor_recipe.cardinality == Left() @test compressor_recipe.n_rows == n_rows - @test compressor_recipe.n_cols == n_cols + @test compressor_recipe.n_cols == n_rows + end + + # test with right cardinality + let n_rows = 10, + n_cols = 3, + A = ones(n_rows, n_cols) + + compressor_recipe = complete_compressor(Identity(cardinality = Right()), A) + + # test values and types + @test compressor_recipe.cardinality == Right() + @test compressor_recipe.n_rows == n_cols + @test compressor_recipe.n_cols == n_cols end end @testset "Identity: Update Compressor" begin + # try updating a left compressor let n_rows = 10, n_cols = 10, - A = ones(n_rows, n_cols) + A = ones(n_rows, n_cols), + B = ones(n_rows - 1, n_cols) compressor_recipe = complete_compressor(Identity(), A) - update_compressor!(compressor_recipe) + update_compressor!(compressor_recipe, B) # test values and types @test compressor_recipe.cardinality == Left() - @test compressor_recipe.n_rows == n_rows - @test compressor_recipe.n_cols == n_cols + @test compressor_recipe.n_rows == n_rows - 1 + @test compressor_recipe.n_cols == n_rows - 1 end + # try updating a right compressor + let n_rows = 10, + n_cols = 10, + A = ones(n_rows, n_cols), + B = ones(n_rows, n_cols - 1) + + compressor_recipe = complete_compressor(Identity(cardinality = Right()), A) + update_compressor!(compressor_recipe, B) + # test values and types + @test compressor_recipe.cardinality == Right() + @test compressor_recipe.n_rows == n_cols - 1 + @test compressor_recipe.n_cols == n_cols - 1 + end + end @testset "Identity: Left multiplication" begin From 94ffc2f59dda13abf8005d6c39629f2e2e20384e Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 10 Oct 2025 16:08:38 +0100 Subject: [PATCH 09/14] modified compressor to allow for sketched LU --- .../selection_techniques/lupp.jl | 46 +++++- .../selection_techniques/lupp.jl | 152 +++++++++++++----- 2 files changed, 150 insertions(+), 48 deletions(-) diff --git a/src/Approximators/selection_techniques/lupp.jl b/src/Approximators/selection_techniques/lupp.jl index faf26d8b..f5d85c8d 100644 --- a/src/Approximators/selection_techniques/lupp.jl +++ b/src/Approximators/selection_techniques/lupp.jl @@ -5,10 +5,25 @@ A `Selector` that implements LU with partial pivoting for selecting column indic matrix. # Fields -- None +- `compressor::Compressor`, the compression technique that will applied to the matrix, + before selecting indices. + +# Constructor + LUPP(;compressor = Identity()) + +## Keywords +- `compressor::Compressor`, the compression technique that will applied to the matrix, + before selecting indices. Defaults the `Identity` compressor. + +## Returns +- Will return a `Selector` object. """ mutable struct LUPP <: Selector - + compressor::Compressor +end + +function LUPP(;compressor = Identity()) + LUPP(compressor) end """ @@ -18,17 +33,24 @@ A `SelectorRecipe` that contains all the necessary preallocations for selecting indices from a matrix using LU with partial pivoting. # Fields -- None +- `compressor::Compressor`, the compression technique that will applied to the matrix, + before selecting indices. +- `SA::AbstractMatrix`, a buffer matrix for storing the sketched matrix. """ mutable struct LUPPRecipe <: SelectorRecipe - + compressor::CompressorRecipe + SA::AbstractMatrix end -function complete_selector(ingredients::LUPP) - return LUPPRecipe() +function complete_selector(ingredients::LUPP, A::AbstractMatrix) + compressor = complete_compressor(ingredients.compressor, A) + n_rows, n_cols = size(compressor) + SA = Matrix{eltype(A)}(undef, n_rows, n_cols) + return LUPPRecipe(compressor, SA) end function update_selector!(selector::LUPPRecipe) + update_compressor!(selector.compressor) return nothing end @@ -54,9 +76,19 @@ function select_indices!( ) ) end + + if n_idx > selector.compressor.n_rows + throw( + DimensionMismatch( + "Must select fewer indices then the `compression_dim`." + ) + ) + + end + mul!(selector.SA, selector.compressor, A) # because LUPP selects rows and selectors select columns we need to pivot on A' - p = lu!(A').p + p = lu!(selector.SA').p idx[start_idx:start_idx + n_idx - 1] = p[1:n_idx] return nothing end diff --git a/test/Approximators/selection_techniques/lupp.jl b/test/Approximators/selection_techniques/lupp.jl index 5505d8e4..c9996924 100644 --- a/test/Approximators/selection_techniques/lupp.jl +++ b/test/Approximators/selection_techniques/lupp.jl @@ -7,45 +7,69 @@ import LinearAlgebra: mul! @testset "LUPP Tests" begin @testset "LUPP" begin - supertype(LUPP) == Selector + @test supertype(LUPP) == Selector # test fieldnames and types - fieldnames(LUPP) == () - fieldtypes(LUPP) == () + @test fieldnames(LUPP) == (:compressor,) + @test fieldtypes(LUPP) == (Compressor,) # Test Constructor let sel = LUPP() @test typeof(sel) == LUPP + @test typeof(sel.compressor) == Identity + end + + let + sel = LUPP(compressor = SparseSign()) + @test typeof(sel) == LUPP + @test typeof(sel.compressor) == SparseSign end end @testset "LUPPRecipe" begin - supertype(LUPPRecipe) == SelectorRecipe + @test supertype(LUPPRecipe) == SelectorRecipe # test fieldnames and types - fieldnames(LUPPRecipe) == () - fieldtypes(LUPPRecipe) == () + @test fieldnames(LUPPRecipe) == (:compressor, :SA) + @test fieldtypes(LUPPRecipe) == (CompressorRecipe, AbstractMatrix) + end + @testset "LUPP: Complete Selector" begin + # test with identity compressor + let n_rows = 2, + n_cols = 2, + A = zeros(n_rows, n_cols), + sel = LUPP() - # Test Constructor - let - sel = LUPPRecipe() - @test typeof(sel) == LUPPRecipe + sel_rec = complete_selector(sel, A) + @test typeof(sel_rec) == LUPPRecipe + @test typeof(sel_rec.compressor) == IdentityRecipe{Left} + @test typeof(sel_rec.SA) <: AbstractMatrix + @test size(sel_rec.SA) == (n_rows, n_cols) end - end + # test with gaussian compressor + let n_rows = 3, + n_cols = 3, + A = zeros(n_rows, n_cols), + comp_dim = 2, + sel = LUPP(compressor=Gaussian(compression_dim = comp_dim)) - @testset "LUPP: Complete Selector" begin - let sel = LUPP() - sel_rec = complete_selector(sel) + sel_rec = complete_selector(sel, A) @test typeof(sel_rec) == LUPPRecipe + @test typeof(sel_rec.compressor) == GaussianRecipe{Left} + @test typeof(sel_rec.SA) <: AbstractMatrix + @test size(sel_rec.SA) == (comp_dim, n_cols) end end @testset "LUPP: Update Selector" begin - let sel_rec = complete_selector(LUPP()) + let n_rows = 2, + n_cols = 2, + A = zeros(n_rows, n_cols), + sel_rec = complete_selector(LUPP(compressor = Gaussian()), A) update_selector!(sel_rec) @test typeof(sel_rec) == LUPPRecipe end @@ -53,30 +77,11 @@ import LinearAlgebra: mul! end @testset "LUPP: Select Indices" begin - A = [0 1 0; - 0 0 2; - 3 0 0] - # test selecting only one index - let A = deepcopy(A), - idx = zeros(Int64, 3), - start_idx = 2, - n_idx = 1 - - select_indices!(idx, LUPPRecipe(), A, n_idx, start_idx) - @test idx[2] == 2 - end - - # test selecting three indices - let A = deepcopy(A), - idx = zeros(Int64, 3), - start_idx = 1, - n_idx = 3 - - select_indices!(idx, LUPPRecipe(), A, n_idx, start_idx) - @test idx == [2; 3; 1] - end - + A = [0.0 10.0 0.0; + 0.0 0.0 20.0; + 30.0 0.0 0.0] # test the error checking + # start with checking n_idx not being larger than number of columns let A = A, idx = zeros(Int64, 3), start_idx = 1, @@ -84,13 +89,14 @@ import LinearAlgebra: mul! @test_throws DimensionMismatch select_indices!( idx, - LUPPRecipe(), + complete_selector(LUPP(), A), A, n_idx, start_idx ) end - + + # check that n_idx will not go over index vector let A = A, idx = zeros(Int64, 3), start_idx = 3, @@ -98,13 +104,77 @@ import LinearAlgebra: mul! @test_throws DimensionMismatch select_indices!( idx, - LUPPRecipe(), + complete_selector(LUPP(), A), + A, + n_idx, + start_idx + ) + end + + # check that n_idx is not larger than the compression_dim + let A = A, + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 3 + + @test_throws DimensionMismatch select_indices!( + idx, + complete_selector( + LUPP(compressor = Gaussian(compression_dim = 2)), + A + ), A, n_idx, start_idx ) end + # Test with identity compressor + # notice that in this selection it has nothing to do with column norm + # test selecting only one index + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 2, + n_idx = 1, + sel_rec = complete_selector(LUPP(), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx[2] == 2 + end + + # test selecting three indices + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 3, + sel_rec = complete_selector(LUPP(), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx == [2; 3; 1] + end + + # test with gaussian compressor + # test selecting only one index + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 2, + n_idx = 1, + sel_rec = complete_selector(LUPP(compressor = Gaussian()), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx[2] == 1 + end + + # test selecting two indices + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 2, + sel_rec = complete_selector(LUPP(compressor = Gaussian()), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx == [1; 3; 0] + end end end From 7c1757cb925ff8bbb3cb3bbbe478b0ec798242d5 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 10 Oct 2025 16:12:50 +0100 Subject: [PATCH 10/14] appropriately named selector directory --- src/Approximators/{selection_techniques => Selectors}/lupp.jl | 0 test/Approximators/{selection_techniques => Selectors}/lupp.jl | 0 test/runtests.jl | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/Approximators/{selection_techniques => Selectors}/lupp.jl (100%) rename test/Approximators/{selection_techniques => Selectors}/lupp.jl (100%) diff --git a/src/Approximators/selection_techniques/lupp.jl b/src/Approximators/Selectors/lupp.jl similarity index 100% rename from src/Approximators/selection_techniques/lupp.jl rename to src/Approximators/Selectors/lupp.jl diff --git a/test/Approximators/selection_techniques/lupp.jl b/test/Approximators/Selectors/lupp.jl similarity index 100% rename from test/Approximators/selection_techniques/lupp.jl rename to test/Approximators/Selectors/lupp.jl diff --git a/test/runtests.jl b/test/runtests.jl index 762dbe40..e9c8017d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,7 +16,7 @@ directs = "Compressors/", "Approximators/", "Approximators/RangeApproximator/", - "Approximators/selection_techniques/", + "Approximators/Selectors/", "Solvers/", "Solvers/helpers/", "Solvers/ErrorMethods/", From 30b0a3ac4167d1de9019339a2435489ad4932de1 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 10 Oct 2025 16:19:08 +0100 Subject: [PATCH 11/14] corrected misspecified link --- src/Approximators/Selectors.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Approximators/Selectors.jl b/src/Approximators/Selectors.jl index 8e7f5a1d..cc2ea51a 100644 --- a/src/Approximators/Selectors.jl +++ b/src/Approximators/Selectors.jl @@ -144,4 +144,4 @@ function select_indices!( end # Include the selector files -include("selection_techniques/lupp.jl") +include("Selectors/lupp.jl") From 6ae245cb8956dfc541873cd286a36dea48525f8f Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 10 Oct 2025 16:36:47 +0100 Subject: [PATCH 12/14] updated the qrcp function to allow for sketching --- .../qrcp.jl | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) rename src/Approximators/{selection_techniques => Selectors}/qrcp.jl (51%) diff --git a/src/Approximators/selection_techniques/qrcp.jl b/src/Approximators/Selectors/qrcp.jl similarity index 51% rename from src/Approximators/selection_techniques/qrcp.jl rename to src/Approximators/Selectors/qrcp.jl index db184d58..24309960 100644 --- a/src/Approximators/selection_techniques/qrcp.jl +++ b/src/Approximators/Selectors/qrcp.jl @@ -5,10 +5,25 @@ A `Selector` that implements QR with column norm pivoting for selecting column i a matrix. # Fields -- None +- `compressor::Compressor`, the compression technique that will applied to the matrix, + before selecting indices. + +# Constructor + QRCP(;compressor = Identity()) + +## Keywords +- `compressor::Compressor`, the compression technique that will applied to the matrix, + before selecting indices. Defaults the `Identity` compressor. + +## Returns +- Will return a `QRCP` object. """ mutable struct QRCP <: Selector + compressor::Compressor +end +function QRCP(;compressor = Identity()) + QRCP(compressor) end """ @@ -18,17 +33,24 @@ A `SelectorRecipe` that contains all the necessary preallocations for selecting indices from a matrix using QR with column norm pivoting. # Fields -- None +- `compressor::Compressor`, the compression technique that will applied to the matrix, + before selecting indices. +- `SA::AbstractMatrix`, a buffer matrix for storing the sketched matrix. """ mutable struct QRCPRecipe <: SelectorRecipe - + compressor::CompressorRecipe + SA::AbstractMatrix end function complete_selector(ingredients::QRCP) - return QRCPRecipe() + compressor = complete_compressor(ingredients.compressor, A) + n_rows, n_cols = size(compressor) + SA = Matrix{eltype(A)}(undef, n_rows, n_cols) + return QRCPRecipe(compressor, SA) end function update_selector!(selector::QRCPRecipe) + update_compressor!(selector.compressor) return nothing end @@ -54,7 +76,17 @@ function select_indices!( ) ) end + + if n_idx > selector.compressor.n_rows + throw( + DimensionMismatch( + "Must select fewer indices then the `compression_dim`." + ) + ) + + end + mul!(selector.SA, selector.compressor, A) p = qr!(A, ColumnNorm()).p # store newly selected indices at inputted index storage points idx[start_idx:start_idx + n_idx - 1] = p[1:n_idx] From ea75070756b32f961e6b2412bb9bbca6dbdf3e2d Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Fri, 10 Oct 2025 16:53:44 +0100 Subject: [PATCH 13/14] added sketching capabilities to qr --- src/Approximators/Selectors/qrcp.jl | 2 +- .../selection_techniques/qrcp.jl | 112 ------------------ 2 files changed, 1 insertion(+), 113 deletions(-) delete mode 100644 test/Approximators/selection_techniques/qrcp.jl diff --git a/src/Approximators/Selectors/qrcp.jl b/src/Approximators/Selectors/qrcp.jl index 24309960..3e40cefb 100644 --- a/src/Approximators/Selectors/qrcp.jl +++ b/src/Approximators/Selectors/qrcp.jl @@ -42,7 +42,7 @@ mutable struct QRCPRecipe <: SelectorRecipe SA::AbstractMatrix end -function complete_selector(ingredients::QRCP) +function complete_selector(ingredients::QRCP, A::AbstractMatrix) compressor = complete_compressor(ingredients.compressor, A) n_rows, n_cols = size(compressor) SA = Matrix{eltype(A)}(undef, n_rows, n_cols) diff --git a/test/Approximators/selection_techniques/qrcp.jl b/test/Approximators/selection_techniques/qrcp.jl deleted file mode 100644 index 17fd55b8..00000000 --- a/test/Approximators/selection_techniques/qrcp.jl +++ /dev/null @@ -1,112 +0,0 @@ -module QRCP_tests -using Test -using ..FieldTest -using ..ApproxTol -using RLinearAlgebra -import LinearAlgebra: mul! - -@testset "QRCP Tests" begin - @testset "QRCP" begin - supertype(QRCP) == Selector - - # test fieldnames and types - fieldnames(QRCP) == () - fieldtypes(QRCP) == () - - # Test Constructor - let - sel = QRCP() - @test typeof(sel) == QRCP - end - - end - - @testset "QRCPRecipe" begin - supertype(QRCPRecipe) == SelectorRecipe - # test fieldnames and types - fieldnames(QRCPRecipe) == () - fieldtypes(QRCPRecipe) == () - - - # Test Constructor - let - sel = QRCPRecipe() - @test typeof(sel) == QRCPRecipe - end - - end - - @testset "QRCP: Complete Selector" begin - let sel = QRCP() - sel_rec = complete_selector(sel) - @test typeof(sel_rec) == QRCPRecipe - end - - end - - @testset "QRCP: Update Selector" begin - let sel_rec = complete_selector(QRCP()) - update_selector!(sel_rec) - @test typeof(sel_rec) == QRCPRecipe - end - - end - - @testset "QRCP: Select Indices" begin - A = [0 1 0; - 0 0 2; - 3 0 0] - # test selecting only one index - let A = deepcopy(A), - idx = zeros(Int64, 3), - start_idx = 2, - n_idx = 1 - - select_indices!(idx, QRCPRecipe(), A, n_idx, start_idx) - @test idx[2] == 1 - end - - # test selecting three indices - let A = deepcopy(A), - idx = zeros(Int64, 3), - start_idx = 1, - n_idx = 3 - - select_indices!(idx, QRCPRecipe(), A, n_idx, start_idx) - @test idx == [1; 3; 2] - end - - # test the error checking - let A = A, - idx = zeros(Int64, 3), - start_idx = 1, - n_idx = 4 - - @test_throws DimensionMismatch select_indices!( - idx, - QRCPRecipe(), - A, - n_idx, - start_idx - ) - end - - let A = A, - idx = zeros(Int64, 3), - start_idx = 3, - n_idx = 2 - - @test_throws DimensionMismatch select_indices!( - idx, - QRCPRecipe(), - A, - n_idx, - start_idx - ) - end - - end - -end - -end From 5a0b090c0c0675446786cb4ac4d381abe7d22499 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Sun, 12 Oct 2025 19:56:51 +0100 Subject: [PATCH 14/14] added qrcp test file --- test/Approximators/Selectors/qrcp.jl | 183 +++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 test/Approximators/Selectors/qrcp.jl diff --git a/test/Approximators/Selectors/qrcp.jl b/test/Approximators/Selectors/qrcp.jl new file mode 100644 index 00000000..05daeae2 --- /dev/null +++ b/test/Approximators/Selectors/qrcp.jl @@ -0,0 +1,183 @@ +module QRCP_tests +using Test +using ..FieldTest +using ..ApproxTol +using RLinearAlgebra +import LinearAlgebra: mul! + +@testset "QRCP Tests" begin + @testset "QRCP" begin + @test supertype(QRCP) == Selector + + # test fieldnames and types + @test fieldnames(QRCP) == (:compressor,) + @test fieldtypes(QRCP) == (Compressor,) + + # Test Constructor + let + sel = QRCP() + @test typeof(sel) == QRCP + @test typeof(sel.compressor) == Identity + end + + let + sel = QRCP(compressor = SparseSign()) + @test typeof(sel) == QRCP + @test typeof(sel.compressor) == SparseSign + end + + end + + @testset "QRCPRecipe" begin + @test supertype(QRCPRecipe) == SelectorRecipe + # test fieldnames and types + @test fieldnames(QRCPRecipe) == (:compressor, :SA) + @test fieldtypes(QRCPRecipe) == (CompressorRecipe, AbstractMatrix) + end + + @testset "QRCP: Complete Selector" begin + # test with identity compressor + let n_rows = 2, + n_cols = 2, + A = zeros(n_rows, n_cols), + sel = QRCP() + + sel_rec = complete_selector(sel, A) + @test typeof(sel_rec) == QRCPRecipe + @test typeof(sel_rec.compressor) == IdentityRecipe{Left} + @test typeof(sel_rec.SA) <: AbstractMatrix + @test size(sel_rec.SA) == (n_rows, n_cols) + end + + # test with gaussian compressor + let n_rows = 3, + n_cols = 3, + A = zeros(n_rows, n_cols), + comp_dim = 2, + sel = QRCP(compressor=Gaussian(compression_dim = comp_dim)) + + sel_rec = complete_selector(sel, A) + @test typeof(sel_rec) == QRCPRecipe + @test typeof(sel_rec.compressor) == GaussianRecipe{Left} + @test typeof(sel_rec.SA) <: AbstractMatrix + @test size(sel_rec.SA) == (comp_dim, n_cols) + end + + end + + @testset "QRCP: Update Selector" begin + let n_rows = 2, + n_cols = 2, + A = zeros(n_rows, n_cols), + sel_rec = complete_selector(QRCP(compressor = Gaussian()), A) + update_selector!(sel_rec) + @test typeof(sel_rec) == QRCPRecipe + end + + end + + @testset "QRCP: Select Indices" begin + A = [0.0 10.0 0.0; + 0.0 0.0 20.0; + 30.0 0.0 0.0] + # test the error checking + # start with checking n_idx not being larger than number of columns + let A = A, + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 4 + + @test_throws DimensionMismatch select_indices!( + idx, + complete_selector(QRCP(), A), + A, + n_idx, + start_idx + ) + end + + # check that n_idx will not go over index vector + let A = A, + idx = zeros(Int64, 3), + start_idx = 3, + n_idx = 2 + + @test_throws DimensionMismatch select_indices!( + idx, + complete_selector(QRCP(), A), + A, + n_idx, + start_idx + ) + end + + # check that n_idx is not larger than the compression_dim + let A = A, + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 3 + + @test_throws DimensionMismatch select_indices!( + idx, + complete_selector( + QRCP(compressor = Gaussian(compression_dim = 2)), + A + ), + A, + n_idx, + start_idx + ) + end + + # Test with identity compressor + # notice that in this selection it has nothing to do with column norm + # test selecting only one index + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 2, + n_idx = 1, + sel_rec = complete_selector(QRCP(), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx[2] == 1 + end + + # test selecting three indices + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 3, + sel_rec = complete_selector(QRCP(), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx == [1; 3; 2] + end + + # test with gaussian compressor + # test selecting only one index + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 2, + n_idx = 1, + sel_rec = complete_selector(QRCP(compressor = Gaussian()), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx[2] == 1 + end + + # test selecting two indices + let A = deepcopy(A), + idx = zeros(Int64, 3), + start_idx = 1, + n_idx = 2, + sel_rec = complete_selector(QRCP(compressor = Gaussian()), A) + + select_indices!(idx, sel_rec, A, n_idx, start_idx) + @test idx == [1; 3; 0] + end + + end + +end + +end