From 53c4e557ba9e4abe715250a0e0633a3fb79e551e Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 10 Feb 2025 19:23:24 +0000 Subject: [PATCH 01/42] start of developer documents file --- docs/make.jl | 10 +- docs/src/dev/checklists.md | 42 ++ docs/src/dev/contributing.md | 37 ++ docs/src/dev/design.md | 757 +++++++++++++++++++++++++++++++++++ docs/src/dev/style_guide.md | 60 +++ 5 files changed, 904 insertions(+), 2 deletions(-) create mode 100644 docs/src/dev/checklists.md create mode 100644 docs/src/dev/contributing.md create mode 100644 docs/src/dev/design.md create mode 100644 docs/src/dev/style_guide.md diff --git a/docs/make.jl b/docs/make.jl index 745c324c..7c7ce1e2 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -21,17 +21,23 @@ makedocs( "Solvers Overview" => "api/solvers.md", "Solver Sub-routines" => [ "SubSolvers" => "api/sub_solvers.md", - "SolverErrors" => "api/solver_errors.md", + "SolverErrors" => "api/solver_error.md", "Loggers" => "api/loggers.md" ], ], "Approximators" => [ "Approximators Overview" => "api/approximators.md", "Approximator Sub-routines" => [ - "ApproximatorErrors" => "api/approximator_errors.md" + "ApproximatorErrors" => "api/approximator_error.md" ], ], ], + "Contributing" => [ + "Contributing Overview" => "dev/contributing.md", + "Design of Library" => "dev/design.md", + "Checklists" => "dev/checklists.md", + "Style Guide" => "dev/style_guide.md", + ], "References" => "references.md", ] ) diff --git a/docs/src/dev/checklists.md b/docs/src/dev/checklists.md new file mode 100644 index 00000000..66eabd54 --- /dev/null +++ b/docs/src/dev/checklists.md @@ -0,0 +1,42 @@ +# Development Checklists + +```@contents +Pages=["checklists.md"] +``` +The purpose of this page is to maintain checklists of tasks to complete when adding new +methods to the library. These checklists are organized by method. + +## Compressors +If you are implementing a compression method for the library, make sure you have completed +the following steps before making a pull request. + +``` +1. Implementation +- [ ] Create a file in the directory `Compressors`. +- [ ] Create a Compressor structure with n_rows and n_cols as well as other user-controlled +parameters. +- [ ] Create a constructor with keyword default values for your struct. +- [ ] Create a CompressorRecipe structure that uses the parameters from the Compressor +structure to preallocate memory. +- [ ] Create a `complete_compressor` function that takes as inputs of the Compressor, A, x,b +and returns a CompressorRecipe +- [ ] Create a `update_compressor!` function that generates new random values for a random +components of the Compressor +- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the left +- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the right +- [ ] a 5 input `mul!` function for applying a compressor to a vector +- [ ] a 5 input `mul!` function for applying the adjoint of the compressor to a vector +- [ ] Add an include("Compressors/[YOURFILE]") at bottom of page +- [ ] Add import statements to src/RLinearAlgebra.jl with any functions from other packages +that you use. +- [ ] Add your Compressor and CompressorRecipe to src/RLinearAlgebra.jl. +- [ ] Add your Compressor and CompressorRecipe to docs/src/api/compressors.md +under the appropiate heading. +- [ ] Add a procedural test to test/linear_samplers. Be sure to check that the functions +work as intended and all warnings/assertions are displayed. +2. Pull request +- [ ] Give a specific title to pull request. +- [ ] Lay out the features added to the pull request. +- [ ] Tag two people to review your pull request. +``` + diff --git a/docs/src/dev/contributing.md b/docs/src/dev/contributing.md new file mode 100644 index 00000000..93fdcc01 --- /dev/null +++ b/docs/src/dev/contributing.md @@ -0,0 +1,37 @@ +# Contributing + +```@contents +Pages=["contributing.md"] +``` +## Improve the documentation +If you have ever been reading the documentation and been unsure about a description or + instruction, the documentation can be improved. Since you are the person finding this + gap in the documentation, you are also the person in the best position to fix it. + +The documentation is written in Markdown and built using + [Documenter.jl](https://documenter.juliadocs.org/stable/man/guide/). + The source code for all the docs is + [here](https://github.com/numlinalg/RLinearAlgebra.jl/tree/master/docs). + + +## Bug Reports +If you find a bug in our software we would love to know about it. You can make an issue +relating to a bug report [here](https://github.com/numlinalg/RLinearAlgebra.jl/issues/new?assignees=dmaldona%2C+npritch928%2C+vp314&labels=bug&projects=&template=bug_report.md&title=). + +## Feature Requests +Have a new method that you think would be a valuable addition to the package? + Make a [feature request](https://github.com/numlinalg/RLinearAlgebra.jl/issues/new?assignees=dmaldona%2C+npritch928%2C+vp314&labels=enhancement&projects=&template=feature_request.md&title=) + and let us know about it. Please provide details of why this feature would be valuable + and if possible point us to resources to help us better understand the feature. + +## Contribute code + +You can also contribute code to `RLinearAlgebra.jl`. Before contributing make sure that you + are familiar with [Git](https://git-scm.com/book/en/v2), + [GitHub](https://docs.github.com/en/get-started/start-your-journey/hello-world), + [Julia package development](https://docs.julialang.org/en/v1/stdlib/Pkg/#Developing-packages-1), + and the [Design](@ref) of the RLinearAlgebra.jl. + Once you are familiar with these items you can contribute to `RLinearAlgebra.jl` by + following the steps laid out in the + [JUMP](https://jump.dev/JuMP.jl/stable/developers/contributing/) guide. + diff --git a/docs/src/dev/design.md b/docs/src/dev/design.md new file mode 100644 index 00000000..2b8f0dbc --- /dev/null +++ b/docs/src/dev/design.md @@ -0,0 +1,757 @@ +# Design +## Overview Library Goals +RLinearAlgebra.jl implements randomized numerical linear algebra (RNLA) routines for +two tasks: (1) solving a matrix equations and (2) forming a low-rank approximation +to matrices. The primary tool Randomized Linear Algebra uses to accomplish these tasks is +multiplying the large matrix system by a smaller randomized matrix to compress the +large matrix. In the literature this process if often referred to as sampling or sketching, +RLinearAlgebra.jl refers to this process as compression. + +The library is organized with main techniques falling into one of three types: +Approximators, Compressors, and Solvers. Solvers feature their own set of +sub-techniques: Loggers, SolverErrors, and SubSolvers that facilitate solving. Approximators +have only one set of sub-techniques known as ApproximatorErrors. + +RLinearAlgebra.jl is designed so that the codebase has a good balance between efficiency +and modularity. RLinearAlgebra.jl tries to achieve these goals by introducing two +structures, one that contains user-controlled parameters which takes the form of +`[Technique]` and a second that is used by the technique to execute the techniques because it +contains the necessary preallocated memory and is known as a `[Technique]Recipe`. + +We can see an example of the difference between the two structures when considering an +implementation of compression with Gaussian matrices. In this implementation we wish to +have the user specify a compression dimension and size without having to know the dimension +of the matrix the sketch is applied to. The `Gaussian` structure facilitates this by having +two fields `n_rows` and `n_cols` with the default for both being zero. When the user then +constructs this structure the can specify the dimension and direction of the compression +by specifying the number of rows or columns they wish for the compression matrix to have. +If the user wanted a Gaussian matrix with 3 rows they would call +`Gaussian(n_rows = 3)`. We then turn this dimensional information into an usable compression +matrix by using the `complete_compressor` function to form a `GaussianRecipe`. This +`GaussianRecipe` contains Fields `n_rows = 3`, `n_cols` set to be the number of rows in the +compressor, and a Gaussian matrix of the size specified by `n_rows` and `n_cols`. + +Once a `[Technique]Recipe` has been created, this data structure can then be used to +execute a particular technique. The command to execute each technique varies by the class +of techniques, as such we lay out the specifics for each type of techniques in the following +section. + +## Technique Types +Overall, there are three top-level technique types: (1) Compressors, (2) Solvers, and +(3) Approximators, with the latter two also having additional sets of technique types +used in the execution of the top-level techniques. We group the discussions of the technique +classes by top-level technique. + +### Compressors +When implementing a Compressor, RLinearAlgebra requires an mutable `Compressor` +structure, a mutable `CompressorRecipe` structure, a `complete_compressor` function, a +`update_compressor!` function, and for five input mul! functions (one for applying the +compressor to vectors, one for applying the adjoint of a compressor to a vector, +and two for applying the compressor to matrices). + +#### Compressor Structure +Every compression technique needs a place to store user-controlled parameters. +This will be accomplished by the immutable Compressor structure. +We present an example structure used for the Sparse Sign technique. + +``` +struct SparseSign <: Compressor + n_rows::Int64 + n_cols::Int64 + nnz::Int64 +end +``` +You will first notice that `n_rows` and `n_cols` are fields present in the Compressor, +these fields allow for the user to specify either the number of rows or the number of +columns they wish the compressor to have. **Both `n_rows` and `n_cols` are required for +every Compressor structure.** Beyond those fields the technique will dictate the other +parameters that should be made available to the user. In addition to the structure, there +should be a **constructor for the structure that accepts keyword inputs for each +field of the Compressor structure.** For example in the `SparseSign` case we define, +``` +function SparseSign(;n_rows::Int64 = 0, n_cols::Int64 = 0, nnz::Int64 = 8) + # Partially construct the sparse sign datatype + return SparseSign(n_rows, n_cols, nnz) +end +``` +#### CompressorRecipe Structure +To form the compressor from the user-inputted information, we need information about the +linear system. Once this information is attained preallocations of the necessary memory can +be done. These preallocations are then stored in the `CompressorRecipe` structure. Because +this structure has all of the preallocated memory for applying the compression technique it +is this structure that can be applied to matrices and vectors. + +As example, we have included the CompressorRecipe for the sparse sign compressor. This +structure importantly includes the size of the compressor in the `n_rows` and `n_cols` +fields +``` +mutable struct SparseSignRecipe <: CompressorRecipe + n_rows::Int64 + n_cols::Int64 + max_idx::Int64 + nnz::Int64 + scale::Float64 + idxs::Vector{Int64} + signs::Vector{Bool} +end +``` +Here we have the **required `n_rows` and `n_cols`** fields for all compressors. The +remaining fields are specific to the sparse sign compression technique. + +#### complete_compressor +To create the CompressorRecipe from linear system information and the user-controlled +parameters, we use the function `complete_compressor(::Compressor, ::AbsractMatrix)`, +if vector information is required we can also define +`complete_compressor(::Compressor, ::AbsractMatrix, ::AbstractVector)`. +An example of how this is done for the sparse sign case can be seen below. +``` +function complete_compressor(sparse_info::SparseSign, A::AbstractMatrix) + n_rows = sparse_info.n_rows + n_cols = sparse_info.n_cols + # FInd the zero dimension and set it to be the dimension of A + if n_rows == 0 && n_cols == 0 + # by default we will compress the row dimension to size 2 + n_cols = size(A, 1) + n_rows = 2 + # correct these sizes + initial_size = max(n_rows, n_cols) + sample_size = min(n_rows, n_cols) + elseif n_rows == 0 && n_cols > 0 + # Assuming that if n_rows is not specified we compress column dimension + n_rows = size(A, 2) + # If the user specifies one size as nonzero that is the sample size + sample_size = n_cols + initial_size = n_rows + elseif n_rows > 0 && n_cols == 0 + n_cols = size(A, 1) + sample_size = n_rows + initial_size = n_cols + else + if n_rows == size(A, 2) + initial_size = n_rows + sample_size = n_cols + elseif n_cols == size(A, 2) + initial_size = n_cols + sample_size == n_rows + else + @assert false "Either you inputted row or column dimension must match \\ + the column or row dimension of the matrix." + end + end + + nnz = (sparse_info.nnz == 8) ? min(8, sample_size) : sparse_info.nnz + @assert nnz <= sample_size "Number of non-zero indices, $nnz, must be less than \\ + compression dimension, $sample_size." + idxs = Vector{Int64}(undef, nnz * initial_size) + start = 1 + for i in 1:initial_size + # every grouping of nnz entries corresponds to each row/column in sample + stop = start + nnz - 1 + # Sample indices from the intial_size + @views sample!( + 1:sample_size, + idxs[start:stop], + replace = false, + ordered = true + ) + start = stop + 1 + end + + # Store signs as a boolean to save memory + signs = bitrand(nnz * initial_size) + scale = 1 / sqrt(nnz) + + return SparseSignRecipe(n_rows, n_cols, sample_size, nnz, scale, idxs, signs) +end +``` +The `complete_compressor` function assumes that if the user inputs only `n_rows` or `n_cols` +in the Compressor structure this is the desired compression dimension. If they input +neither, it creates a compressor with a compression dimension of two and +if the input both and neither is consistent with +a dimension of the inputted linear system it returns an error. Otherwise, it assumes the +inconsistent dimension is the compression dimension. Once the sizes of the compressor have +been determined it next allocates the memory necessary for storing the initial compressor +and packages these allocations with the size information into the `CompressorRecipe`. + +#### update_compressor! +To generate a new version of the compressor we can call the function `update_compressor!`, +this function simply changes the random components of the CompressorRecipe. In the sparse +sign case this means updating the nonzero indices and the signs as can be seen in the +following example code. +``` +function update_compressor!( + S::SparseSignRecipe, + A::AbstractMatrix, + b::AbstractVector, + x::AbstractVector + ) + # Sample_size will be the minimum of the two size dimensions of `S` + sample_size = min(S.n_rows, S.n_cols) + initial_size = max(S.n_rows, S.n_cols) + start = 1 + for i in 1:sample_size + # every grouping of nnz entries corresponds to each row/column in sample + stop = start + S.nnz - 1 + # Sample indices from the intial_size + @views sample!( + 1:sample_size, + S.idxs[start:stop], + replace = false, + ordered = true + ) + start = stop + 1 + end + # There is no inplace update of bitrand and using sample is slower + S.signs .= bitrand(S.nnz * initial_size) + return +end +``` + +#### mul! +The last pieces of code that every compression technique requires are the `mul!` functions. +For these functions we follow the conventions laid out in the LinearAlgebra library where +there are five inputs (C, A, S, alpha, beta) and it outputs `C = beta * C + alpha * A * S`. +The `mul!` functions that should be implemented are two for applying the compression matrix +to vectors, one in standard orientation and one for when the adjoint of the compressor is +applied to the vector. Additionally, two `mul!` functions should be implemented for when +the compression matrix is applied to a matrix, one for when the compression matrix, S, is +applied from the left, i.e. AS, and one for when the compression matrix is applied from the +right, i.e. SA. + +### Solvers +A Solver technique is any technique that aims to find a vector ``x`` such that either +``Ax = b`` or ``x = \\min_u \\|A u - b\\|_2^2``. Solvers rely on compression techniques, +logging techniques, error techniques, and sub-solver techniques. We first discuss +implementation requirements for the sub-techniques and then discuss how we can use these +when creating a solver structure. + +#### Loggers +Loggers are structures with two goals (1) log a progress value produced by an error metric +and (2) evaluate whether that error is sufficient for stopping. The user controlled inputs +for a logging technique are contained in the Logger structure. + +##### Logger +The `Logger` structure is where the user inputs any information required +to logging progress and stopping the method. **The Logger is required to have a field for +`max_it`, `threshold_info`, and `stopping_criterion`.** The `max_it` field is a field +for the maximum number of iterations of the method. The `stopping_criterion` is a field that +contains a function that returns a stopping decision based on the information in the +`LoggerRecipe` and the `Tuple` of information supplied by the user in the `threshold_info` +field. It is important to note that constructors for these techniques should have keyword +inputs with predefined defaults. We present an example of the Logger structure for a +`BasicLogger` below +``` +struct BasicLogger <: Logger + max_it::Int64 + collection_rate::Int64 + threshold_info::Union{Float64, Tuple} + stopping_criterion::Function +end +``` + +Aside from the required parameters the `BasicLogger` also features a `collection rate` +parameter to allow the user to specify how often they wish for the `LoggerRecipe` to log +progress. + +##### LoggerRecipe +The `LoggerRecipe` will contain the user-controlled parameters from the `Logger` as well as +memory for storing the logged information. All `LoggerRecipes` +**must contain a `max_it` field and a `converged` field,** where the `converged` field is a +boolean indicating if the method has converged. An example of a `LoggerRecipe` is presented +below for the `BasicLoggerRecipe`. This Logger has a vector for the history of the progress +metric, a field whose inclusion is strongly suggested. It also has `record_location` field +to keep track of where the next observed progress estimate should be placed depending +on the `collection_rate`. +``` +mutable struct BasicLoggerRecipe{F} <: LoggerRecipe where F<:Function + max_it::Int64 + err::Float64 + threshold_info::Union{Float64, Tuple} + iteration::Int64 + record_location::Int64 + collection_rate::Int64 + converged::Bool + stopping_criterion::F + hist::Vector{Float64} +end +``` + +##### complete_logger +As with the other techniques, `complete_logger` takes a `Logger` data structure and +performs the appropriate allocations to generate a `LoggerRecipe`. An example of this +function for BasicLogger is presented below. +``` +function complete_logger(logger::BasicLogger, A::AbstractMatrix) + # We will run for a number of iterations equal to 3 itmes the number of rows if maxit is + # not set + max_it = logger.max_it == 0 ? 3 * size(A, 1) : logger.max_it + + max_collection = Int(ceil(max_it / logger.collection_rate)) + # use one more than max it form collection + hist = zeros(max_collection + 1) + return BasicLoggerRecipe{typeof(logger.stopping_criterion)}(max_it, + 0.0, + logger.threshold_info, + 1, + 1, + logger.collection_rate, + false, + logger.stopping_criterion, + hist + ) +end +``` + +##### update_logger! +As with the compressors `update_logger!` performs an in-place update of the +LoggerRecipe using the inputted progress metric and iteration of the method. An example of +the `update_logger!` function for the BasicLoggerRecipe is included below. +``` +function update_logger!(logger::BasicLoggerRecipe, err::Float64, iteration::Int64) + logger.iteration = iteration + logger.err = err + if rem(iteration, logger.collection_rate) == 0 + logger.hist[logger.record_location] = err + logger.record_location += 1 + end + # Always check max_it stopping criterion + # Compute in this way to avoid bounds error from searching in the max_it + 1 location + logger.converged = iteration <= logger.max_it ? logger.stopping_criterion(logger) : + false + return + +end +``` + +##### Stopping Functions +As was noted in the description of the required fields for the `Logger` the user should +have the opportunity to input a stopping function that should take the input of a +LoggerRecipe to which it updates the value of the `converged` field if stopping should +occur. An example implementation of function for threshold stopping, stop when progress +the metric falls below a particular threshold is presented below. +``` +function threshold_stop(log::LoggerRecipe) + return log.err < log.threshold_info +end +``` +#### SolverErrors +For computing the progress of a solver it is important to include implementations of +particular error techniques. These typically will be techniques like the residual or +compressed residual, but could be more complicated techniques like an estimate of backwards +stability. + +##### SolverError +This is a structure that holds user-controlled parameters for a progress estimation +technique. For basic techniques like the residual where no user-controlled parameters are +required this will simply be an empty structure. We have included an example of a +`SolverError` structure for the residual computations. It is important to note that +constructors for these techniques should have keyword inputs with predefined defaults. +``` +struct FullResidual <: SolverError + +end +``` + +##### SolverErrorRecipe +This structure contains the user-controlled parameters from the `SolverError` as well +memory allocations of a size determined based on the linear system. An example for a +residual technique has been included below. + +``` +mutable struct FullResidualRecipe{V<:AbstractVector} <: SolverErrorRecipe + residual::V +end +``` +##### complete_error +To generate the `SolverErrorRecipe` from the information in the linear system and +`SolverError` we use the function `complete_error`. This function should be implemented to +take the inputs of the SolverError`, a matrix `A` representing the linear system, and a +vector `b` representing the constant vector of the linear system. An example of this +function for the residual error technique has been included below. + +``` +function complete_error(error::FullResidual, A::AbstractMatrix, b::AbstractVector) + return FullResidualRecipe{typeof(b)}(zeros(size(b,1))) +end +``` +##### compute_error +To excute the technique we call the function `compute_error` with the inputs of the +`SolverErrorRecipe`, `Solver`, coefficient matrix `A`, and constant vector `b`. This +function then performs the necessary computations to return a single value indication of the +progress of the solver. An example of this for the residual technique that returns the norm- +squared of the residual is included below. +``` +function compute_error( + error::FullResidualRecipe, + solver::KaczmarzRecipe, + A::AbstractMatrix, + b::AbstractVector + )::Float64 + copyto!(error.residual, b) + mul!(error.residual, A, solver.solution_vec, -1.0, 1.0) + return dot(error.residual, error.residual) +end +``` + +#### SubSolvers +Although, randomized solvers are used to solve a larger linear system. They typically +rely on using compressors to generate a compressed linear system that can be easily solved +using standard techniques. The specifics of the 'standard' techniques is typically not +specified. For instance, if the compressed system is a least squares problem one could solve +this system with a QR algorithm or LSQR and potentially get vastly different performance +results. To allow the user to experiment with different techniques for solving the +compressed linear system, we introduce the SubSolver data structures. + +##### SubSolver +This is a data structure that allows the user to specify how they wish to solve the +compressed linear systems generated in the solving process. When the solver type is a direct +method it is possible for there to be no user inputs in this data structure. For iterative +methods there could be extensive user-controlled parameters included in this structure. For +example, for a LSQR SubSolver the user could input the maximum of iterations, a +preconditioner type, or stopping thresholds. We have included an example of the `SubSolver` +structure for the `LQSolver`, which is an approach for solving undetermined linear systems +and does not have any user-controlled parameters associated with it. It is important to note +that constructors for these techniques should have keyword inputs with predefined defaults. +``` +struct LQSolver <: SubSolver + +end +``` + +##### SubSolverRecipe +This is a data structure that contains the preallocated memory necessary for solving the +linear system. + +##### complete_solver +This is a function that takes a `SubSolver` and the linear system as input and uses these +inputs to output a SubSolverRecipe. + +##### update_sub_solver +This is a function that updates the preallocated memory in the SubSolverRecipe with +the relevant information for the new compressed linear system. + +##### ldiv! +A function that uses the SubSolverRecipe to solve the compressed linear system. + +#### Solvers +With an understanding of all of these sub techniques, we can discuss how to use these +methods to implement a Solver technique. The first data structure required for a solver is +the `Solver` structure. + +##### Solver +The Solver data structure is a structure where the user can input values of user-controlled +parameters specific to a particular type of solver. This typically involves the user +inputting the structures associated with their desired Compressor, Logger, Error, and +SubSolver, as well as any parameters like step-sizes associated with the particular +randomized solver they are using. As an example, we have included the Solver structure +associated with the Kaczmarz solver. It is important to note that constructors for these +techniques should have keyword inputs with predefined defaults. +``` +mutable struct Kaczmarz <: Solver + alpha::Float64 + S::Compressor + log::Logger + error::SolverError + sub_solver::SubSolver +end +``` + +##### SolverRecipe +The SolverRecipe will contain all the preallocated memory associate with the solver, the +solver specific user-controlled parameters, and all recipes associated with the +sub-techniques included in the `Solver` structure. We have included an example for the +`KaczmarzRecipe` below. +``` +mutable struct KaczmarzRecipe{T<:Number, + V<:AbstractVector, + M<:AbstractMatrix, + VV<:SubArray, + 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 + vec_view::VV +end +``` +The first four fields are associated with the sub-techniques for the solver. The alpha +field is a user defined value and the remaining fields are preallocated space for storing +the result of the compression and the solution vector. + +##### complete_solver +The `complete_solver` function performs the necessary computations and allocations to change +a `Solver` structure into a `SolverRecipe`. In the example code below for a Kaczmarz solver +these computations include running `complete_[technique]` for the compression, logging, +error, and sub solver techniques, as well as allocating memory for storing the compressed +matrix and compressed vector, the solution vector, and update vector. **The `views` +allocated by this function should be replicated in other multi-compression solver structures +to allow for varying sizes of the compression matrix.** +``` +function complete_solver( + solver::Kaczmarz, + x::AbstractVector, + A::AbstractMatrix, + b::AbstractVector + ) + # 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 kaczmarz solver." + @assert isdefined(logger, :converged) "LoggerRecipe $(typeof(logger)) does not contain\ + the field 'converged' and is not valid for a kaczmarz 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 + sample_size = n_rows + initial_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, sample_size, cols_a) + compressed_vec = typeof(b)(undef, sample_size) + # 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, :) + vec_view = view(compressed_vec, 1:sample_size) + solution_vec = x + update_vec = typeof(x)(undef, cols_a) + return KaczmarzRecipe{eltype(A), + typeof(b), + typeof(A), + typeof(vec_view), + 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, + vec_view + ) +end +``` + +##### rsolve! +Every implementation of a Solver technique should include a `rsolve!` function that performs +in-place updates to a solution vector and `SolverRecipe`. An example of such an +implementation for a Kaczmarz solver is included below. To the greatest extent possible +the implementation should be written in a way that avoids new memory allocations. This means +making use in-place update functions like `mul!` or `ldiv!` rather than `*` or `\`. +``` +function rsolve!( + solver::KaczmarzRecipe, + x::AbstractVector, + A::AbstractMatrix, + b::AbstractVector + ) + solver.solution_vec = x + err = 0.0 + 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 + end + + # 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:rows_s, :) + solver.vec_view = view(solver.compressed_vec, 1:rows_s) + # compress the matrix and constant vector + mul!(solver.mat_view, solver.S, A) + mul!(solver.vec_view, solver.S, b) + # Compute the block residual + mul!(solver.vec_view, solver.mat_view, solver.solution_vec, -1.0, 1.0) + # 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.vec_view) + # Using over-relaxation parameter, alpha, to update solution + solver.solution_vec .+= solver.alpha .* solver.update_vec + end + + return solver.solution_vec, solver.log +end +``` + +### Approximators +Aside from solving linear systems, Randomized Linear Algebra has also been proven to be +extremely useful for generating low rank approximations to linear systems. These low-rank +approximations can then be used to solve linear systems or perform more efficient +matrix-matrix multiplications. The main types of low rank approximation methods implemented +in this version of the library are random range finder techniques like random SVD, CUR +type methods, and Nystrom Methods. Low rank approximations can be formed simply by calling +the `rapproximate` function. Once a Low-rank approximation has been formed it can then be +applied either as a preconditioner by calling the `ldiv!` function or multiplied by calling +the `mul!` function. Each of the low rank approximation technique requires the +implementation of the following data structures and functions. + +#### Approximator +This is a data structure that contains the user defined parameters for an approximator. An +example of this structure for the RangeFinder decomposition is included below. In the case +of the randomized range finder the only real user controlled parameter is the sketch size, +which is controlled by the `Compressor`. It should be noted that constructors for these +structures should be based around keyword inputs with preset defaults. +``` +mutable struct RangeFinder <: Approximator + S::Compressor + error::ErrorMethod +end +``` + +#### ApproximatorRecipe +This a data structure that contains preallocated memory and the user-controlled parameters +for a specific approximation method. An example of this data structure for a RangeFinder +decomposition is included below. +``` +mutable struct RangeFinderRecipe <: ApproximatorRecipe + S::CompressorRecipe + error::ErrorMethodRecipe + compressed_mat::AbstractMatrix + approx_range::AbstractMatrix +end +``` + +#### complete_approximator +The `complete_approximator` function takes the matrix `A` and the +`Approximator` data structure to output an `ApproximatorRecipe` with properly allocated +storage for the low-rank approximation. An example of this function for the +`RangeFinderRecipe` is included below. +``` +function complete_approximator(approx::RangeFinder, A::AbstractMatrix) + S = complete_compressor(approx.S, A) + err = complete_error(approx.error, A) + s_rows, s_cols = size(S) + a_rows, a_cols = size(A) + compressed_mat = Matrix{eltype(A)}(undef, a_rows, s_cols) + approx_range = Matrix{eltype(A)}(undef, a_rows, s_cols) + return RangeFinderRecipe(S, err, compressed_mat, approx_range) +end +``` + +#### rapproximate! +A function that returns an `ApproximatorRecipe` and approximation error value for a +particular approximation method. The returned `ApproximatorRecipe` can then be used for +matrix multiplication or preconditioning through the use of the `mul!` and `ldiv!` functions +respectively. An example of this function for the `RangeFinderRecipe` is included below. +``` +function r_approximate!( + approximator::RangeFinderRecipe + A::AbstractMatrix +) + m, n = size(A) + update_compressor!(aproximator.S) + # compuress the matrix + mul!(compressed_mat, A, aproximator.S) + # Array is required to compute the skinny qr + approximator.approx_range .= Array(qr(compressed_mat).Q) + err = compute_error(aproximator.error, A) + return approximator, error +end +``` + +#### ldiv! +A function that solves the system `Mx = b` for `x` where M is a low rank approximation +matrix. This is useful for preconditioning linear systems. When there is no obvious way to +use the low rank approximation to solve this system the implementation will be the same as +the implementation for `mul!`. + +#### mul! +A function that multiplies a low rank approximation with a matrix. This should be +implemented as the five input `mul!` function. For these functions we follow the conventions +laid out in the LinearAlgebra library where there are five inputs (C, A, S, alpha, beta) and +it outputs `C = beta * C + alpha * A * S`. + +### Approximation Error +For the `Approximator`s an important sub-techniques are those that verify the accuracy of a +particular approximation. These methods can be exact, as in the case of computing +``\|A - QQ'A\|_F``, where ``Q`` is a row-space approximator or approximate such as the +``\|AS - QQ'AS\|_F`` where ``S`` is a Gaussian matrix with 10 column vectors. + +#### ApproximatorError +The `ApproximatorError` data structure is a data structure that takes the user controlled +parameters for a method that computes the approximation error, e.g ``A - QQ'A`` for a +particular approximation method. In cases where this error is an exact approximation no +user-controlled parameters may be needed and the `ApproximatorError` can be implemented as +an empty data structure. If user-controlled parameters are necessary then constructors +should be implemented that take in keyword arguments with defaults. We have included an +example data structure for a method that computes the projected error, ``A - QQ'A``, below. +``` +mutable struct ProjectedError <: ApproximatorError + +end + +``` + +#### ApproximatorErrorRecipe +An `ApproximatorErrorRecipe` contains the user-controlled parameters and preallocated memory +for a method that computes the approximation error, e.g `A - QQ'A` for a particular +approximation method. +``` +mutable struct ProjectedErrorRecipe{T, M{T}} <: ApproximatorErrorRecipe + where M <: AbstractMatrix + error::Float64 + large_buff_mat::M + small_buffer_mat::M +end +``` +#### complete_error +The `complete_error` function takes the information from a `ApproximatorError` and an +`AbstractMatrix` to create an `ApproximatorErrorRecipe`. An example for the +`ProjectedError` structure is included below. +``` +function complete_error(error::ProjectedError, S::CompressorRecipe, A::AbstractMatrix) + row_s, col_s = size(S) + row_a, col_a = size(A) + T = eltype(A) + M = Matrix{T} + small_buffer_mat = M(undef, col_s, col_a) + large_buffer_mat = M(undef, row, col_a) + return ProjectedErrorRecipe{T, M}(0.0, large_buffer_mat, small_buffer_mat) +end +``` + +#### compute_error +A function that computes the error of a particular approximation method with respect to the +matrix `A` for a particular approximation technique. An example for `ProjectedError` is +included below. +``` +function compute_error( + error::ProjectedErrorRecipe, + approximator::RandRangeFinderRecipe, + A::AbstractMatrix + ) + mul!(error.small_buffer_mat, approximator.row_space', A) + mul!(error.large_buffer_mat, approximator.row_space, error.small_buffer_mat) + error.large_buffer_mat .-= A + error.error = norm(error.large_buffer_mat) + return error.error +end +``` diff --git a/docs/src/dev/style_guide.md b/docs/src/dev/style_guide.md new file mode 100644 index 00000000..2742fafd --- /dev/null +++ b/docs/src/dev/style_guide.md @@ -0,0 +1,60 @@ +# Style Guide +```@contents +Pages=["styleguide.md"] +``` +When writing code for `RLinearAlgebra.jl` we expect the code to be written in accordance +with the [BLUE](https://github.com/JuliaDiff/BlueStyle) style. + +## Documentation +This section describes the writing style that should be used when writing documentation for +`RLinearAlgebra.jl.` Many of these ideas for these suggestions +come from [JUMP](https://jump.dev/JuMP.jl/stable/developers/style/). +Overall when documenting the code one should follow these recommendations: + - Be concise + - Prefer lists over long sentences + - Use numbers when describing an ordered set of ideas + - Use bullets when these is no specific order + +### Docstrings + - Every new **function** and **data structure** needs to have a docstring + - Use properly punctuated complete sentences + +Below, we provide an example of a function docstring and a data structure docstring. + +#### Function Docstring +``` +""" + myFunction(args; kwargs...) + +A couple of sentences describing the function. These sentences should describe what inputs +are required and what is output by the function. + +### Arguments +- `arg1`, description of arg 1 + +### Outputs +The result of calling the function. This should be either the data structure that is +modified or what is returned. + +A citation from the package DocumenterCitations. +""" + +``` + +#### Data Structure Docstring +``` +""" + YourStructure <: YourStructuresSuperType + +A brief sentence describing the purpose of the structure. + +A citation in MLA format if the function comes from another author's work. + +### Fields +- `S::FieldType`, brief description of field purpose + +Include a sentence or two describing how the constructors work. Please be sure to include +the default values of the constructor. +""" + +``` From f82346b8b0bba103c7e0e8b8c4115e2a9b8355b5 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 10 Feb 2025 19:30:27 +0000 Subject: [PATCH 02/42] correct issue in make file --- docs/make.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 7c7ce1e2..4ffcc55e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -21,14 +21,14 @@ makedocs( "Solvers Overview" => "api/solvers.md", "Solver Sub-routines" => [ "SubSolvers" => "api/sub_solvers.md", - "SolverErrors" => "api/solver_error.md", + "SolverErrors" => "api/solver_errors.md", "Loggers" => "api/loggers.md" ], ], "Approximators" => [ "Approximators Overview" => "api/approximators.md", "Approximator Sub-routines" => [ - "ApproximatorErrors" => "api/approximator_error.md" + "ApproximatorErrors" => "api/approximator_errors.md" ], ], ], From 95b0cd87c475c4f5822cbeef5d5e2a1569636d2a Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 25 Jun 2025 11:34:37 -0500 Subject: [PATCH 03/42] checklist --- docs/src/dev/checklists.md | 46 +++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/src/dev/checklists.md b/docs/src/dev/checklists.md index 66eabd54..66ef9fcf 100644 --- a/docs/src/dev/checklists.md +++ b/docs/src/dev/checklists.md @@ -12,7 +12,7 @@ the following steps before making a pull request. ``` 1. Implementation -- [ ] Create a file in the directory `Compressors`. +- [ ] Create a file in the directory `src/Compressors`. - [ ] Create a Compressor structure with n_rows and n_cols as well as other user-controlled parameters. - [ ] Create a constructor with keyword default values for your struct. @@ -40,3 +40,47 @@ work as intended and all warnings/assertions are displayed. - [ ] Tag two people to review your pull request. ``` +## Loggers +If you are implementing a logging method for the library, make sure you have completed +the following steps before making a pull request. In the following guides, `BasicLogger` +is used as an example. + + +1. Implementation +- Method's core codes (`src/Solvers/Loggers`): + - [ ] Create a file in the directory `src/Solvers/Loggers`. For example, `src/Solvers/Loggers/basic_logger.jl`. + - [ ] Create a `Logger` struct with `max_it`, `collection_rate`, `threshold_info`, `stopping_criterion`, any other method-needed parameters, and argument validations to check invalid inputs. For example, `BasicLogger<:Logger`. + - [ ] Create a constructor with keyword default values for your `Logger` struct. For example, + ``` + BasicLogger(; + max_it = 0, + collection_rate = 1, + threshold = 0.0, + stopping_criterion = threshold_stop + ) = BasicLogger(max_it, collection_rate, threshold, stopping_criterion) + ``` + - [ ] Add documentation for this `Logger` struct, with mainly 5 parts: brief introduction, fields introduction, constructor and its keywords, what the constructor returns, and what the argument validations throw. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. + - [ ] Create a `LoggerRecipe` struct that uses the parameters from the `Logger` struct to preallocate memory. For example, `BasicLoggerRecipe{F<:Function} <: LoggerRecipe`. + - [ ] Create a `complete_logger` function that takes the `logger` struct as an input, and returns the `LoggerRecipe` struct you defined last step. For example, `complete_logger(logger::BasicLogger)`. + - [ ] Add documentation for this `LoggerRecipe` struct, with mainly 2 parts: brief introduction, fields introduction. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. + - [ ] Create a `update_logger!` function to log the errors as the iteration goes on, and stop the logging with convergence status or the maximum iteration limit. For example, + ``` + update_logger!(logger::BasicLoggerRecipe, error::Float64, iteration::Int64) + ``` + - [ ] Create a `reset_logger!` function to clean the history log information after convergence or exceed the maximum iteration. For example, `reset_logger!(logger::BasicLoggerRecipe)`. + - [ ] Create a `threshold_stop` function as the convergent stopping criterion designed for your `Logger` struct. For example, `threshold_stop(log::BasicLoggerRecipe)` + - [ ] Add documentation for this `threshold_stop` function, with mainly 3 parts: brief introduction, arguments introduction, and returns. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. + - [ ] **Optional**: If you have any helper functions that needed for your implementation, please implement them in a folder at `src/Solvers/Loggers`. +- Package structure cooperation (`src/Solvers/Loggers.jl`, `src/RLinearAlgebra.jl`, `src/refs.bib`): + - [ ] Add an include("Loggers/[YOURFILE]") at bottom of the page, `src/Solvers/Loggers.jl`. + - [ ] Add import statements to `src/RLinearAlgebra.jl` with any functions from other packages that you use. + - [ ] Export your `Logger`, `LoggerRecipe`, any structs and functions you needed for your logger method to work in `src/RLinearAlgebra.jl`. + - [ ] Add your `Logger`, `LoggerRecipe`, needed structs and functions to `docs/src/api.loggers.md`, under the appropriate heading. + - [ ] If there are any new-added references, please add in `src/refs.bib`. +- Tests (`test/Solvers/Loggers`): + - [ ] Add a procedural test to `test/Solvers/Loggers`. Be sure to check that the functions work as intended and all warnings/assertions are displayed. For example, `test/Solvers/Loggers/basic_logger.jl`. +2. Pull request +- [ ] Give a specific title to pull request. +- [ ] Lay out the features added to the pull request. +- [ ] Tag two people to review your pull request. +- [ ] **Optional**: If possible, please also add Copilot as a reviewer and choose to adopt its suggestions if reasonable. From 64760f1d0cdf54bfd9bf406dd7f15037d96b951b Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 2 Jul 2025 10:55:47 -0500 Subject: [PATCH 04/42] Checklist saperation --- .github/workflows/Documenter.yml | 3 +- docs/make.jl | 6 ++- docs/src/dev/checklists.md | 68 +++++++------------------- docs/src/dev/checklists/compressors.md | 33 +++++++++++++ docs/src/dev/checklists/loggers.md | 47 ++++++++++++++++++ 5 files changed, 106 insertions(+), 51 deletions(-) create mode 100644 docs/src/dev/checklists/compressors.md create mode 100644 docs/src/dev/checklists/loggers.md diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 440ae315..bbd66f54 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -3,7 +3,8 @@ on: push: branches: - master - tags: '*' + tags: + - 'v*' pull_request: jobs: build: diff --git a/docs/make.jl b/docs/make.jl index 4ffcc55e..12a92cac 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -35,7 +35,11 @@ makedocs( "Contributing" => [ "Contributing Overview" => "dev/contributing.md", "Design of Library" => "dev/design.md", - "Checklists" => "dev/checklists.md", + "Checklists" => [ + "dev/checklists.md", + "Compressors" => "dev/checklists/compressors.md", + "Loggers" => "dev/checklists/loggers.md" + ], "Style Guide" => "dev/style_guide.md", ], "References" => "references.md", diff --git a/docs/src/dev/checklists.md b/docs/src/dev/checklists.md index 66ef9fcf..a422f9e2 100644 --- a/docs/src/dev/checklists.md +++ b/docs/src/dev/checklists.md @@ -1,53 +1,19 @@ -# Development Checklists - -```@contents -Pages=["checklists.md"] -``` +# Overview The purpose of this page is to maintain checklists of tasks to complete when adding new methods to the library. These checklists are organized by method. -## Compressors -If you are implementing a compression method for the library, make sure you have completed -the following steps before making a pull request. +## Create issue and use it! +Please add a brief introduction when you create an issue. After the introduction, copy and paste the corresponding checklist. +You can check the +boxes after you finish each steps, to help you contribute smoothly! +For example, If I want to add a `moving_average` logging method to the package, I will create an issue as follows: ``` -1. Implementation -- [ ] Create a file in the directory `src/Compressors`. -- [ ] Create a Compressor structure with n_rows and n_cols as well as other user-controlled -parameters. -- [ ] Create a constructor with keyword default values for your struct. -- [ ] Create a CompressorRecipe structure that uses the parameters from the Compressor -structure to preallocate memory. -- [ ] Create a `complete_compressor` function that takes as inputs of the Compressor, A, x,b -and returns a CompressorRecipe -- [ ] Create a `update_compressor!` function that generates new random values for a random -components of the Compressor -- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the left -- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the right -- [ ] a 5 input `mul!` function for applying a compressor to a vector -- [ ] a 5 input `mul!` function for applying the adjoint of the compressor to a vector -- [ ] Add an include("Compressors/[YOURFILE]") at bottom of page -- [ ] Add import statements to src/RLinearAlgebra.jl with any functions from other packages -that you use. -- [ ] Add your Compressor and CompressorRecipe to src/RLinearAlgebra.jl. -- [ ] Add your Compressor and CompressorRecipe to docs/src/api/compressors.md -under the appropiate heading. -- [ ] Add a procedural test to test/linear_samplers. Be sure to check that the functions -work as intended and all warnings/assertions are displayed. -2. Pull request -- [ ] Give a specific title to pull request. -- [ ] Lay out the features added to the pull request. -- [ ] Tag two people to review your pull request. -``` - -## Loggers -If you are implementing a logging method for the library, make sure you have completed -the following steps before making a pull request. In the following guides, `BasicLogger` -is used as an example. - - -1. Implementation -- Method's core codes (`src/Solvers/Loggers`): +# Introduction +Add the moving average method for both full and sketching residual. For more details, please see [the paper](https://arxiv.org/abs/2208.04989). +# Checklist +## Implementation +1. Method's core codes (`src/Solvers/Loggers`): - [ ] Create a file in the directory `src/Solvers/Loggers`. For example, `src/Solvers/Loggers/basic_logger.jl`. - [ ] Create a `Logger` struct with `max_it`, `collection_rate`, `threshold_info`, `stopping_criterion`, any other method-needed parameters, and argument validations to check invalid inputs. For example, `BasicLogger<:Logger`. - [ ] Create a constructor with keyword default values for your `Logger` struct. For example, @@ -71,16 +37,20 @@ is used as an example. - [ ] Create a `threshold_stop` function as the convergent stopping criterion designed for your `Logger` struct. For example, `threshold_stop(log::BasicLoggerRecipe)` - [ ] Add documentation for this `threshold_stop` function, with mainly 3 parts: brief introduction, arguments introduction, and returns. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. - [ ] **Optional**: If you have any helper functions that needed for your implementation, please implement them in a folder at `src/Solvers/Loggers`. -- Package structure cooperation (`src/Solvers/Loggers.jl`, `src/RLinearAlgebra.jl`, `src/refs.bib`): +2. Package structure cooperation (`src/Solvers/Loggers.jl`, `src/RLinearAlgebra.jl`, `src/refs.bib`): - [ ] Add an include("Loggers/[YOURFILE]") at bottom of the page, `src/Solvers/Loggers.jl`. - [ ] Add import statements to `src/RLinearAlgebra.jl` with any functions from other packages that you use. - [ ] Export your `Logger`, `LoggerRecipe`, any structs and functions you needed for your logger method to work in `src/RLinearAlgebra.jl`. - [ ] Add your `Logger`, `LoggerRecipe`, needed structs and functions to `docs/src/api.loggers.md`, under the appropriate heading. - [ ] If there are any new-added references, please add in `src/refs.bib`. -- Tests (`test/Solvers/Loggers`): +3. Tests (`test/Solvers/Loggers`): - [ ] Add a procedural test to `test/Solvers/Loggers`. Be sure to check that the functions work as intended and all warnings/assertions are displayed. For example, `test/Solvers/Loggers/basic_logger.jl`. -2. Pull request -- [ ] Give a specific title to pull request. + - [ ] After finish implementing, you can goes to the julia's package environment by type `]` in the julia command line and run `test` to test whether you can pass all the tests. + +## Pull request +- [ ] Give a specific title to pull request. - [ ] Lay out the features added to the pull request. - [ ] Tag two people to review your pull request. - [ ] **Optional**: If possible, please also add Copilot as a reviewer and choose to adopt its suggestions if reasonable. +``` +Note that, the checklist is copied from [Loggers](@ref "Loggers checklist") diff --git a/docs/src/dev/checklists/compressors.md b/docs/src/dev/checklists/compressors.md new file mode 100644 index 00000000..1447a857 --- /dev/null +++ b/docs/src/dev/checklists/compressors.md @@ -0,0 +1,33 @@ +# Compressors checklist +If you are implementing a compression method for the library, make sure you have completed +the following steps before making a pull request. + +``` +1. Implementation +- [ ] Create a file in the directory `src/Compressors`. +- [ ] Create a Compressor structure with n_rows and n_cols as well as other user-controlled +parameters. +- [ ] Create a constructor with keyword default values for your struct. +- [ ] Create a CompressorRecipe structure that uses the parameters from the Compressor +structure to preallocate memory. +- [ ] Create a `complete_compressor` function that takes as inputs of the Compressor, A, x,b +and returns a CompressorRecipe +- [ ] Create a `update_compressor!` function that generates new random values for a random +components of the Compressor +- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the left +- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the right +- [ ] a 5 input `mul!` function for applying a compressor to a vector +- [ ] a 5 input `mul!` function for applying the adjoint of the compressor to a vector +- [ ] Add an include("Compressors/[YOURFILE]") at bottom of page +- [ ] Add import statements to src/RLinearAlgebra.jl with any functions from other packages +that you use. +- [ ] Add your Compressor and CompressorRecipe to src/RLinearAlgebra.jl. +- [ ] Add your Compressor and CompressorRecipe to docs/src/api/compressors.md +under the appropiate heading. +- [ ] Add a procedural test to test/linear_samplers. Be sure to check that the functions +work as intended and all warnings/assertions are displayed. +2. Pull request +- [ ] Give a specific title to pull request. +- [ ] Lay out the features added to the pull request. +- [ ] Tag two people to review your pull request. +``` \ No newline at end of file diff --git a/docs/src/dev/checklists/loggers.md b/docs/src/dev/checklists/loggers.md new file mode 100644 index 00000000..6995faa9 --- /dev/null +++ b/docs/src/dev/checklists/loggers.md @@ -0,0 +1,47 @@ +# Loggers checklist +If you are implementing a logging method for the library, make sure you have completed +the following steps before making a pull request. In the following guides, `BasicLogger` +method is used as an example. + +``` +## Implementation +1. Method's core codes (`src/Solvers/Loggers`): + - [ ] Create a file in the directory `src/Solvers/Loggers`. For example, `src/Solvers/Loggers/basic_logger.jl`. + - [ ] Create a `Logger` struct with `max_it`, `collection_rate`, `threshold_info`, `stopping_criterion`, any other method-needed parameters, and argument validations to check invalid inputs. For example, `BasicLogger<:Logger`. + - [ ] Create a constructor with keyword default values for your `Logger` struct. For example, + ``` + BasicLogger(; + max_it = 0, + collection_rate = 1, + threshold = 0.0, + stopping_criterion = threshold_stop + ) = BasicLogger(max_it, collection_rate, threshold, stopping_criterion) + ``` + - [ ] Add documentation for this `Logger` struct, with mainly 5 parts: brief introduction, fields introduction, constructor and its keywords, what the constructor returns, and what the argument validations throw. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. + - [ ] Create a `LoggerRecipe` struct that uses the parameters from the `Logger` struct to preallocate memory. For example, `BasicLoggerRecipe{F<:Function} <: LoggerRecipe`. + - [ ] Create a `complete_logger` function that takes the `logger` struct as an input, and returns the `LoggerRecipe` struct you defined last step. For example, `complete_logger(logger::BasicLogger)`. + - [ ] Add documentation for this `LoggerRecipe` struct, with mainly 2 parts: brief introduction, fields introduction. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. + - [ ] Create a `update_logger!` function to log the errors as the iteration goes on, and stop the logging with convergence status or the maximum iteration limit. For example, + ``` + update_logger!(logger::BasicLoggerRecipe, error::Float64, iteration::Int64) + ``` + - [ ] Create a `reset_logger!` function to clean the history log information after convergence or exceed the maximum iteration. For example, `reset_logger!(logger::BasicLoggerRecipe)`. + - [ ] Create a `threshold_stop` function as the convergent stopping criterion designed for your `Logger` struct. For example, `threshold_stop(log::BasicLoggerRecipe)` + - [ ] Add documentation for this `threshold_stop` function, with mainly 3 parts: brief introduction, arguments introduction, and returns. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. + - [ ] **Optional**: If you have any helper functions that needed for your implementation, please implement them in a folder at `src/Solvers/Loggers`. +2. Package structure cooperation (`src/Solvers/Loggers.jl`, `src/RLinearAlgebra.jl`, `src/refs.bib`): + - [ ] Add an include("Loggers/[YOURFILE]") at bottom of the page, `src/Solvers/Loggers.jl`. + - [ ] Add import statements to `src/RLinearAlgebra.jl` with any functions from other packages that you use. + - [ ] Export your `Logger`, `LoggerRecipe`, any structs and functions you needed for your logger method to work in `src/RLinearAlgebra.jl`. + - [ ] Add your `Logger`, `LoggerRecipe`, needed structs and functions to `docs/src/api.loggers.md`, under the appropriate heading. + - [ ] If there are any new-added references, please add in `src/refs.bib`. +3. Tests (`test/Solvers/Loggers`): + - [ ] Add a procedural test to `test/Solvers/Loggers`. Be sure to check that the functions work as intended and all warnings/assertions are displayed. For example, `test/Solvers/Loggers/basic_logger.jl`. + - [ ] After finish implementing, you can goes to the julia's package environment by type `]` in the julia command line and run `test` to test whether you can pass all the tests. + +## Pull request +- [ ] Give a specific title to pull request. +- [ ] Lay out the features added to the pull request. +- [ ] Tag two people to review your pull request. +- [ ] **Optional**: If possible, please also add Copilot as a reviewer and choose to adopt its suggestions if reasonable. +``` \ No newline at end of file From 1633503a72df091bf40218e214dabb7db43beeaa Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 2 Jul 2025 13:15:01 -0500 Subject: [PATCH 05/42] dev --- docs/make.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 12a92cac..f10bdaed 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -50,5 +50,7 @@ makedocs( # See "Hosting Documentation" and deploydocs() in the Documenter manual # for more information. deploydocs( - repo = "github.com/numlinalg/RLinearAlgebra.jl" + repo = "github.com/numlinalg/RLinearAlgebra.jl", + devbranch = "master", # master's newest commit will become dev + push_preview = true # pull requests to the master will become available ) From c6de8843eb891da5539bc0aafcd055334b00a202 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 23 Jul 2025 17:29:02 +0100 Subject: [PATCH 06/42] small changes --- docs/src/dev/checklists.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/dev/checklists.md b/docs/src/dev/checklists.md index a422f9e2..b9bdd4d4 100644 --- a/docs/src/dev/checklists.md +++ b/docs/src/dev/checklists.md @@ -1,6 +1,5 @@ # Overview -The purpose of this page is to maintain checklists of tasks to complete when adding new -methods to the library. These checklists are organized by method. +The purpose of this page is to offer checklists of tasks, for everyone who help to improve the package. These checklists are organized by method. ## Create issue and use it! Please add a brief introduction when you create an issue. After the introduction, copy and paste the corresponding checklist. From f7a284f7224bf05786eb7a8243063ae1ac79985a Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 6 Aug 2025 16:51:39 +0100 Subject: [PATCH 07/42] yml --- .github/workflows/Bump_version.yml | 117 +++++++++++++++++++++++++ .github/workflows/Changelog_update.yml | 51 +++++++++++ .github/workflows/CompatHelper.yml | 50 +++++++++++ .github/workflows/TagBot.yml | 25 ++++++ readme.md | 1 + 5 files changed, 244 insertions(+) create mode 100644 .github/workflows/Bump_version.yml create mode 100644 .github/workflows/Changelog_update.yml create mode 100644 .github/workflows/CompatHelper.yml create mode 100644 .github/workflows/TagBot.yml diff --git a/.github/workflows/Bump_version.yml b/.github/workflows/Bump_version.yml new file mode 100644 index 00000000..eff43fb3 --- /dev/null +++ b/.github/workflows/Bump_version.yml @@ -0,0 +1,117 @@ +name: 'Propose Version Bump' + +on: + pull_request: + types: + - closed + branches: + - main + +jobs: + propose-bump: + # This job only runs when a PR is successfully merged. + if: github.event.pull_request.merged == true + + runs-on: ubuntu-latest + + steps: + # Step 1: Checkout the repository code. + - name: 'Checkout Repository' + uses: actions/checkout@v4 + with: + fetch-depth: 0 # We need history to find the commit by its SHA + token: ${{ secrets.PAT_FOR_BUMP_PR }} + + # Step 2: Get the merge commit message + - name: 'Get Merge Commit Message' + id: commit_message + run: | + # Use git log to get the full message of the exact merge commit SHA. + # The -n 1 flag ensures we only get one commit. + # The output is set for the next step to use. + MSG=$(git log --format=%B -n 1 ${{ github.event.pull_request.merge_commit_sha }}) + echo "message<> $GITHUB_OUTPUT + echo "$MSG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Step 3: Determine bump type and update the Project.toml file. + - name: 'Calculate Bump and Update Version File' + id: bump_logic + shell: python + run: | + import os + import re + import sys + + # Read the commit message from the previous step's output + commit_message = os.environ['COMMIT_MESSAGE'] + + # Determine bump type based on the Angular Conventional Commit spec + bump_type = '' + first_line = commit_message.splitlines()[0] + + if re.search(r'BREAKING CHANGE:', commit_message) or re.match(r'^\w+(\([\w-]+\))?!:', first_line): + bump_type = 'major' + elif re.match(r'^feat(\([\w-]+\))?:', first_line): + bump_type = 'minor' + elif re.match(r'^fix(\([\w-]+\))?:', first_line): + bump_type = 'patch' + + if not bump_type: + print('No version bump required for this commit.') + print('::set-output name=bumped::false') + sys.exit(0) + + print(f'Determined bump type: {bump_type}') + + try: + with open('Project.toml', 'r+') as f: + content = f.read() + version_match = re.search(r'^version\s*=\s*\"(\d+)\.(\d+)\.(\d+)\"', content, re.M) + + if not version_match: + print('Error: Could not find version in Project.toml') + sys.exit(1) + + major, minor, patch = map(int, version_match.groups()) + + if bump_type == 'major': major, minor, patch = major + 1, 0, 0 + elif bump_type == 'minor': minor, patch = minor + 1, 0 + elif bump_type == 'patch': patch += 1 + + new_version = f'{major}.{minor}.{patch}' + print(f'Bumping version to {new_version}') + + new_content = re.sub(r'^version\s*=\s*\".*\"', f'version = "{new_version}"', content, count=1, flags=re.M) + + f.seek(0) + f.write(new_content) + f.truncate() + + print(f'::set-output name=new_version::{new_version}') + print('::set-output name=bumped::true') + + except FileNotFoundError: + print('Error: Project.toml not found.') + sys.exit(1) + env: + # Pass the message from the "Get Merge Commit Message" step + COMMIT_MESSAGE: ${{ steps.commit_message.outputs.message }} + + # Step 4: Create a new pull request with the version bump. + - name: 'Create Version Bump PR' + if: steps.bump_logic.outputs.bumped == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.PAT_FOR_BUMP_PR }} + commit-message: "chore: bump version to ${{ steps.bump_logic.outputs.new_version }}" + title: "chore: Propose version bump to ${{ steps.bump_logic.outputs.new_version }}" + body: | + This PR was automatically generated to propose the next version for the Julia package. + + The recommended version bump is to **${{ steps.bump_logic.outputs.new_version }}**. This was determined based on the conventional commit message of the last merged PR. + + Please review and merge to apply the version update. + branch: "chore/version-bump-${{ steps.bump_logic.outputs.new_version }}" + labels: 'automated-pr, version_update' + delete-branch: true \ No newline at end of file diff --git a/.github/workflows/Changelog_update.yml b/.github/workflows/Changelog_update.yml new file mode 100644 index 00000000..fb632ee0 --- /dev/null +++ b/.github/workflows/Changelog_update.yml @@ -0,0 +1,51 @@ +name: Propose Changelog Update + +on: + # JuliaTagBot activated + issue_comment: + types: + - created + workflow_dispatch: + +jobs: + propose-changelog: + # Only when TagBot is activated, change the CHANGELOG + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + # Step 1 + - name: Checkout repository + uses: actions/checkout@v4 + with: + # All git history + fetch-depth: 0 + + # Step 2 + - name: Generate cumulative CHANGELOG.md + id: changelog_generator + uses: TriPSs/conventional-changelog-action@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + # output-file: "CHANGELOG.md" + input-file: "CHANGELOG.md" + version-file: "./Project.toml" + skip-commit: true + skip-tag: true + git-push: false + + # Step 3 + - name: Create Pull Request with updated CHANGELOG.md + if: steps.changelog_generator.outputs.skipped == 'false' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.PAT_FOR_CHANGELOG }} + commit-message: "docs(changelog): update CHANGELOG.md" + title: "Docs: Update CHANGELOG.md for new release" + body: | + This PR was automatically generated to update the `CHANGELOG.md` file for the upcoming release. + + Please review the changes and merge to trigger the final release. + # Branch for changelog + branch: "chore/update-changelog" + # If exist, update + delete-branch: true diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 00000000..d1891a4b --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,50 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v2 + with: + version: '1' + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # This repo uses Documenter, so we can reuse our [Documenter SSH key](https://documenter.juliadocs.org/stable/man/hosting/walkthrough/). + # If we didn't have one of those setup, we could configure a dedicated ssh deploy key `COMPATHELPER_PRIV` following https://juliaregistries.github.io/CompatHelper.jl/dev/#Creating-SSH-Key. + # Either way, we need an SSH key if we want the PRs that CompatHelper creates to be able to trigger CI workflows themselves. + # That is because GITHUB_TOKEN's can't trigger other workflows (see https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow). + # Check if you have a deploy key setup using these docs: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/reviewing-your-deploy-keys. + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} \ No newline at end of file diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 00000000..d9c0ddea --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,25 @@ +name: Run TagBot on Merge + +on: + pull_request: + types: + - closed + branches: + - main + +jobs: + tagbot: + # When pr is merged and the branch's name is 'chore/update-changelog' + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'chore/update-changelog' + runs-on: ubuntu-latest + steps: + # Step 1 + - name: Checkout repository + uses: actions/checkout@v4 + + # Step 2 + - name: Run TagBot + uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/readme.md b/readme.md index 20cc0063..e25ad11e 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,7 @@ [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://numlinalg.github.io/RLinearAlgebra.jl/dev) [![Runtests](https://github.com/numlinalg/RLinearAlgebra.jl/actions/workflows/Runtests.yml/badge.svg)](https://github.com/numlinalg/RLinearAlgebra.jl/actions/workflows/Runtests.yml) [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) + RLinearAlgebra is a Julia package that implements standard Randomized Linear Algebra algorithms and provides means for performance comparison. From 0d10adac2be9ca1c71ecb7e9f4d24b0e5b64281d Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 13 Aug 2025 17:01:15 +0100 Subject: [PATCH 08/42] docs --- .github/PULL_REQUEST_TEMPLATE.md | 0 .github/dependabot.yml | 7 ++ .github/workflows/CI.yml | 41 ++++++++++ .github/workflows/CompatHelper.yml | 42 +---------- .github/workflows/Documenter.yml | 2 +- .../{Bump_version.yml => HM_Bump_version.yml} | 0 ...log_update.yml => HM_Changelog_update.yml} | 0 .github/workflows/HM_TagBot.yml | 25 +++++++ .github/workflows/TagBot.yml | 40 +++++----- docs/make.jl | 4 + docs/src/tutorials/getting_started.md | 74 +++++++++++++++++++ docs/src/tutorials/introduction.md | 12 +++ 12 files changed, 191 insertions(+), 56 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/CI.yml rename .github/workflows/{Bump_version.yml => HM_Bump_version.yml} (100%) rename .github/workflows/{Changelog_update.yml => HM_Changelog_update.yml} (100%) create mode 100644 .github/workflows/HM_TagBot.yml create mode 100644 docs/src/tutorials/getting_started.md create mode 100644 docs/src/tutorials/introduction.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..e69de29b diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..700707ce --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..6629b541 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,41 @@ +name: CI +on: + push: + branches: + - main + tags: ['*'] + pull_request: + workflow_dispatch: +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created + actions: write + contents: read + strategy: + fail-fast: false + matrix: + version: + - '1.11' + - '1.6' + - 'pre' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index d1891a4b..cba9134c 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -3,48 +3,14 @@ on: schedule: - cron: 0 0 * * * workflow_dispatch: -permissions: - contents: write - pull-requests: write jobs: CompatHelper: runs-on: ubuntu-latest steps: - - name: Check if Julia is already available in the PATH - id: julia_in_path - run: which julia - continue-on-error: true - - name: Install Julia, but only if it is not already available in the PATH - uses: julia-actions/setup-julia@v2 - with: - version: '1' - arch: ${{ runner.arch }} - if: steps.julia_in_path.outcome != 'success' - - name: "Add the General registry via Git" - run: | - import Pkg - ENV["JULIA_PKG_SERVER"] = "" - Pkg.Registry.add("General") - shell: julia --color=yes {0} - - name: "Install CompatHelper" - run: | - import Pkg - name = "CompatHelper" - uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" - version = "3" - Pkg.add(; name, uuid, version) - shell: julia --color=yes {0} - - name: "Run CompatHelper" - run: | - import CompatHelper - CompatHelper.main() - shell: julia --color=yes {0} + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # This repo uses Documenter, so we can reuse our [Documenter SSH key](https://documenter.juliadocs.org/stable/man/hosting/walkthrough/). - # If we didn't have one of those setup, we could configure a dedicated ssh deploy key `COMPATHELPER_PRIV` following https://juliaregistries.github.io/CompatHelper.jl/dev/#Creating-SSH-Key. - # Either way, we need an SSH key if we want the PRs that CompatHelper creates to be able to trigger CI workflows themselves. - # That is because GITHUB_TOKEN's can't trigger other workflows (see https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow). - # Check if you have a deploy key setup using these docs: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/reviewing-your-deploy-keys. COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} - # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} \ No newline at end of file + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index bbd66f54..b8512405 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -2,7 +2,7 @@ name: Documenter on: push: branches: - - master + - main tags: - 'v*' pull_request: diff --git a/.github/workflows/Bump_version.yml b/.github/workflows/HM_Bump_version.yml similarity index 100% rename from .github/workflows/Bump_version.yml rename to .github/workflows/HM_Bump_version.yml diff --git a/.github/workflows/Changelog_update.yml b/.github/workflows/HM_Changelog_update.yml similarity index 100% rename from .github/workflows/Changelog_update.yml rename to .github/workflows/HM_Changelog_update.yml diff --git a/.github/workflows/HM_TagBot.yml b/.github/workflows/HM_TagBot.yml new file mode 100644 index 00000000..d9c0ddea --- /dev/null +++ b/.github/workflows/HM_TagBot.yml @@ -0,0 +1,25 @@ +name: Run TagBot on Merge + +on: + pull_request: + types: + - closed + branches: + - main + +jobs: + tagbot: + # When pr is merged and the branch's name is 'chore/update-changelog' + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'chore/update-changelog' + runs-on: ubuntu-latest + steps: + # Step 1 + - name: Checkout repository + uses: actions/checkout@v4 + + # Step 2 + - name: Run TagBot + uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index d9c0ddea..0cd3114e 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,25 +1,31 @@ -name: Run TagBot on Merge - +name: TagBot on: - pull_request: + issue_comment: types: - - closed - branches: - - main - + - created + workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: - tagbot: - # When pr is merged and the branch's name is 'chore/update-changelog' - if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'chore/update-changelog' + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - # Step 1 - - name: Checkout repository - uses: actions/checkout@v4 - - # Step 2 - - name: Run TagBot - uses: JuliaRegistries/TagBot@v1 + - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/docs/make.jl b/docs/make.jl index f10bdaed..4779cec1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -15,6 +15,10 @@ makedocs( modules = [RLinearAlgebra], pages = [ "Home" => "index.md", + "Tutorials" => [ + "Introduction" => "tutorials/introduction.md", + "Getting started" => "tutorials/getting_started.md" + ], "API Reference" => [ "Compressors" => "api/compressors.md", "Solvers" => [ diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/getting_started.md new file mode 100644 index 00000000..dee6bc3c --- /dev/null +++ b/docs/src/tutorials/getting_started.md @@ -0,0 +1,74 @@ +# A simple example + + +## Use Case: Solving a Least-Squares Problem with the Sparse Sign Method + +This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve an overdetermined linear system (i.e., a least-squares problem) of the form: +$$ +\min_{x} \|Ax - b\|_2^2 +$$ +We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and run the solver. + +--- +### 1. Problem Setup + +First, we define a specific linear system $Ax = b$. To verify the accuracy of the final result, we will first create a known solution, $x_{\text{true}}$, and then use it to generate the vector $b$. + +* **Matrix `A`**: A random $100 \times 20$ matrix. +* **Vector `b`**: Calculated as $b = A x_{\text{true}}$, with dimensions $100 \times 1$. +* **Goal**: Find a solution $x$ that is as close as possible to $x_{\text{true}}$. + +--- +### 2. Solution Steps + +We will proceed through the following steps to build and run a randomized solver. + +#### Step 1: Environment Setup and Problem Definition +First, we need to import the required libraries and create the matrix `A` and vector `b` as defined above. We will also set an initial guess, `x_init`, for the solver. + +```julia +# Import relevant libraries +using RLinearAlgebra, Random, LinearAlgebra + + +# Define the dimensions of the linear system +num_rows, num_cols = 100, 20 + +# Create the matrix A and a known true solution x_true +A = randn(Float64, num_rows, num_cols) +x_true = randn(Float64, num_cols) + +# Calculate the right-hand side vector b from A and x_true +b = A * x_true + +# Set an initial guess for the solution vector x (typically a zero vector) +x_init = zeros(Float64, num_cols) + +println("Problem setup complete:") +println(" - Dimensions of matrix A: ", size(A)) +println(" - Dimensions of vector b: ", size(b)) +``` + +#### Step 2: Configure the `SparseSign` Compressor +The core idea of randomized methods is to reduce the scale of the original problem using a random "sketch" or "compression" matrix, $S$. Here, we choose `SparseSign` as our `Compressor`. This compressor generates a sparse matrix whose non-zero elements are +1 or -1 (with scaling). + +We will configure a compression matrix $S$ that compresses the 100 rows of the original system down to 30 rows. + +```julia +# The goal is to compress the 100 rows of A to 30 rows +compression_dim = 30 +# We want each row of the compression matrix S to have 5 non-zero elements +non_zeros = 5 + +# Configure the SparseSign compressor +# - cardinality=Left(): Indicates the compression matrix S will be left-multiplied with A (SAx = Sb). +# - compression_dim: The compressed dimension (number of rows). +# - nnz: The number of non-zero elements per row in S. +# - type: The element type for the compression matrix. +sparse_compressor = SparseSign( + cardinality=Left(), + compression_dim=compression_dim, + nnz=non_zeros, + type=Float64 +) +``` diff --git a/docs/src/tutorials/introduction.md b/docs/src/tutorials/introduction.md new file mode 100644 index 00000000..1b219eaa --- /dev/null +++ b/docs/src/tutorials/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +The purpose of these tutorials is to use examples help + new users quickly get hands on the usage of RLinearAlgebra. + +## How these tutorials are structured + +- Start from the solving $$Ax = b$$ + + + + From 1d5ac70aa81709fb84fb61816acb338101d2116b Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 13 Aug 2025 17:30:40 +0100 Subject: [PATCH 09/42] 123 --- .github/PULL_REQUEST_TEMPLATE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e69de29b..d343cf8f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +# Title Formatting +Please format your Pull Request title according to the Conventional Commits specification. This is crucial for automating our release process. + +Based on the changes in your PR, please use one of the following prefixes in your title: + +- fix: for a bug fix. + +Example: fix: correct user authentication flow + +- feat: for a new feature. + +Example: feat: add user profile page + +- For breaking changes, you must signify this in one of two ways: + + - Append a ! after the type in the title. + + Example: feat!: remove user endpoint + + - Include a footer in your PR description below that starts with BREAKING CHANGE:. + From ffba34892ed022bc35566c25421fb0383db25a8a Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Sat, 23 Aug 2025 17:00:01 +0100 Subject: [PATCH 10/42] initialized the introduction --- docs/src/manual/introduction.md | 144 ++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/src/manual/introduction.md diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md new file mode 100644 index 00000000..54aa3f3e --- /dev/null +++ b/docs/src/manual/introduction.md @@ -0,0 +1,144 @@ +# A library for exploring Randomized Linear Algebra +If you are here, you probably know that Linear Algebra is foundational to data science and +scientific computing. You also probably know Linear Algebra routines dominate the +computational cost of many of the algorithms in these fields. Thus, improving the +scalability of these algorithms requires more scalable Linear Algebra techniques. + +An exciting recent set of techniques that offer such improved scalability of +Linear Algebra techniques are known as Randomized Linear Algebra. +In general, Randomized Linear Algebra techniques aim to achieve this improved +scalability by forming a representative sample of a matrix and performing +operations on that sample. In some circumstances operating on this sample +can offer profound speed-ups as can see in the following example +where a technique known as the RandomizedSVD (see [halko2011finding](@cite)) +is used to compute a rank-20 approximation to ``3000 \\times 3000`` matrix +``A`` in place of a truncated SVD. Compared to computing the SVD and +truncating it, the RandomizedSVD is 100 times faster and just as accurate +as the truncated SVD. + +```julia +using RLinearAlgebra +using LinearAlgebra + +# Generate a rank-20 matrix +A = randn(3000, 20) * randn(20, 3000); + +@time U,S,V = svd(A); +# 4.566639 seconds (13 allocations: 412.354 MiB, 0.92% gc time) + +# Form the RandomizedSVD data structure +technique = RandomizedSVD( + compressor = Gaussian(compression_dim= 22, cardinality=Right()), + orthogonalize=false, + power_its = 0 +) + +# Take the RandomizedSVD of A +@time rec = rapproximate(technique, A); +# 0.050950 seconds (39 allocations: 5.069 MiB) + +# Take the norm of the difference between the RandomizedSVD and TrunctatedSVD at rank 22 +norm(rec.U * Diagonal(rec.S) * rec.V' - U[:,1:22] * Diagonal(S[1:22]) * (V[:, 1:22])') +# 6.914995919005829e-11 +``` + +Over the years, numerous Randomized Linear Algebra approaches have been proposed not only +for basic linear tasks such as computing a low-rank approximation to a matrix, solving +a linear system, or solving a least squares problem, but also for how obtain a +representative sample of the matrix itself. To this point, a single easy to prototype +library has not been developed to bring these techniques to the masses. RLinearAlgebra.jl +is designed to do exactly that. + +In particular, RLinearAlgebra.jl leverages a modular design to allow you +to easily test Randomized Linear Algebra routines under a wide-range of parameter choices. +RLinearAlgebra.jl provides routines for two core Linear Algebra tasks: finding a solution to +a linear system via ``Ax=b`` or ``\\min_x \\|Ax - b\\|`` and forming a low rank +approximation to a matrix, ``\\hat A`` where ``\\hat A \\approx A``. The solution to a linear +system appears everywhere: Optimization, Tomography, Statistics, Scientific Computing, Machine +Learning, etc. The low-rank approximation problem has only become more relevant in recent years +owing to the drastic increase in matrix sizes. It has been widely used in Statistics via PCA, but +also has become increasingly more relevant in all the fields where solving a linear system is +relevant. + +This manual will walk you through the use of the RLinearAlgebra library. The remainder of this +section will be focused on providing an overview of the common design elements in the library, +and information about how to get started using the library. + +## Overview of the Library +The library is based on two data structure types: **techniques** that contain the parameters +that define a particular method and **technique recipes** that contain these parameters and +the necessary preallocations for the desired technique to be executed effectively. As the +user you only need to define the techniques and the library will do all the work to form +the recipes for you. If you wish to convert a technique into a technique recipe you can use +the `complete_[technique type]` function. + +### The Technique Types +With an understanding of the basic structures in the library, one may wonder, "What +types of techniques are there? First, there are the techniques for solving the linear +system, `Solvers` and techniques for forming a low-rank approximation to a matrix, +`Approximators`. Both `Solvers` and `Approximators` achieve speedups by working on +compressed forms (often known as sketched or sampled) of the linear system, techniques that +compress the linear system are known as `Compressors`. Aside from these global techniques, +there are also techniques that are specific to `Solvers`, which include: + +1. `SubSolvers`, techniques that solve the inner (compressed) linear system. +2. `Loggers`, techniques that log information and determine whether a stopping criterion has + been met. +3. `SolverError`, a technique that computes the error of a current iterate of a solver. + +Similarly, `Approximators` have their own specific techniques, which include: + +1. `ApproximorError`, a technique that computes the error of an `Approximator`. + +With all these technique structures, you may be wondering, what can I do with these +structures? Well, the answer is not much. As is summarized in the following table. +| Technique | Parent Technique | Function Calls| +|----------|------------------| --| +|`Approximator`| None | `complete_approximator`
`rapproximate`| +|`Compressor` | None| `complete_compressor`| +|`Solver`| None| `complete_solver`
`rsolve`| +|`ApproximatorError`| `Approximator` | `complete_approximator_error`| +|`Logger`| `Solver`| `complete_logger`| +|`SolverError` | `Solver`| `complete_solver_error`| +|`SubSolver`| `Solver`| `complete_sub_solver`| + + +From the above table we can see that essentially all you are able to do unless you are using +an `Approximator` or a `Solver` is complete the technique. The reason being that all the +technique structures contain only information about algorithmic parameters that require no +information about the linear system. The recipes on the other hand have all the information +required to use a technique without having to allocate new memory. We determine the +preallocations for the Recipes by merging the parameter information of the technique +structures with the matrix and linear system information via the `complete_[technique]` +functions, which is the only function that you can call when you have a technique structure. +There is a special exception for `rsolve` and `rapproximate` because they implicitly call +all the necessary completes to form the appropriate recipe. The bottom line is that do +anything useful you will need a recipe. + +### The Recipe Types +Every technique can be transformed into a recipe. As has been stated before, what makes the +recipes different is that they contain all the required memory allocations and +parameterizations that can only be determined from linear system information. For users, +all you have to know is that as soon as you have a recipe you can do a lot. As can be seen +in the following table. +| Technique Recipe| Parent Recipe | Function Calls| +|----------|------------------| --| +|`Approximator`| None | `mul!`
`rapproximate!`| +|`Compressor` | None| `mul!`
`update_compressor!`| +|`Solver`| None| `rsolve!`| +|`ApproximatorError`| `Approximator` | `compute_approximator_error`| +|`Logger`| `Solver`| `reset_logger!`
`update_logger!`| +|`SolverError` | `Solver`| `compute_error`| +|`SubSolver`| `Solver`| `update_sub_solver!`
`ldiv!` | + +Instead of providing +a different function for each method associated with these tasks, RLinearAlgebra.jl +leverages the multiple-dispatch functionality of Julia to allow all linear systems and +least squares problems to be solved calling the function +`rsolve(solver::Solver, x::AbstractVector, A::AbstractMatrix, b::AbstractVector)` +and all matrices to be approximated by calling the function +`rapproximate(approximator::Approximator, A::AbstractMatrix)`. Under this design, changing +the routine for solving your linear system or approximate your matrix is as +simple as changing the`solver` or `approximator` arguments. + +## Installing RLinearAlgebra From 2c54d6a21b4f3eef21d6ba0259f800a67a8ea064 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Sun, 24 Aug 2025 17:35:28 +0100 Subject: [PATCH 11/42] slight intro edits --- docs/make.jl | 3 + docs/src/manual/introduction.md | 132 +++++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 35 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 4779cec1..efab84b7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -19,6 +19,9 @@ makedocs( "Introduction" => "tutorials/introduction.md", "Getting started" => "tutorials/getting_started.md" ], + "Manual" => [ + "Introduction" => "manual/introduction.md", + ], "API Reference" => [ "Compressors" => "api/compressors.md", "Solvers" => [ diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md index 54aa3f3e..63282f58 100644 --- a/docs/src/manual/introduction.md +++ b/docs/src/manual/introduction.md @@ -4,14 +4,14 @@ scientific computing. You also probably know Linear Algebra routines dominate th computational cost of many of the algorithms in these fields. Thus, improving the scalability of these algorithms requires more scalable Linear Algebra techniques. -An exciting recent set of techniques that offer such improved scalability of -Linear Algebra techniques are known as Randomized Linear Algebra. +An exciting new set of techniques that offers such improved scalability of +Linear Algebra techniques are Randomized Linear Algebra techniques. In general, Randomized Linear Algebra techniques aim to achieve this improved scalability by forming a representative sample of a matrix and performing operations on that sample. In some circumstances operating on this sample can offer profound speed-ups as can see in the following example where a technique known as the RandomizedSVD (see [halko2011finding](@cite)) -is used to compute a rank-20 approximation to ``3000 \\times 3000`` matrix +is used to compute a rank-20 approximation to ``3000 \times 3000`` matrix ``A`` in place of a truncated SVD. Compared to computing the SVD and truncating it, the RandomizedSVD is 100 times faster and just as accurate as the truncated SVD. @@ -46,34 +46,47 @@ Over the years, numerous Randomized Linear Algebra approaches have been proposed for basic linear tasks such as computing a low-rank approximation to a matrix, solving a linear system, or solving a least squares problem, but also for how obtain a representative sample of the matrix itself. To this point, a single easy to prototype -library has not been developed to bring these techniques to the masses. RLinearAlgebra.jl -is designed to do exactly that. +library has not been developed to bring these techniques to the masses. RLinearAlgebra.jl is designed to be exactly such an easy-to-use library. In particular, RLinearAlgebra.jl leverages a modular design to allow you -to easily test Randomized Linear Algebra routines under a wide-range of parameter choices. -RLinearAlgebra.jl provides routines for two core Linear Algebra tasks: finding a solution to -a linear system via ``Ax=b`` or ``\\min_x \\|Ax - b\\|`` and forming a low rank -approximation to a matrix, ``\\hat A`` where ``\\hat A \\approx A``. The solution to a linear +to easily test Randomized Linear Algebra routines under a wide-range of parameter choices. RLinearAlgebra.jl provides routines for two core Linear Algebra tasks: finding a solution to +a linear system via ``Ax=b`` or ``\min_x \|Ax - b\|`` and forming a low rank +approximation to a matrix, ``\hat A`` where ``\hat A \approx A``. The solution to a linear system appears everywhere: Optimization, Tomography, Statistics, Scientific Computing, Machine Learning, etc. The low-rank approximation problem has only become more relevant in recent years owing to the drastic increase in matrix sizes. It has been widely used in Statistics via PCA, but also has become increasingly more relevant in all the fields where solving a linear system is relevant. -This manual will walk you through the use of the RLinearAlgebra library. The remainder of this +This manual will walk you through the use of the RLinearAlgebra.jl library. The remainder of this section will be focused on providing an overview of the common design elements in the library, and information about how to get started using the library. ## Overview of the Library The library is based on two data structure types: **techniques** that contain the parameters that define a particular method and **technique recipes** that contain these parameters and -the necessary preallocations for the desired technique to be executed effectively. As the +the necessary preallocations for the desired technique to be executed efficiently. As the user you only need to define the techniques and the library will do all the work to form the recipes for you. If you wish to convert a technique into a technique recipe you can use the `complete_[technique type]` function. +### Key Functions for using the library +RLinearAlgebra.jl can be used for two key linear algebra tasks: 1. solving linear systems +and 2. forming low-rank approximations to matrices. All linear systems can be solved by +calling the function, `rsolve(solver, x, A, b)`. Where `solver` is a technique structure +that defines the technique you wish to use to solve the linear system, `x` is an initial +guess of a possible solution to the linear system, `A` is the matrix for the linear system, +and `b` is a constant vector. To form matrix approximations, one must call the function +`rapproximate(approximator, A)`. Here `approximator` is a structure that defines the +low-rank approximation technique that you wish to use and `A` is the matrix that you +wish to approximate. + +Each `solver` and `approximator` technique contains fields requiring other sub-techniques +that can impact the performance of the `solver`/`approximator`. The description of these +technique types can be found in the following section. + ### The Technique Types -With an understanding of the basic structures in the library, one may wonder, "What +With an understanding of the basic structures in the library, one may wonder, what types of techniques are there? First, there are the techniques for solving the linear system, `Solvers` and techniques for forming a low-rank approximation to a matrix, `Approximators`. Both `Solvers` and `Approximators` achieve speedups by working on @@ -90,24 +103,26 @@ Similarly, `Approximators` have their own specific techniques, which include: 1. `ApproximorError`, a technique that computes the error of an `Approximator`. -With all these technique structures, you may be wondering, what can I do with these -structures? Well, the answer is not much. As is summarized in the following table. -| Technique | Parent Technique | Function Calls| -|----------|------------------| --| -|`Approximator`| None | `complete_approximator`
`rapproximate`| -|`Compressor` | None| `complete_compressor`| -|`Solver`| None| `complete_solver`
`rsolve`| -|`ApproximatorError`| `Approximator` | `complete_approximator_error`| -|`Logger`| `Solver`| `complete_logger`| -|`SolverError` | `Solver`| `complete_solver_error`| -|`SubSolver`| `Solver`| `complete_sub_solver`| +With all these technique structures, you may be wondering, what functions +can I call on these structures? Well, the answer is not many. As is +summarized in the following table. + +| Technique | Parent Technique | Function Calls | +| ---------- | ------------------ | ----------------------------------- | +|`Approximator` | None | `complete_approximator`,`rapproximate`| +|`Compressor` | None | `complete_compressor` | +|`Solver` | None | `complete_solver`, `rsolve` | +|`ApproximatorError`| `Approximator` | `complete_approximator_error` | +|`Logger` | `Solver` | `complete_logger` | +|`SolverError` | `Solver` | `complete_solver_error` | +|`SubSolver` | `Solver` | `complete_sub_solver` | From the above table we can see that essentially all you are able to do unless you are using an `Approximator` or a `Solver` is complete the technique. The reason being that all the technique structures contain only information about algorithmic parameters that require no information about the linear system. The recipes on the other hand have all the information -required to use a technique without having to allocate new memory. We determine the +required to use a technique including the required pre-allocated memory. We determine the preallocations for the Recipes by merging the parameter information of the technique structures with the matrix and linear system information via the `complete_[technique]` functions, which is the only function that you can call when you have a technique structure. @@ -117,19 +132,20 @@ anything useful you will need a recipe. ### The Recipe Types Every technique can be transformed into a recipe. As has been stated before, what makes the -recipes different is that they contain all the required memory allocations and -parameterizations that can only be determined from linear system information. For users, +recipes different is that they contain all the required memory allocations. These allocations can +only be determined from once the matrix is known. As a user, all you have to know is that as soon as you have a recipe you can do a lot. As can be seen in the following table. -| Technique Recipe| Parent Recipe | Function Calls| -|----------|------------------| --| -|`Approximator`| None | `mul!`
`rapproximate!`| -|`Compressor` | None| `mul!`
`update_compressor!`| -|`Solver`| None| `rsolve!`| -|`ApproximatorError`| `Approximator` | `compute_approximator_error`| -|`Logger`| `Solver`| `reset_logger!`
`update_logger!`| -|`SolverError` | `Solver`| `compute_error`| -|`SubSolver`| `Solver`| `update_sub_solver!`
`ldiv!` | + +| Technique Recipe | Parent Recipe | Function Calls | +|----------------- |------------------| ---------------------------------| +|`Approximator` | None | `mul!`, `rapproximate!` | +|`Compressor` | None | `mul!`,`update_compressor!` | +|`Solver` | None | `rsolve!` | +|`ApproximatorError`| `Approximator` | `compute_approximator_error` | +|`Logger` | `Solver` | `reset_logger!`, `update_logger!`| +|`SolverError` | `Solver` | `compute_error` | +|`SubSolver` | `Solver` | `update_sub_solver!`,`ldiv!` | Instead of providing a different function for each method associated with these tasks, RLinearAlgebra.jl @@ -142,3 +158,49 @@ the routine for solving your linear system or approximate your matrix is as simple as changing the`solver` or `approximator` arguments. ## Installing RLinearAlgebra +Currently, RLinearAlgebra.jl is not registered in Julia's official package registry. +It can be installed by writing in the REPL: +```julia +] add https://github.com/numlinalg/RLinearAlgebra.jl.git +``` +It can also be cloned into a local directory and installed by: +1. `cd` into the local project directory +2. Call `git clone https://github.com/numlinalg/RLinearAlgebra.jl.git` +3. Run Julia +4. Call `using Pkg` +5. Call `Pkg.activate(RLinearAlgebra.jl)` +6. Call `Pkg.instantiate()` + +For more information see [Using someone else's project](https://pkgdocs.julialang.org/v1/environments/#Using-someone-else's-project). + +## Using RLinearAlgebra.jl +For this example let's assume that we have a vector that we wish to compress +using one the RLinearAlgebra.jl `SparseSign` compressor. To do this: + +1. Load RLinearAlgebra.jl and generate your vector +```julia +using RLinearAlgebra +using LinearAlgebra +# Specify the size of the vector +n = 10000 +x = rand(n) +``` +2. Define the `SparseSign` technique. This requires us to specify a `cardinality`, + the direction we intend to apply the compressor from, and a `compression_dim`, + the number of entries we want in the compressed vector. In this instance we + want the cardinality to be `Left()` and the `compression_dim = 20`. +```julia +comp = SparseSign(compression_dim = 20, Cardinality = Left()) +``` +3. Use the `complete_compressor` function to generate the `SparseSignRecipe`. +```julia +S = complete_compressor(comp, A) +``` +4. Apply the compressor to the vector using the multiplication function +```julia +Sx = S * x + +norm(Sx) + +norm(x) +``` \ No newline at end of file From ff88338b88c816bedacff55058d1f9dd32ec06f8 Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Sun, 24 Aug 2025 17:36:28 +0100 Subject: [PATCH 12/42] removed redundant key function sec --- docs/src/manual/introduction.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md index 63282f58..e30bf8c3 100644 --- a/docs/src/manual/introduction.md +++ b/docs/src/manual/introduction.md @@ -70,21 +70,6 @@ user you only need to define the techniques and the library will do all the work the recipes for you. If you wish to convert a technique into a technique recipe you can use the `complete_[technique type]` function. -### Key Functions for using the library -RLinearAlgebra.jl can be used for two key linear algebra tasks: 1. solving linear systems -and 2. forming low-rank approximations to matrices. All linear systems can be solved by -calling the function, `rsolve(solver, x, A, b)`. Where `solver` is a technique structure -that defines the technique you wish to use to solve the linear system, `x` is an initial -guess of a possible solution to the linear system, `A` is the matrix for the linear system, -and `b` is a constant vector. To form matrix approximations, one must call the function -`rapproximate(approximator, A)`. Here `approximator` is a structure that defines the -low-rank approximation technique that you wish to use and `A` is the matrix that you -wish to approximate. - -Each `solver` and `approximator` technique contains fields requiring other sub-techniques -that can impact the performance of the `solver`/`approximator`. The description of these -technique types can be found in the following section. - ### The Technique Types With an understanding of the basic structures in the library, one may wonder, what types of techniques are there? First, there are the techniques for solving the linear From 6708ae9ca49923ff39b925a1b172c463576ce5e9 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Sun, 24 Aug 2025 15:48:32 -0500 Subject: [PATCH 13/42] rewrite in html --- docs/src/manual/introduction.md | 40 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md index e30bf8c3..4d0ecd78 100644 --- a/docs/src/manual/introduction.md +++ b/docs/src/manual/introduction.md @@ -168,7 +168,7 @@ using RLinearAlgebra using LinearAlgebra # Specify the size of the vector n = 10000 -x = rand(n) +x = rand(n) ``` 2. Define the `SparseSign` technique. This requires us to specify a `cardinality`, the direction we intend to apply the compressor from, and a `compression_dim`, @@ -177,15 +177,47 @@ x = rand(n) ```julia comp = SparseSign(compression_dim = 20, Cardinality = Left()) ``` -3. Use the `complete_compressor` function to generate the `SparseSignRecipe`. +1. Use the `complete_compressor` function to generate the `SparseSignRecipe`. ```julia S = complete_compressor(comp, A) ``` -4. Apply the compressor to the vector using the multiplication function +1. Apply the compressor to the vector using the multiplication function ```julia Sx = S * x norm(Sx) norm(x) -``` \ No newline at end of file +``` + +```@raw html +
    +
  1. + Load RLinearAlgebra.jl and generate your vector +
    using RLinearAlgebra
    +using LinearAlgebra
    +# Specify the size of the vector
    +n = 10000
    +x = rand(n)
    +
  2. +
  3. + Define the SparseSign technique. This requires us to specify a cardinality, + the direction we intend to apply the compressor from, and a compression_dim, + the number of entries we want in the compressed vector. In this instance we + want the cardinality to be Left() and the compression_dim = 20. +
    comp = SparseSign(compression_dim = 20, Cardinality = Left())
    +
  4. +
  5. + Use the complete_compressor function to generate the SparseSignRecipe. +
    S = complete_compressor(comp, A)
    +
  6. +
  7. + Apply the compressor to the vector using the multiplication function +
    Sx = S * x
    +    
    +norm(Sx)
    +
    +norm(x)
    +
  8. +
+``` From 63a9479fe004accc7aebd2d597874f78df1ac052 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Sun, 24 Aug 2025 15:59:57 -0500 Subject: [PATCH 14/42] Add md with no Julia light --- docs/src/manual/introduction.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md index 4d0ecd78..d80fb009 100644 --- a/docs/src/manual/introduction.md +++ b/docs/src/manual/introduction.md @@ -190,6 +190,39 @@ norm(Sx) norm(x) ``` +### Keep Markdown +For this example let's assume that we have a vector that we wish to compress +using one the RLinearAlgebra.jl `SparseSign` compressor. To do this: + +1. Load RLinearAlgebra.jl and generate your vector + + using RLinearAlgebra + using LinearAlgebra + # Specify the size of the vector + n = 10000 + x = rand(n) + +2. Define the `SparseSign` technique. This requires us to specify a `cardinality`, + the direction we intend to apply the compressor from, and a `compression_dim`, + the number of entries we want in the compressed vector. In this instance we + want the cardinality to be `Left()` and the `compression_dim = 20`. + + comp = SparseSign(compression_dim = 20, Cardinality = Left()) + +3. Use the `complete_compressor` function to generate the `SparseSignRecipe`. + + S = complete_compressor(comp, A) + +1. Apply the compressor to the vector using the multiplication function + + Sx = S * x + + norm(Sx) + + norm(x) + + +### Use HTML ```@raw html
  1. From c557f179d55289254cdbcab434297a68eedebbeb Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 27 Aug 2025 11:56:58 -0500 Subject: [PATCH 15/42] Only left the version with HTML and comment out others --- docs/make.jl | 1 + docs/src/custom_html.css | 7 +++ docs/src/manual/introduction.md | 79 +++++++++++++++++++-------------- 3 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 docs/src/custom_html.css diff --git a/docs/make.jl b/docs/make.jl index efab84b7..b11c86ba 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,6 +10,7 @@ makedocs( sitename = "RLinearAlgebra", format = Documenter.HTML( collapselevel=1, + assets = String["custom_html.css"], ), plugins=[bib], modules = [RLinearAlgebra], diff --git a/docs/src/custom_html.css b/docs/src/custom_html.css new file mode 100644 index 00000000..43a97745 --- /dev/null +++ b/docs/src/custom_html.css @@ -0,0 +1,7 @@ +/* This file is aim to customize html format */ + +/* Add margin at the beginning and the end of code blocks */ +li pre { + margin-top: 1em; + margin-bottom: 1em; +} \ No newline at end of file diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md index d80fb009..f814c39a 100644 --- a/docs/src/manual/introduction.md +++ b/docs/src/manual/introduction.md @@ -157,6 +157,51 @@ It can also be cloned into a local directory and installed by: 6. Call `Pkg.instantiate()` For more information see [Using someone else's project](https://pkgdocs.julialang.org/v1/environments/#Using-someone-else's-project). + + + +## Using RLinearAlgebra.jl +For this example let's assume that we have a vector that we wish to compress +using one the RLinearAlgebra.jl `SparseSign` compressor. To do this: + +```@raw html +
      +
    1. + Load RLinearAlgebra.jl and generate your vector +
      using RLinearAlgebra
      +using LinearAlgebra
      +# Specify the size of the vector
      +n = 10000
      +x = rand(n)
      +
    2. +
    3. + Define the SparseSign technique. This requires us to specify a cardinality, + the direction we intend to apply the compressor from, and a compression_dim, + the number of entries we want in the compressed vector. In this instance we + want the cardinality to be Left() and the compression_dim = 20. +
      comp = SparseSign(compression_dim = 20, Cardinality = Left())
      +
    4. +
    5. + Use the complete_compressor function to generate the SparseSignRecipe. +
      S = complete_compressor(comp, A)
      +
    6. +
    7. + Apply the compressor to the vector using the multiplication function +
      Sx = S * x
      +    
      +norm(Sx)
      +
      +norm(x)
      +
    8. +
    +``` + + + + +```@raw html + ``` From 25f40994783c8ce8e5fb9c5c36a61cc99081d398 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 24 Sep 2025 08:43:55 -0500 Subject: [PATCH 16/42] 123 --- docs/src/tutorials/getting_started.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/getting_started.md index dee6bc3c..53881563 100644 --- a/docs/src/tutorials/getting_started.md +++ b/docs/src/tutorials/getting_started.md @@ -72,3 +72,6 @@ sparse_compressor = SparseSign( type=Float64 ) ``` + + + From 6226f6bcc975851d671e421e4820bb08f3cc21c2 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 24 Sep 2025 08:58:19 -0500 Subject: [PATCH 17/42] merge main --- docs/src/tutorials/getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/getting_started.md index 53881563..9c32aee4 100644 --- a/docs/src/tutorials/getting_started.md +++ b/docs/src/tutorials/getting_started.md @@ -74,4 +74,4 @@ sparse_compressor = SparseSign( ``` - + From e83a636a9e5ec9431cbbd928681c00b513fbf830 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 24 Sep 2025 11:08:23 -0500 Subject: [PATCH 18/42] 0.24 --- docs/src/manual/introduction.md | 67 --------------------------- docs/src/tutorials/getting_started.md | 53 +++++++++++---------- 2 files changed, 29 insertions(+), 91 deletions(-) diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md index f814c39a..27bb6f50 100644 --- a/docs/src/manual/introduction.md +++ b/docs/src/manual/introduction.md @@ -199,71 +199,4 @@ norm(x) -```@raw html - -``` diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/getting_started.md index 9c32aee4..39ee3f25 100644 --- a/docs/src/tutorials/getting_started.md +++ b/docs/src/tutorials/getting_started.md @@ -4,29 +4,25 @@ ## Use Case: Solving a Least-Squares Problem with the Sparse Sign Method This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve an overdetermined linear system (i.e., a least-squares problem) of the form: -$$ -\min_{x} \|Ax - b\|_2^2 -$$ -We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and run the solver. + +$$\min_{x} \|Ax - b\|_2^2$$ + +We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve the problem. --- ### 1. Problem Setup -First, we define a specific linear system $Ax = b$. To verify the accuracy of the final result, we will first create a known solution, $x_{\text{true}}$, and then use it to generate the vector $b$. +Let's define a specific linear system $Ax = b$. + +To verify the accuracy of the final result, suppose that we know the true solution of the system, $x_{\text{true}}$, and then use it and a random generated matrix $A$ to generate the vector $b$. * **Matrix `A`**: A random $100 \times 20$ matrix. * **Vector `b`**: Calculated as $b = A x_{\text{true}}$, with dimensions $100 \times 1$. * **Goal**: Find a solution $x$ that is as close as possible to $x_{\text{true}}$. ---- -### 2. Solution Steps - -We will proceed through the following steps to build and run a randomized solver. +To achieve this, we need to import the required libraries and create the matrix `A` and vector `b` as defined above. We will also set an initial guess, `x_init`, for the solver. -#### Step 1: Environment Setup and Problem Definition -First, we need to import the required libraries and create the matrix `A` and vector `b` as defined above. We will also set an initial guess, `x_init`, for the solver. - -```julia +```@example SparseSignExample # Import relevant libraries using RLinearAlgebra, Random, LinearAlgebra @@ -35,26 +31,30 @@ using RLinearAlgebra, Random, LinearAlgebra num_rows, num_cols = 100, 20 # Create the matrix A and a known true solution x_true -A = randn(Float64, num_rows, num_cols) -x_true = randn(Float64, num_cols) +A = randn(Float64, num_rows, num_cols); +x_true = randn(Float64, num_cols); # Calculate the right-hand side vector b from A and x_true -b = A * x_true +b = A * x_true; # Set an initial guess for the solution vector x (typically a zero vector) -x_init = zeros(Float64, num_cols) +x_init = zeros(Float64, num_cols); -println("Problem setup complete:") -println(" - Dimensions of matrix A: ", size(A)) -println(" - Dimensions of vector b: ", size(b)) +println("Dimensions:") +println(" - Matrix A: ", size(A)) +println(" - Vector b: ", size(b)) +println(" - True solution x_true: ", size(x_true)) +println(" - Initial guess x_init: ", size(x_true)) ``` -#### Step 2: Configure the `SparseSign` Compressor -The core idea of randomized methods is to reduce the scale of the original problem using a random "sketch" or "compression" matrix, $S$. Here, we choose `SparseSign` as our `Compressor`. This compressor generates a sparse matrix whose non-zero elements are +1 or -1 (with scaling). +--- +### 2. Configure the `SparseSign` Compressor + +The idea of randomized methods is to reduce the scale of the original problem when the dimention of matrix $A$ is too big, using a random "sketch" or "compression" matrix, $S$. Here, we choose `SparseSign` as our `Compressor`. This compressor generates a sparse matrix whose non-zero elements are +1 or -1 (with scaling). More information can be found [here](@ref SparseSign). We will configure a compression matrix $S$ that compresses the 100 rows of the original system down to 30 rows. -```julia +```@example SparseSignExample # The goal is to compress the 100 rows of A to 30 rows compression_dim = 30 # We want each row of the compression matrix S to have 5 non-zero elements @@ -72,6 +72,11 @@ sparse_compressor = SparseSign( type=Float64 ) ``` +Oops, I suddenly felt 30 rows is not a small enough size, and want to change the dim to 10. Then I can do this: - +```@example SparseSignExample +# Change the dimension of the compressor. Similarly, you can use the idea for other configurations' changes. +sparse_compressor.compression_dim = 10 +sparse_compressor +``` From e5b94a9805a6b94ebc895f724dce1c4b1e2f1a93 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 1 Oct 2025 11:08:52 -0500 Subject: [PATCH 19/42] docs/getting_started.md changes --- docs/src/tutorials/getting_started.md | 147 +++++++++++++++++++++++--- src/Compressors/sparse_sign.jl | 2 +- 2 files changed, 131 insertions(+), 18 deletions(-) diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/getting_started.md index 39ee3f25..e4862752 100644 --- a/docs/src/tutorials/getting_started.md +++ b/docs/src/tutorials/getting_started.md @@ -1,7 +1,4 @@ -# A simple example - - -## Use Case: Solving a Least-Squares Problem with the Sparse Sign Method +# Use Case: Solving a Least-Squares Problem with the Sparse Sign Method This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve an overdetermined linear system (i.e., a least-squares problem) of the form: @@ -10,14 +7,14 @@ $$\min_{x} \|Ax - b\|_2^2$$ We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve the problem. --- -### 1. Problem Setup +## 1. Problem Setup Let's define a specific linear system $Ax = b$. To verify the accuracy of the final result, suppose that we know the true solution of the system, $x_{\text{true}}$, and then use it and a random generated matrix $A$ to generate the vector $b$. -* **Matrix `A`**: A random $100 \times 20$ matrix. -* **Vector `b`**: Calculated as $b = A x_{\text{true}}$, with dimensions $100 \times 1$. +* **Matrix `A`**: A random $1000 \times 20$ matrix. +* **Vector `b`**: Calculated as $b = A x_{\text{true}}$, with dimensions $1000 \times 1$. * **Goal**: Find a solution $x$ that is as close as possible to $x_{\text{true}}$. To achieve this, we need to import the required libraries and create the matrix `A` and vector `b` as defined above. We will also set an initial guess, `x_init`, for the solver. @@ -28,7 +25,7 @@ using RLinearAlgebra, Random, LinearAlgebra # Define the dimensions of the linear system -num_rows, num_cols = 100, 20 +num_rows, num_cols = 1000, 20 # Create the matrix A and a known true solution x_true A = randn(Float64, num_rows, num_cols); @@ -48,22 +45,29 @@ println(" - Initial guess x_init: ", size(x_true)) ``` --- -### 2. Configure the `SparseSign` Compressor +## 2. Create compressors + +In practice, we may encounter a much larger $A$ matrix than what we have here. Solving the problem with such a large matrix can slow down the performance of iterative algorithms that we will use to solve the least square problem. Therefore, we will use a randomized sketching technique to compress the matrix A and the corresponding vector b to a lower dimension, while preserving the essential geometric information of the system. + +Here, we will use the sparse sign method. + +### (a) Configure the `SparseSign` Compressor The idea of randomized methods is to reduce the scale of the original problem when the dimention of matrix $A$ is too big, using a random "sketch" or "compression" matrix, $S$. Here, we choose `SparseSign` as our `Compressor`. This compressor generates a sparse matrix whose non-zero elements are +1 or -1 (with scaling). More information can be found [here](@ref SparseSign). -We will configure a compression matrix $S$ that compresses the 100 rows of the original system down to 30 rows. +We will configure a compression matrix `S` that compresses the 100 rows of the original system down to 30 rows. ```@example SparseSignExample -# The goal is to compress the 100 rows of A to 30 rows -compression_dim = 30 +# The goal is to compress the 100 rows of A to 300 rows +compression_dim = 300 # We want each row of the compression matrix S to have 5 non-zero elements non_zeros = 5 # Configure the SparseSign compressor -# - cardinality=Left(): Indicates the compression matrix S will be left-multiplied with A (SAx = Sb). +# - cardinality=Left(): Indicates the compression matrix S will be +# left-multiplied with A (SAx = Sb). # - compression_dim: The compressed dimension (number of rows). -# - nnz: The number of non-zero elements per row in S. +# - nnz: The number of non-zero elements per column (left)/row (right) in S. # - type: The element type for the compression matrix. sparse_compressor = SparseSign( cardinality=Left(), @@ -72,11 +76,120 @@ sparse_compressor = SparseSign( type=Float64 ) ``` -Oops, I suddenly felt 30 rows is not a small enough size, and want to change the dim to 10. Then I can do this: + +--- +### (b) Build the `SparseSign` recipe + +After configuring the compressor, we need to combine it with our specific matrix `A` to create a `SparseSignRecipe`. This recipe contains the generated sparse matrix and all necessary information to perform the compression efficiently. + +```@example SparseSignExample +# Pass the compressor configuration and the original matrix A to +# create the final compression recipe. +S = complete_compressor(sparse_compressor, A) + +# You can closely look at the compression recipe you created. +println(" - Compression matrix is applied to left or right: ", S.cardinality) +println(" - Compression matrix's number of rows: ", S.n_rows) +println(" - Compression matrix's number of columns: ", S.n_cols) +println(" - The number of nonzeros in each column (left)/row (right) of compression matrix: ", S.nnz) +println(" - Compression matrix's nonzero entry values: ", S.scale) +println(" - Compression matrix: ", S.op) +``` +Oops, I suddenly felt `300` rows is not a small enough size, and want to change the dimension to `10`. Then I need to +respecify the `SparseSign` compressor and build the recipe again: ```@example SparseSignExample -# Change the dimension of the compressor. Similarly, you can use the idea for other configurations' changes. +# Change the dimension of the compressor. Similarly, you can use the idea +# for other configurations' changes. sparse_compressor.compression_dim = 10 -sparse_compressor + +# Rebuild the compressor recipe +S = complete_compressor(sparse_compressor, A) +println("Compression matrix's number of rows: ", S.n_rows) +``` + +### (c) Apply the sparse sign matrix to the system + +While the solver can use the `S` recipe to perform multiplications on-the-fly, it can sometimes be useful to form the compressed system explicitly. We can use `*` for this. + +```@example SparseSignExample +# Form the compressed system SAx = Sb +SA = S * A +Sb = S * b + +println("Dimensions of the compressed system:") +println(" - Matrix SA: ", size(SA)) +println(" - Vector Sb: ", size(Sb)) +``` + +--- +## 3. Create solver + +With the problem and compressor defined, the next step is to choose and configure a solver. Here, we choose to use the +[Kaczmarz solver](@ref Kaczmarz). We configure it by passing in "ingredient" objects for each of its main functions: compressing the system (already done), logging progress, and checking for errors. + +### (a) Configure the logger and stopping rules + +To monitor the solver, we will use a `BasicLogger`. This object will serve two purposes: record the error history, and tell the solver when to stop. + +We will configure it to stop after a maximum of `50` iterations or if the calculated error drops below a tolerance of `1e-6`. + +```@example SparseSignExample +# Configure the logger to control the solver's execution +logger = BasicLogger( + max_it = 50, + threshold = 1e-6, + collection_rate = 5 +) ``` +--- +### (b) Build the Kaczmarz Solver +Now, we assemble our configured components (compressor `S`, logger `L`) into the main Kaczmarz solver object. We will use the default methods for error checking and the sub-solver to be LQ factorization ([LQSolver](@ref LQSolver)). + +```@example SparseSignExample +# Create the Kaczmarz solver object by passing in the ingredients +kaczmarz_solver = Kaczmarz( + compressor = sparse_compressor, + log = logger, + sub_solver = LQSolver() +) +``` +Before we can run the solver, we must call `complete_solver`. This function takes the solver configurations and the specific problem data `A, b, x_init` and creates a `KaczmarzRecipe`. The recipe pre-allocates all the necessary memory buffers for efficient computation. + +```@example SparseSignExample +# Create the solver recipe by combining the solver and the problem data +solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) +``` + +--- +## 4. Solve the Compressed System + +With the recipe fully prepared, we can now call `rsolve!` to run the Kaczmarz algorithm. The function will iterate until the `stopping criterion` in the `logger` is met. + +The `rsolve!` function will modify `x_init` in-place, updating it with the calculated solution. + +```@example SparseSignExample +# Run the solver! +rsolve!(solver_recipe, x_init, A, b) + +# The solution is now stored in the updated x_init vector +solution = x_init; +``` + +--- +## 5. Verify the result + +Finally, let's check how close our calculated solution is to the known `x_true`. We can do this by calculating the Euclidean norm of the difference between the two vectors. A small error norm indicates a successful approximation. + +```@example SparseSignExample +# We can inspect the logger's history to see the convergence +error_history = solver_recipe.log.hist; +println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) +println(" - Final error: ", error_history[solver_recipe.log.record_location - 1]) + +# Calculate the norm of the error +error_norm = norm(solution - x_true) +println(" - Norm of the error between the solution and x_true: ", error_norm) +``` +As you can see, by using the modular Kaczmarz solver, we were able to configure a randomized block-based method and find a solution vector that is very close to the true solution. \ No newline at end of file diff --git a/src/Compressors/sparse_sign.jl b/src/Compressors/sparse_sign.jl index 93de96b2..46568abe 100644 --- a/src/Compressors/sparse_sign.jl +++ b/src/Compressors/sparse_sign.jl @@ -26,7 +26,7 @@ with dimension ``n \\times s``, where ``s`` is the compression dimension that is supplied by the user. In this case, each row of ``S`` is generated independently by the following steps: -1. Randomly choose `nnz` components fo the ``s`` components of the row. Note, `nnz` +1. Randomly choose `nnz` components of the ``s`` components of the row. Note, `nnz` is supplied by the user. 2. For each selected component, randomly set it either to ``-1/\\sqrt{\\text{nnz}}`` or ``1/\\sqrt{\\text{nnz}}`` with equal probability. From 99fa32a1912d39a455ae6ead2f51193c38ba6b04 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 1 Oct 2025 11:14:43 -0500 Subject: [PATCH 20/42] comment out CI.yml --- .github/workflows/CI.yml | 82 ++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6629b541..e1e71c33 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,41 +1,41 @@ -name: CI -on: - push: - branches: - - main - tags: ['*'] - pull_request: - workflow_dispatch: -concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} -jobs: - test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created - actions: write - contents: read - strategy: - fail-fast: false - matrix: - version: - - '1.11' - - '1.6' - - 'pre' - os: - - ubuntu-latest - arch: - - x64 - steps: - - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v2 - with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - - uses: julia-actions/cache@v2 - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-runtest@v1 +# name: CI +# on: +# push: +# branches: +# - main +# tags: ['*'] +# pull_request: +# workflow_dispatch: +# concurrency: +# # Skip intermediate builds: always. +# # Cancel intermediate builds: only if it is a pull request build. +# group: ${{ github.workflow }}-${{ github.ref }} +# cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +# jobs: +# test: +# name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} +# runs-on: ${{ matrix.os }} +# timeout-minutes: 60 +# permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created +# actions: write +# contents: read +# strategy: +# fail-fast: false +# matrix: +# version: +# - '1.11' +# - '1.6' +# - 'pre' +# os: +# - ubuntu-latest +# arch: +# - x64 +# steps: +# - uses: actions/checkout@v4 +# - uses: julia-actions/setup-julia@v2 +# with: +# version: ${{ matrix.version }} +# arch: ${{ matrix.arch }} +# - uses: julia-actions/cache@v2 +# - uses: julia-actions/julia-buildpkg@v1 +# - uses: julia-actions/julia-runtest@v1 From bba815aa7527a12772764a2ce3dc75813270078d Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 1 Oct 2025 11:24:57 -0500 Subject: [PATCH 21/42] delete md --- .github/PULL_REQUEST_TEMPLATE.md | 21 ------------ .github/pull_request_template.md | 57 -------------------------------- 2 files changed, 78 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/pull_request_template.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index d343cf8f..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,21 +0,0 @@ -# Title Formatting -Please format your Pull Request title according to the Conventional Commits specification. This is crucial for automating our release process. - -Based on the changes in your PR, please use one of the following prefixes in your title: - -- fix: for a bug fix. - -Example: fix: correct user authentication flow - -- feat: for a new feature. - -Example: feat: add user profile page - -- For breaking changes, you must signify this in one of two ways: - - - Append a ! after the type in the title. - - Example: feat!: remove user endpoint - - - Include a footer in your PR description below that starts with BREAKING CHANGE:. - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 142ccd0e..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,57 +0,0 @@ - - - -## Description - -Fixes # - -## Motivation and Context - - - -## How has this been tested - - - -## Types of changes - - -- [ ] CI -- [ ] Docs -- [ ] Feature -- [ ] Fix -- [ ] Performance -- [ ] Refactor -- [ ] Style -- [ ] Test -- [ ] Other (**use sparingly**): - -## Checklists: - - -**Code and Comments** -If this PR includes modification to the code base, please select all that apply. -- [ ] My code follows the code style of this project. -- [ ] I have updated all package dependencies (if any). -- [ ] I have included all relevant files to realize the functionality of the PR. -- [ ] I have exported relevant functionality (if any). - -**API Documentation** -- [ ] For every exported function (if any), I have included a detailed docstring. -- [ ] I have checked the spelling and grammar of all docstring updates through an external tool. -- [ ] I have checked that the docstring's function signature is correctly formatted and has all arguments. -- [ ] I have checked that the docstring's list of arguments, fields, or return values match the function. -- [ ] I have compiled the docs locally and read through all docstring updates to check for errors. - -**Manual Documentation** -- [ ] I have checked the spelling and grammar of all manual updates through an external tool. -- [ ] Any code included in the docstring is tested using doc tests to ensure consistency. -- [ ] I have compiled the docs locally and read through all manual updates to check for errors. - -**Testing** -- [ ] I have added unit tests to cover my changes. (For Macros, be sure to check - [@code_lowered](https://docs.julialang.org/en/v1/stdlib/InteractiveUtils/#InteractiveUtils.@code_lowered) and - [@code_typed](https://docs.julialang.org/en/v1/stdlib/InteractiveUtils/#InteractiveUtils.@code_typed)) -- [ ] All new and existing tests passed. -- [ ] I have achieved sufficient code coverage. - From 0827c799ce569ae6fd7cac459c636a1097507255 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 09:08:28 -0500 Subject: [PATCH 22/42] delete CI --- .github/workflows/CI.yml | 41 -------- .github/workflows/CompatHelper.yml | 16 --- .github/workflows/HM_Bump_version.yml | 117 ---------------------- .github/workflows/HM_Changelog_update.yml | 51 ---------- .github/workflows/HM_TagBot.yml | 25 ----- .github/workflows/TagBot.yml | 31 ------ 6 files changed, 281 deletions(-) delete mode 100644 .github/workflows/CI.yml delete mode 100644 .github/workflows/CompatHelper.yml delete mode 100644 .github/workflows/HM_Bump_version.yml delete mode 100644 .github/workflows/HM_Changelog_update.yml delete mode 100644 .github/workflows/HM_TagBot.yml delete mode 100644 .github/workflows/TagBot.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml deleted file mode 100644 index e1e71c33..00000000 --- a/.github/workflows/CI.yml +++ /dev/null @@ -1,41 +0,0 @@ -# name: CI -# on: -# push: -# branches: -# - main -# tags: ['*'] -# pull_request: -# workflow_dispatch: -# concurrency: -# # Skip intermediate builds: always. -# # Cancel intermediate builds: only if it is a pull request build. -# group: ${{ github.workflow }}-${{ github.ref }} -# cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} -# jobs: -# test: -# name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} -# runs-on: ${{ matrix.os }} -# timeout-minutes: 60 -# permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created -# actions: write -# contents: read -# strategy: -# fail-fast: false -# matrix: -# version: -# - '1.11' -# - '1.6' -# - 'pre' -# os: -# - ubuntu-latest -# arch: -# - x64 -# steps: -# - uses: actions/checkout@v4 -# - uses: julia-actions/setup-julia@v2 -# with: -# version: ${{ matrix.version }} -# arch: ${{ matrix.arch }} -# - uses: julia-actions/cache@v2 -# - uses: julia-actions/julia-buildpkg@v1 -# - uses: julia-actions/julia-runtest@v1 diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml deleted file mode 100644 index cba9134c..00000000 --- a/.github/workflows/CompatHelper.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: CompatHelper -on: - schedule: - - cron: 0 0 * * * - workflow_dispatch: -jobs: - CompatHelper: - runs-on: ubuntu-latest - steps: - - name: Pkg.add("CompatHelper") - run: julia -e 'using Pkg; Pkg.add("CompatHelper")' - - name: CompatHelper.main() - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} - run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/HM_Bump_version.yml b/.github/workflows/HM_Bump_version.yml deleted file mode 100644 index eff43fb3..00000000 --- a/.github/workflows/HM_Bump_version.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: 'Propose Version Bump' - -on: - pull_request: - types: - - closed - branches: - - main - -jobs: - propose-bump: - # This job only runs when a PR is successfully merged. - if: github.event.pull_request.merged == true - - runs-on: ubuntu-latest - - steps: - # Step 1: Checkout the repository code. - - name: 'Checkout Repository' - uses: actions/checkout@v4 - with: - fetch-depth: 0 # We need history to find the commit by its SHA - token: ${{ secrets.PAT_FOR_BUMP_PR }} - - # Step 2: Get the merge commit message - - name: 'Get Merge Commit Message' - id: commit_message - run: | - # Use git log to get the full message of the exact merge commit SHA. - # The -n 1 flag ensures we only get one commit. - # The output is set for the next step to use. - MSG=$(git log --format=%B -n 1 ${{ github.event.pull_request.merge_commit_sha }}) - echo "message<> $GITHUB_OUTPUT - echo "$MSG" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - # Step 3: Determine bump type and update the Project.toml file. - - name: 'Calculate Bump and Update Version File' - id: bump_logic - shell: python - run: | - import os - import re - import sys - - # Read the commit message from the previous step's output - commit_message = os.environ['COMMIT_MESSAGE'] - - # Determine bump type based on the Angular Conventional Commit spec - bump_type = '' - first_line = commit_message.splitlines()[0] - - if re.search(r'BREAKING CHANGE:', commit_message) or re.match(r'^\w+(\([\w-]+\))?!:', first_line): - bump_type = 'major' - elif re.match(r'^feat(\([\w-]+\))?:', first_line): - bump_type = 'minor' - elif re.match(r'^fix(\([\w-]+\))?:', first_line): - bump_type = 'patch' - - if not bump_type: - print('No version bump required for this commit.') - print('::set-output name=bumped::false') - sys.exit(0) - - print(f'Determined bump type: {bump_type}') - - try: - with open('Project.toml', 'r+') as f: - content = f.read() - version_match = re.search(r'^version\s*=\s*\"(\d+)\.(\d+)\.(\d+)\"', content, re.M) - - if not version_match: - print('Error: Could not find version in Project.toml') - sys.exit(1) - - major, minor, patch = map(int, version_match.groups()) - - if bump_type == 'major': major, minor, patch = major + 1, 0, 0 - elif bump_type == 'minor': minor, patch = minor + 1, 0 - elif bump_type == 'patch': patch += 1 - - new_version = f'{major}.{minor}.{patch}' - print(f'Bumping version to {new_version}') - - new_content = re.sub(r'^version\s*=\s*\".*\"', f'version = "{new_version}"', content, count=1, flags=re.M) - - f.seek(0) - f.write(new_content) - f.truncate() - - print(f'::set-output name=new_version::{new_version}') - print('::set-output name=bumped::true') - - except FileNotFoundError: - print('Error: Project.toml not found.') - sys.exit(1) - env: - # Pass the message from the "Get Merge Commit Message" step - COMMIT_MESSAGE: ${{ steps.commit_message.outputs.message }} - - # Step 4: Create a new pull request with the version bump. - - name: 'Create Version Bump PR' - if: steps.bump_logic.outputs.bumped == 'true' - uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.PAT_FOR_BUMP_PR }} - commit-message: "chore: bump version to ${{ steps.bump_logic.outputs.new_version }}" - title: "chore: Propose version bump to ${{ steps.bump_logic.outputs.new_version }}" - body: | - This PR was automatically generated to propose the next version for the Julia package. - - The recommended version bump is to **${{ steps.bump_logic.outputs.new_version }}**. This was determined based on the conventional commit message of the last merged PR. - - Please review and merge to apply the version update. - branch: "chore/version-bump-${{ steps.bump_logic.outputs.new_version }}" - labels: 'automated-pr, version_update' - delete-branch: true \ No newline at end of file diff --git a/.github/workflows/HM_Changelog_update.yml b/.github/workflows/HM_Changelog_update.yml deleted file mode 100644 index fb632ee0..00000000 --- a/.github/workflows/HM_Changelog_update.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Propose Changelog Update - -on: - # JuliaTagBot activated - issue_comment: - types: - - created - workflow_dispatch: - -jobs: - propose-changelog: - # Only when TagBot is activated, change the CHANGELOG - if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' - runs-on: ubuntu-latest - steps: - # Step 1 - - name: Checkout repository - uses: actions/checkout@v4 - with: - # All git history - fetch-depth: 0 - - # Step 2 - - name: Generate cumulative CHANGELOG.md - id: changelog_generator - uses: TriPSs/conventional-changelog-action@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - # output-file: "CHANGELOG.md" - input-file: "CHANGELOG.md" - version-file: "./Project.toml" - skip-commit: true - skip-tag: true - git-push: false - - # Step 3 - - name: Create Pull Request with updated CHANGELOG.md - if: steps.changelog_generator.outputs.skipped == 'false' - uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.PAT_FOR_CHANGELOG }} - commit-message: "docs(changelog): update CHANGELOG.md" - title: "Docs: Update CHANGELOG.md for new release" - body: | - This PR was automatically generated to update the `CHANGELOG.md` file for the upcoming release. - - Please review the changes and merge to trigger the final release. - # Branch for changelog - branch: "chore/update-changelog" - # If exist, update - delete-branch: true diff --git a/.github/workflows/HM_TagBot.yml b/.github/workflows/HM_TagBot.yml deleted file mode 100644 index d9c0ddea..00000000 --- a/.github/workflows/HM_TagBot.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Run TagBot on Merge - -on: - pull_request: - types: - - closed - branches: - - main - -jobs: - tagbot: - # When pr is merged and the branch's name is 'chore/update-changelog' - if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'chore/update-changelog' - runs-on: ubuntu-latest - steps: - # Step 1 - - name: Checkout repository - uses: actions/checkout@v4 - - # Step 2 - - name: Run TagBot - uses: JuliaRegistries/TagBot@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml deleted file mode 100644 index 0cd3114e..00000000 --- a/.github/workflows/TagBot.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: TagBot -on: - issue_comment: - types: - - created - workflow_dispatch: - inputs: - lookback: - default: "3" -permissions: - actions: read - checks: read - contents: write - deployments: read - issues: read - discussions: read - packages: read - pages: read - pull-requests: read - repository-projects: read - security-events: read - statuses: read -jobs: - TagBot: - if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' - runs-on: ubuntu-latest - steps: - - uses: JuliaRegistries/TagBot@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ssh: ${{ secrets.DOCUMENTER_KEY }} From ff201f0b07977c2dbd7f5912cdbc1845a4606949 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 09:10:10 -0500 Subject: [PATCH 23/42] pr template --- .github/dependabot.yml | 7 ---- .github/pull_request_template.md | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) delete mode 100644 .github/dependabot.yml create mode 100644 .github/pull_request_template.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 700707ce..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" # Location of package manifests - schedule: - interval: "weekly" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..142ccd0e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,57 @@ + + + +## Description + +Fixes # + +## Motivation and Context + + + +## How has this been tested + + + +## Types of changes + + +- [ ] CI +- [ ] Docs +- [ ] Feature +- [ ] Fix +- [ ] Performance +- [ ] Refactor +- [ ] Style +- [ ] Test +- [ ] Other (**use sparingly**): + +## Checklists: + + +**Code and Comments** +If this PR includes modification to the code base, please select all that apply. +- [ ] My code follows the code style of this project. +- [ ] I have updated all package dependencies (if any). +- [ ] I have included all relevant files to realize the functionality of the PR. +- [ ] I have exported relevant functionality (if any). + +**API Documentation** +- [ ] For every exported function (if any), I have included a detailed docstring. +- [ ] I have checked the spelling and grammar of all docstring updates through an external tool. +- [ ] I have checked that the docstring's function signature is correctly formatted and has all arguments. +- [ ] I have checked that the docstring's list of arguments, fields, or return values match the function. +- [ ] I have compiled the docs locally and read through all docstring updates to check for errors. + +**Manual Documentation** +- [ ] I have checked the spelling and grammar of all manual updates through an external tool. +- [ ] Any code included in the docstring is tested using doc tests to ensure consistency. +- [ ] I have compiled the docs locally and read through all manual updates to check for errors. + +**Testing** +- [ ] I have added unit tests to cover my changes. (For Macros, be sure to check + [@code_lowered](https://docs.julialang.org/en/v1/stdlib/InteractiveUtils/#InteractiveUtils.@code_lowered) and + [@code_typed](https://docs.julialang.org/en/v1/stdlib/InteractiveUtils/#InteractiveUtils.@code_typed)) +- [ ] All new and existing tests passed. +- [ ] I have achieved sufficient code coverage. + From 43bec29f4d454fbca220785f6fa7c180fc60f34c Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 09:12:46 -0500 Subject: [PATCH 24/42] Update docs/src/tutorials/getting_started.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/src/tutorials/getting_started.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/getting_started.md index e4862752..d05d48e5 100644 --- a/docs/src/tutorials/getting_started.md +++ b/docs/src/tutorials/getting_started.md @@ -95,9 +95,7 @@ println(" - The number of nonzeros in each column (left)/row (right) of compress println(" - Compression matrix's nonzero entry values: ", S.scale) println(" - Compression matrix: ", S.op) ``` -Oops, I suddenly felt `300` rows is not a small enough size, and want to change the dimension to `10`. Then I need to -respecify the `SparseSign` compressor and build the recipe again: - +If the compression dimension of `300` rows is considered too large, it can be changed to `10` by updating the compressor configuration and rebuilding the recipe as follows: ```@example SparseSignExample # Change the dimension of the compressor. Similarly, you can use the idea # for other configurations' changes. From 3198fad8441e8405b969b8a1e9f0977dc2c09131 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 09:13:15 -0500 Subject: [PATCH 25/42] Update docs/src/dev/style_guide.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/src/dev/style_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/dev/style_guide.md b/docs/src/dev/style_guide.md index 2742fafd..bbd1c0a3 100644 --- a/docs/src/dev/style_guide.md +++ b/docs/src/dev/style_guide.md @@ -1,6 +1,6 @@ # Style Guide ```@contents -Pages=["styleguide.md"] +Pages=["style_guide.md"] ``` When writing code for `RLinearAlgebra.jl` we expect the code to be written in accordance with the [BLUE](https://github.com/JuliaDiff/BlueStyle) style. From c6caee7ff8c0dff3b606bf3f1fe6c178f887c440 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 09:13:33 -0500 Subject: [PATCH 26/42] Update docs/src/manual/introduction.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/src/manual/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md index 27bb6f50..5e6c328d 100644 --- a/docs/src/manual/introduction.md +++ b/docs/src/manual/introduction.md @@ -179,7 +179,7 @@ x = rand(n) the direction we intend to apply the compressor from, and a compression_dim, the number of entries we want in the compressed vector. In this instance we want the cardinality to be Left() and the compression_dim = 20. -
    comp = SparseSign(compression_dim = 20, Cardinality = Left())
    +
    comp = SparseSign(compression_dim = 20, cardinality = Left())
  2. Use the complete_compressor function to generate the SparseSignRecipe. From 45f413fdd1ca60eb3660400e736863bcf7bacaaf Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 09:14:40 -0500 Subject: [PATCH 27/42] Update docs/src/tutorials/getting_started.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/src/tutorials/getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/getting_started.md index d05d48e5..87f042ce 100644 --- a/docs/src/tutorials/getting_started.md +++ b/docs/src/tutorials/getting_started.md @@ -58,7 +58,7 @@ The idea of randomized methods is to reduce the scale of the original problem wh We will configure a compression matrix `S` that compresses the 100 rows of the original system down to 30 rows. ```@example SparseSignExample -# The goal is to compress the 100 rows of A to 300 rows +# The goal is to compress the 1000 rows of A to 300 rows compression_dim = 300 # We want each row of the compression matrix S to have 5 non-zero elements non_zeros = 5 From 2b5de0841ac20b03a9fba5f3876bf40ed8e668a0 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 09:28:44 -0500 Subject: [PATCH 28/42] docs/make pr non-overlap --- docs/make.jl | 3 - docs/src/manual/introduction.md | 202 -------------------------------- readme.md | 1 - 3 files changed, 206 deletions(-) delete mode 100644 docs/src/manual/introduction.md diff --git a/docs/make.jl b/docs/make.jl index 2aa2f2a0..41fba420 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -20,9 +20,6 @@ makedocs( "Introduction" => "tutorials/introduction.md", "Getting started" => "tutorials/getting_started.md" ], - "Manual" => [ - "Introduction" => "manual/introduction.md", - ], "API Reference" => [ "Compressors" => [ "Compressors Overview" => "api/compressors.md", diff --git a/docs/src/manual/introduction.md b/docs/src/manual/introduction.md deleted file mode 100644 index 27bb6f50..00000000 --- a/docs/src/manual/introduction.md +++ /dev/null @@ -1,202 +0,0 @@ -# A library for exploring Randomized Linear Algebra -If you are here, you probably know that Linear Algebra is foundational to data science and -scientific computing. You also probably know Linear Algebra routines dominate the -computational cost of many of the algorithms in these fields. Thus, improving the -scalability of these algorithms requires more scalable Linear Algebra techniques. - -An exciting new set of techniques that offers such improved scalability of -Linear Algebra techniques are Randomized Linear Algebra techniques. -In general, Randomized Linear Algebra techniques aim to achieve this improved -scalability by forming a representative sample of a matrix and performing -operations on that sample. In some circumstances operating on this sample -can offer profound speed-ups as can see in the following example -where a technique known as the RandomizedSVD (see [halko2011finding](@cite)) -is used to compute a rank-20 approximation to ``3000 \times 3000`` matrix -``A`` in place of a truncated SVD. Compared to computing the SVD and -truncating it, the RandomizedSVD is 100 times faster and just as accurate -as the truncated SVD. - -```julia -using RLinearAlgebra -using LinearAlgebra - -# Generate a rank-20 matrix -A = randn(3000, 20) * randn(20, 3000); - -@time U,S,V = svd(A); -# 4.566639 seconds (13 allocations: 412.354 MiB, 0.92% gc time) - -# Form the RandomizedSVD data structure -technique = RandomizedSVD( - compressor = Gaussian(compression_dim= 22, cardinality=Right()), - orthogonalize=false, - power_its = 0 -) - -# Take the RandomizedSVD of A -@time rec = rapproximate(technique, A); -# 0.050950 seconds (39 allocations: 5.069 MiB) - -# Take the norm of the difference between the RandomizedSVD and TrunctatedSVD at rank 22 -norm(rec.U * Diagonal(rec.S) * rec.V' - U[:,1:22] * Diagonal(S[1:22]) * (V[:, 1:22])') -# 6.914995919005829e-11 -``` - -Over the years, numerous Randomized Linear Algebra approaches have been proposed not only -for basic linear tasks such as computing a low-rank approximation to a matrix, solving -a linear system, or solving a least squares problem, but also for how obtain a -representative sample of the matrix itself. To this point, a single easy to prototype -library has not been developed to bring these techniques to the masses. RLinearAlgebra.jl is designed to be exactly such an easy-to-use library. - -In particular, RLinearAlgebra.jl leverages a modular design to allow you -to easily test Randomized Linear Algebra routines under a wide-range of parameter choices. RLinearAlgebra.jl provides routines for two core Linear Algebra tasks: finding a solution to -a linear system via ``Ax=b`` or ``\min_x \|Ax - b\|`` and forming a low rank -approximation to a matrix, ``\hat A`` where ``\hat A \approx A``. The solution to a linear -system appears everywhere: Optimization, Tomography, Statistics, Scientific Computing, Machine -Learning, etc. The low-rank approximation problem has only become more relevant in recent years -owing to the drastic increase in matrix sizes. It has been widely used in Statistics via PCA, but -also has become increasingly more relevant in all the fields where solving a linear system is -relevant. - -This manual will walk you through the use of the RLinearAlgebra.jl library. The remainder of this -section will be focused on providing an overview of the common design elements in the library, -and information about how to get started using the library. - -## Overview of the Library -The library is based on two data structure types: **techniques** that contain the parameters -that define a particular method and **technique recipes** that contain these parameters and -the necessary preallocations for the desired technique to be executed efficiently. As the -user you only need to define the techniques and the library will do all the work to form -the recipes for you. If you wish to convert a technique into a technique recipe you can use -the `complete_[technique type]` function. - -### The Technique Types -With an understanding of the basic structures in the library, one may wonder, what -types of techniques are there? First, there are the techniques for solving the linear -system, `Solvers` and techniques for forming a low-rank approximation to a matrix, -`Approximators`. Both `Solvers` and `Approximators` achieve speedups by working on -compressed forms (often known as sketched or sampled) of the linear system, techniques that -compress the linear system are known as `Compressors`. Aside from these global techniques, -there are also techniques that are specific to `Solvers`, which include: - -1. `SubSolvers`, techniques that solve the inner (compressed) linear system. -2. `Loggers`, techniques that log information and determine whether a stopping criterion has - been met. -3. `SolverError`, a technique that computes the error of a current iterate of a solver. - -Similarly, `Approximators` have their own specific techniques, which include: - -1. `ApproximorError`, a technique that computes the error of an `Approximator`. - -With all these technique structures, you may be wondering, what functions -can I call on these structures? Well, the answer is not many. As is -summarized in the following table. - -| Technique | Parent Technique | Function Calls | -| ---------- | ------------------ | ----------------------------------- | -|`Approximator` | None | `complete_approximator`,`rapproximate`| -|`Compressor` | None | `complete_compressor` | -|`Solver` | None | `complete_solver`, `rsolve` | -|`ApproximatorError`| `Approximator` | `complete_approximator_error` | -|`Logger` | `Solver` | `complete_logger` | -|`SolverError` | `Solver` | `complete_solver_error` | -|`SubSolver` | `Solver` | `complete_sub_solver` | - - -From the above table we can see that essentially all you are able to do unless you are using -an `Approximator` or a `Solver` is complete the technique. The reason being that all the -technique structures contain only information about algorithmic parameters that require no -information about the linear system. The recipes on the other hand have all the information -required to use a technique including the required pre-allocated memory. We determine the -preallocations for the Recipes by merging the parameter information of the technique -structures with the matrix and linear system information via the `complete_[technique]` -functions, which is the only function that you can call when you have a technique structure. -There is a special exception for `rsolve` and `rapproximate` because they implicitly call -all the necessary completes to form the appropriate recipe. The bottom line is that do -anything useful you will need a recipe. - -### The Recipe Types -Every technique can be transformed into a recipe. As has been stated before, what makes the -recipes different is that they contain all the required memory allocations. These allocations can -only be determined from once the matrix is known. As a user, -all you have to know is that as soon as you have a recipe you can do a lot. As can be seen -in the following table. - -| Technique Recipe | Parent Recipe | Function Calls | -|----------------- |------------------| ---------------------------------| -|`Approximator` | None | `mul!`, `rapproximate!` | -|`Compressor` | None | `mul!`,`update_compressor!` | -|`Solver` | None | `rsolve!` | -|`ApproximatorError`| `Approximator` | `compute_approximator_error` | -|`Logger` | `Solver` | `reset_logger!`, `update_logger!`| -|`SolverError` | `Solver` | `compute_error` | -|`SubSolver` | `Solver` | `update_sub_solver!`,`ldiv!` | - -Instead of providing -a different function for each method associated with these tasks, RLinearAlgebra.jl -leverages the multiple-dispatch functionality of Julia to allow all linear systems and -least squares problems to be solved calling the function -`rsolve(solver::Solver, x::AbstractVector, A::AbstractMatrix, b::AbstractVector)` -and all matrices to be approximated by calling the function -`rapproximate(approximator::Approximator, A::AbstractMatrix)`. Under this design, changing -the routine for solving your linear system or approximate your matrix is as -simple as changing the`solver` or `approximator` arguments. - -## Installing RLinearAlgebra -Currently, RLinearAlgebra.jl is not registered in Julia's official package registry. -It can be installed by writing in the REPL: -```julia -] add https://github.com/numlinalg/RLinearAlgebra.jl.git -``` -It can also be cloned into a local directory and installed by: -1. `cd` into the local project directory -2. Call `git clone https://github.com/numlinalg/RLinearAlgebra.jl.git` -3. Run Julia -4. Call `using Pkg` -5. Call `Pkg.activate(RLinearAlgebra.jl)` -6. Call `Pkg.instantiate()` - -For more information see [Using someone else's project](https://pkgdocs.julialang.org/v1/environments/#Using-someone-else's-project). - - - -## Using RLinearAlgebra.jl -For this example let's assume that we have a vector that we wish to compress -using one the RLinearAlgebra.jl `SparseSign` compressor. To do this: - -```@raw html -
      -
    1. - Load RLinearAlgebra.jl and generate your vector -
      using RLinearAlgebra
      -using LinearAlgebra
      -# Specify the size of the vector
      -n = 10000
      -x = rand(n)
      -
    2. -
    3. - Define the SparseSign technique. This requires us to specify a cardinality, - the direction we intend to apply the compressor from, and a compression_dim, - the number of entries we want in the compressed vector. In this instance we - want the cardinality to be Left() and the compression_dim = 20. -
      comp = SparseSign(compression_dim = 20, Cardinality = Left())
      -
    4. -
    5. - Use the complete_compressor function to generate the SparseSignRecipe. -
      S = complete_compressor(comp, A)
      -
    6. -
    7. - Apply the compressor to the vector using the multiplication function -
      Sx = S * x
      -    
      -norm(Sx)
      -
      -norm(x)
      -
    8. -
    -``` - - - - - diff --git a/readme.md b/readme.md index ba9a37e7..715a145d 100644 --- a/readme.md +++ b/readme.md @@ -3,7 +3,6 @@ [![Runtests](https://github.com/numlinalg/RLinearAlgebra.jl/actions/workflows/Runtests.yml/badge.svg)](https://github.com/numlinalg/RLinearAlgebra.jl/actions/workflows/Runtests.yml) [![codecov](https://codecov.io/github/numlinalg/RLinearAlgebra.jl/branch/v0.2-main/graph/badge.svg?token=GI7YUNM4YO)](https://codecov.io/github/numlinalg/RLinearAlgebra.jl) [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) - RLinearAlgebra is a Julia package that implements standard Randomized Linear Algebra algorithms and provides means for performance comparison. From 34c0dcc9de4dd14f112b36b0574426d0e8ed5d3b Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 10:18:53 -0500 Subject: [PATCH 29/42] docs/refine LS example --- .github/workflows/Documenter.yml | 1 - docs/make.jl | 2 +- docs/src/tutorials/introduction.md | 2 +- docs/src/tutorials/{getting_started.md => least_square.md} | 6 ++++-- 4 files changed, 6 insertions(+), 5 deletions(-) rename docs/src/tutorials/{getting_started.md => least_square.md} (97%) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 09114f19..70279c65 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -9,7 +9,6 @@ on: branches: - master - main - - v0.2-main jobs: build: runs-on: ubuntu-latest diff --git a/docs/make.jl b/docs/make.jl index 41fba420..87d92006 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,7 +18,7 @@ makedocs( "Home" => "index.md", "Tutorials" => [ "Introduction" => "tutorials/introduction.md", - "Getting started" => "tutorials/getting_started.md" + "Least square" => "tutorials/least_square.md" ], "API Reference" => [ "Compressors" => [ diff --git a/docs/src/tutorials/introduction.md b/docs/src/tutorials/introduction.md index 1b219eaa..b80f777d 100644 --- a/docs/src/tutorials/introduction.md +++ b/docs/src/tutorials/introduction.md @@ -1,7 +1,7 @@ # Introduction The purpose of these tutorials is to use examples help - new users quickly get hands on the usage of RLinearAlgebra. + new users quickly get hands on the usage of `RLinearAlgebra.jl`. ## How these tutorials are structured diff --git a/docs/src/tutorials/getting_started.md b/docs/src/tutorials/least_square.md similarity index 97% rename from docs/src/tutorials/getting_started.md rename to docs/src/tutorials/least_square.md index 87f042ce..bcaba598 100644 --- a/docs/src/tutorials/getting_started.md +++ b/docs/src/tutorials/least_square.md @@ -88,6 +88,7 @@ After configuring the compressor, we need to combine it with our specific matrix S = complete_compressor(sparse_compressor, A) # You can closely look at the compression recipe you created. +println("Configurations of compression matrix:") println(" - Compression matrix is applied to left or right: ", S.cardinality) println(" - Compression matrix's number of rows: ", S.n_rows) println(" - Compression matrix's number of columns: ", S.n_cols) @@ -97,7 +98,7 @@ println(" - Compression matrix: ", S.op) ``` If the compression dimension of `300` rows is considered too large, it can be changed to `10` by updating the compressor configuration and rebuilding the recipe as follows: ```@example SparseSignExample -# Change the dimension of the compressor. Similarly, you can use the idea +# Change the dimension of the compressor. Similarly, you can use the same idea # for other configurations' changes. sparse_compressor.compression_dim = 10 @@ -130,7 +131,8 @@ With the problem and compressor defined, the next step is to choose and configur To monitor the solver, we will use a `BasicLogger`. This object will serve two purposes: record the error history, and tell the solver when to stop. -We will configure it to stop after a maximum of `50` iterations or if the calculated error drops below a tolerance of `1e-6`. +We will configure it to stop after a maximum of `50` iterations or if the calculated error drops below a tolerance of `1e-6`. And we use `collection_rate = 5` +to configure the frequence of error recording to be every $5$ steps. ```@example SparseSignExample # Configure the logger to control the solver's execution From 1909a3e8a800b85d5c3b61675e0d7785edf54edc Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 8 Oct 2025 10:24:33 -0500 Subject: [PATCH 30/42] docs/tutorials/intro --- docs/src/tutorials/introduction.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/tutorials/introduction.md b/docs/src/tutorials/introduction.md index b80f777d..0e36ff5c 100644 --- a/docs/src/tutorials/introduction.md +++ b/docs/src/tutorials/introduction.md @@ -3,9 +3,10 @@ The purpose of these tutorials is to use examples help new users quickly get hands on the usage of `RLinearAlgebra.jl`. -## How these tutorials are structured +## How tutorials are structured -- Start from the solving $$Ax = b$$ +Problem sets: +- Least squre problem, solving $$\min_{x} \|Ax - b\|_2^2$$ From 80aeb6b947983e3bd67b93234366c79865261dc2 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 15 Oct 2025 09:04:13 -0500 Subject: [PATCH 31/42] docs/purify the branch --- .github/workflows/Documenter.yml | 1 - docs/make.jl | 4 +- docs/src/custom_html.css | 7 - docs/src/dev/checklists.md | 55 -- docs/src/dev/checklists/compressors.md | 33 - docs/src/dev/checklists/loggers.md | 47 -- docs/src/dev/contributing.md | 37 - docs/src/dev/design.md | 757 ------------------ docs/src/dev/style_guide.md | 60 -- .../{least_square.md => least_squares.md} | 0 10 files changed, 2 insertions(+), 999 deletions(-) delete mode 100644 docs/src/custom_html.css delete mode 100644 docs/src/dev/checklists.md delete mode 100644 docs/src/dev/checklists/compressors.md delete mode 100644 docs/src/dev/checklists/loggers.md delete mode 100644 docs/src/dev/contributing.md delete mode 100644 docs/src/dev/design.md delete mode 100644 docs/src/dev/style_guide.md rename docs/src/tutorials/{least_square.md => least_squares.md} (100%) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 70279c65..b12a6305 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -7,7 +7,6 @@ on: - 'v*' pull_request: branches: - - master - main jobs: build: diff --git a/docs/make.jl b/docs/make.jl index 87d92006..dfb41ec8 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,7 +18,7 @@ makedocs( "Home" => "index.md", "Tutorials" => [ "Introduction" => "tutorials/introduction.md", - "Least square" => "tutorials/least_square.md" + "Least squares" => "tutorials/least_squares.md" ], "API Reference" => [ "Compressors" => [ @@ -61,6 +61,6 @@ makedocs( # for more information. deploydocs( repo = "github.com/numlinalg/RLinearAlgebra.jl", - devbranch = "master", # master's newest commit will become dev + devbranch = "main", # master's newest commit will become dev push_preview = true # pull requests to the master will become available ) diff --git a/docs/src/custom_html.css b/docs/src/custom_html.css deleted file mode 100644 index 43a97745..00000000 --- a/docs/src/custom_html.css +++ /dev/null @@ -1,7 +0,0 @@ -/* This file is aim to customize html format */ - -/* Add margin at the beginning and the end of code blocks */ -li pre { - margin-top: 1em; - margin-bottom: 1em; -} \ No newline at end of file diff --git a/docs/src/dev/checklists.md b/docs/src/dev/checklists.md deleted file mode 100644 index b9bdd4d4..00000000 --- a/docs/src/dev/checklists.md +++ /dev/null @@ -1,55 +0,0 @@ -# Overview -The purpose of this page is to offer checklists of tasks, for everyone who help to improve the package. These checklists are organized by method. - -## Create issue and use it! -Please add a brief introduction when you create an issue. After the introduction, copy and paste the corresponding checklist. -You can check the -boxes after you finish each steps, to help you contribute smoothly! - -For example, If I want to add a `moving_average` logging method to the package, I will create an issue as follows: -``` -# Introduction -Add the moving average method for both full and sketching residual. For more details, please see [the paper](https://arxiv.org/abs/2208.04989). -# Checklist -## Implementation -1. Method's core codes (`src/Solvers/Loggers`): - - [ ] Create a file in the directory `src/Solvers/Loggers`. For example, `src/Solvers/Loggers/basic_logger.jl`. - - [ ] Create a `Logger` struct with `max_it`, `collection_rate`, `threshold_info`, `stopping_criterion`, any other method-needed parameters, and argument validations to check invalid inputs. For example, `BasicLogger<:Logger`. - - [ ] Create a constructor with keyword default values for your `Logger` struct. For example, - ``` - BasicLogger(; - max_it = 0, - collection_rate = 1, - threshold = 0.0, - stopping_criterion = threshold_stop - ) = BasicLogger(max_it, collection_rate, threshold, stopping_criterion) - ``` - - [ ] Add documentation for this `Logger` struct, with mainly 5 parts: brief introduction, fields introduction, constructor and its keywords, what the constructor returns, and what the argument validations throw. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. - - [ ] Create a `LoggerRecipe` struct that uses the parameters from the `Logger` struct to preallocate memory. For example, `BasicLoggerRecipe{F<:Function} <: LoggerRecipe`. - - [ ] Create a `complete_logger` function that takes the `logger` struct as an input, and returns the `LoggerRecipe` struct you defined last step. For example, `complete_logger(logger::BasicLogger)`. - - [ ] Add documentation for this `LoggerRecipe` struct, with mainly 2 parts: brief introduction, fields introduction. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. - - [ ] Create a `update_logger!` function to log the errors as the iteration goes on, and stop the logging with convergence status or the maximum iteration limit. For example, - ``` - update_logger!(logger::BasicLoggerRecipe, error::Float64, iteration::Int64) - ``` - - [ ] Create a `reset_logger!` function to clean the history log information after convergence or exceed the maximum iteration. For example, `reset_logger!(logger::BasicLoggerRecipe)`. - - [ ] Create a `threshold_stop` function as the convergent stopping criterion designed for your `Logger` struct. For example, `threshold_stop(log::BasicLoggerRecipe)` - - [ ] Add documentation for this `threshold_stop` function, with mainly 3 parts: brief introduction, arguments introduction, and returns. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. - - [ ] **Optional**: If you have any helper functions that needed for your implementation, please implement them in a folder at `src/Solvers/Loggers`. -2. Package structure cooperation (`src/Solvers/Loggers.jl`, `src/RLinearAlgebra.jl`, `src/refs.bib`): - - [ ] Add an include("Loggers/[YOURFILE]") at bottom of the page, `src/Solvers/Loggers.jl`. - - [ ] Add import statements to `src/RLinearAlgebra.jl` with any functions from other packages that you use. - - [ ] Export your `Logger`, `LoggerRecipe`, any structs and functions you needed for your logger method to work in `src/RLinearAlgebra.jl`. - - [ ] Add your `Logger`, `LoggerRecipe`, needed structs and functions to `docs/src/api.loggers.md`, under the appropriate heading. - - [ ] If there are any new-added references, please add in `src/refs.bib`. -3. Tests (`test/Solvers/Loggers`): - - [ ] Add a procedural test to `test/Solvers/Loggers`. Be sure to check that the functions work as intended and all warnings/assertions are displayed. For example, `test/Solvers/Loggers/basic_logger.jl`. - - [ ] After finish implementing, you can goes to the julia's package environment by type `]` in the julia command line and run `test` to test whether you can pass all the tests. - -## Pull request -- [ ] Give a specific title to pull request. -- [ ] Lay out the features added to the pull request. -- [ ] Tag two people to review your pull request. -- [ ] **Optional**: If possible, please also add Copilot as a reviewer and choose to adopt its suggestions if reasonable. -``` -Note that, the checklist is copied from [Loggers](@ref "Loggers checklist") diff --git a/docs/src/dev/checklists/compressors.md b/docs/src/dev/checklists/compressors.md deleted file mode 100644 index 1447a857..00000000 --- a/docs/src/dev/checklists/compressors.md +++ /dev/null @@ -1,33 +0,0 @@ -# Compressors checklist -If you are implementing a compression method for the library, make sure you have completed -the following steps before making a pull request. - -``` -1. Implementation -- [ ] Create a file in the directory `src/Compressors`. -- [ ] Create a Compressor structure with n_rows and n_cols as well as other user-controlled -parameters. -- [ ] Create a constructor with keyword default values for your struct. -- [ ] Create a CompressorRecipe structure that uses the parameters from the Compressor -structure to preallocate memory. -- [ ] Create a `complete_compressor` function that takes as inputs of the Compressor, A, x,b -and returns a CompressorRecipe -- [ ] Create a `update_compressor!` function that generates new random values for a random -components of the Compressor -- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the left -- [ ] a 5 input `mul!` function for applying a compressor to a matrix from the right -- [ ] a 5 input `mul!` function for applying a compressor to a vector -- [ ] a 5 input `mul!` function for applying the adjoint of the compressor to a vector -- [ ] Add an include("Compressors/[YOURFILE]") at bottom of page -- [ ] Add import statements to src/RLinearAlgebra.jl with any functions from other packages -that you use. -- [ ] Add your Compressor and CompressorRecipe to src/RLinearAlgebra.jl. -- [ ] Add your Compressor and CompressorRecipe to docs/src/api/compressors.md -under the appropiate heading. -- [ ] Add a procedural test to test/linear_samplers. Be sure to check that the functions -work as intended and all warnings/assertions are displayed. -2. Pull request -- [ ] Give a specific title to pull request. -- [ ] Lay out the features added to the pull request. -- [ ] Tag two people to review your pull request. -``` \ No newline at end of file diff --git a/docs/src/dev/checklists/loggers.md b/docs/src/dev/checklists/loggers.md deleted file mode 100644 index 6995faa9..00000000 --- a/docs/src/dev/checklists/loggers.md +++ /dev/null @@ -1,47 +0,0 @@ -# Loggers checklist -If you are implementing a logging method for the library, make sure you have completed -the following steps before making a pull request. In the following guides, `BasicLogger` -method is used as an example. - -``` -## Implementation -1. Method's core codes (`src/Solvers/Loggers`): - - [ ] Create a file in the directory `src/Solvers/Loggers`. For example, `src/Solvers/Loggers/basic_logger.jl`. - - [ ] Create a `Logger` struct with `max_it`, `collection_rate`, `threshold_info`, `stopping_criterion`, any other method-needed parameters, and argument validations to check invalid inputs. For example, `BasicLogger<:Logger`. - - [ ] Create a constructor with keyword default values for your `Logger` struct. For example, - ``` - BasicLogger(; - max_it = 0, - collection_rate = 1, - threshold = 0.0, - stopping_criterion = threshold_stop - ) = BasicLogger(max_it, collection_rate, threshold, stopping_criterion) - ``` - - [ ] Add documentation for this `Logger` struct, with mainly 5 parts: brief introduction, fields introduction, constructor and its keywords, what the constructor returns, and what the argument validations throw. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. - - [ ] Create a `LoggerRecipe` struct that uses the parameters from the `Logger` struct to preallocate memory. For example, `BasicLoggerRecipe{F<:Function} <: LoggerRecipe`. - - [ ] Create a `complete_logger` function that takes the `logger` struct as an input, and returns the `LoggerRecipe` struct you defined last step. For example, `complete_logger(logger::BasicLogger)`. - - [ ] Add documentation for this `LoggerRecipe` struct, with mainly 2 parts: brief introduction, fields introduction. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. - - [ ] Create a `update_logger!` function to log the errors as the iteration goes on, and stop the logging with convergence status or the maximum iteration limit. For example, - ``` - update_logger!(logger::BasicLoggerRecipe, error::Float64, iteration::Int64) - ``` - - [ ] Create a `reset_logger!` function to clean the history log information after convergence or exceed the maximum iteration. For example, `reset_logger!(logger::BasicLoggerRecipe)`. - - [ ] Create a `threshold_stop` function as the convergent stopping criterion designed for your `Logger` struct. For example, `threshold_stop(log::BasicLoggerRecipe)` - - [ ] Add documentation for this `threshold_stop` function, with mainly 3 parts: brief introduction, arguments introduction, and returns. For more details, you can check `src/Solvers/Loggers/basic_logger.jl`. - - [ ] **Optional**: If you have any helper functions that needed for your implementation, please implement them in a folder at `src/Solvers/Loggers`. -2. Package structure cooperation (`src/Solvers/Loggers.jl`, `src/RLinearAlgebra.jl`, `src/refs.bib`): - - [ ] Add an include("Loggers/[YOURFILE]") at bottom of the page, `src/Solvers/Loggers.jl`. - - [ ] Add import statements to `src/RLinearAlgebra.jl` with any functions from other packages that you use. - - [ ] Export your `Logger`, `LoggerRecipe`, any structs and functions you needed for your logger method to work in `src/RLinearAlgebra.jl`. - - [ ] Add your `Logger`, `LoggerRecipe`, needed structs and functions to `docs/src/api.loggers.md`, under the appropriate heading. - - [ ] If there are any new-added references, please add in `src/refs.bib`. -3. Tests (`test/Solvers/Loggers`): - - [ ] Add a procedural test to `test/Solvers/Loggers`. Be sure to check that the functions work as intended and all warnings/assertions are displayed. For example, `test/Solvers/Loggers/basic_logger.jl`. - - [ ] After finish implementing, you can goes to the julia's package environment by type `]` in the julia command line and run `test` to test whether you can pass all the tests. - -## Pull request -- [ ] Give a specific title to pull request. -- [ ] Lay out the features added to the pull request. -- [ ] Tag two people to review your pull request. -- [ ] **Optional**: If possible, please also add Copilot as a reviewer and choose to adopt its suggestions if reasonable. -``` \ No newline at end of file diff --git a/docs/src/dev/contributing.md b/docs/src/dev/contributing.md deleted file mode 100644 index 93fdcc01..00000000 --- a/docs/src/dev/contributing.md +++ /dev/null @@ -1,37 +0,0 @@ -# Contributing - -```@contents -Pages=["contributing.md"] -``` -## Improve the documentation -If you have ever been reading the documentation and been unsure about a description or - instruction, the documentation can be improved. Since you are the person finding this - gap in the documentation, you are also the person in the best position to fix it. - -The documentation is written in Markdown and built using - [Documenter.jl](https://documenter.juliadocs.org/stable/man/guide/). - The source code for all the docs is - [here](https://github.com/numlinalg/RLinearAlgebra.jl/tree/master/docs). - - -## Bug Reports -If you find a bug in our software we would love to know about it. You can make an issue -relating to a bug report [here](https://github.com/numlinalg/RLinearAlgebra.jl/issues/new?assignees=dmaldona%2C+npritch928%2C+vp314&labels=bug&projects=&template=bug_report.md&title=). - -## Feature Requests -Have a new method that you think would be a valuable addition to the package? - Make a [feature request](https://github.com/numlinalg/RLinearAlgebra.jl/issues/new?assignees=dmaldona%2C+npritch928%2C+vp314&labels=enhancement&projects=&template=feature_request.md&title=) - and let us know about it. Please provide details of why this feature would be valuable - and if possible point us to resources to help us better understand the feature. - -## Contribute code - -You can also contribute code to `RLinearAlgebra.jl`. Before contributing make sure that you - are familiar with [Git](https://git-scm.com/book/en/v2), - [GitHub](https://docs.github.com/en/get-started/start-your-journey/hello-world), - [Julia package development](https://docs.julialang.org/en/v1/stdlib/Pkg/#Developing-packages-1), - and the [Design](@ref) of the RLinearAlgebra.jl. - Once you are familiar with these items you can contribute to `RLinearAlgebra.jl` by - following the steps laid out in the - [JUMP](https://jump.dev/JuMP.jl/stable/developers/contributing/) guide. - diff --git a/docs/src/dev/design.md b/docs/src/dev/design.md deleted file mode 100644 index 2b8f0dbc..00000000 --- a/docs/src/dev/design.md +++ /dev/null @@ -1,757 +0,0 @@ -# Design -## Overview Library Goals -RLinearAlgebra.jl implements randomized numerical linear algebra (RNLA) routines for -two tasks: (1) solving a matrix equations and (2) forming a low-rank approximation -to matrices. The primary tool Randomized Linear Algebra uses to accomplish these tasks is -multiplying the large matrix system by a smaller randomized matrix to compress the -large matrix. In the literature this process if often referred to as sampling or sketching, -RLinearAlgebra.jl refers to this process as compression. - -The library is organized with main techniques falling into one of three types: -Approximators, Compressors, and Solvers. Solvers feature their own set of -sub-techniques: Loggers, SolverErrors, and SubSolvers that facilitate solving. Approximators -have only one set of sub-techniques known as ApproximatorErrors. - -RLinearAlgebra.jl is designed so that the codebase has a good balance between efficiency -and modularity. RLinearAlgebra.jl tries to achieve these goals by introducing two -structures, one that contains user-controlled parameters which takes the form of -`[Technique]` and a second that is used by the technique to execute the techniques because it -contains the necessary preallocated memory and is known as a `[Technique]Recipe`. - -We can see an example of the difference between the two structures when considering an -implementation of compression with Gaussian matrices. In this implementation we wish to -have the user specify a compression dimension and size without having to know the dimension -of the matrix the sketch is applied to. The `Gaussian` structure facilitates this by having -two fields `n_rows` and `n_cols` with the default for both being zero. When the user then -constructs this structure the can specify the dimension and direction of the compression -by specifying the number of rows or columns they wish for the compression matrix to have. -If the user wanted a Gaussian matrix with 3 rows they would call -`Gaussian(n_rows = 3)`. We then turn this dimensional information into an usable compression -matrix by using the `complete_compressor` function to form a `GaussianRecipe`. This -`GaussianRecipe` contains Fields `n_rows = 3`, `n_cols` set to be the number of rows in the -compressor, and a Gaussian matrix of the size specified by `n_rows` and `n_cols`. - -Once a `[Technique]Recipe` has been created, this data structure can then be used to -execute a particular technique. The command to execute each technique varies by the class -of techniques, as such we lay out the specifics for each type of techniques in the following -section. - -## Technique Types -Overall, there are three top-level technique types: (1) Compressors, (2) Solvers, and -(3) Approximators, with the latter two also having additional sets of technique types -used in the execution of the top-level techniques. We group the discussions of the technique -classes by top-level technique. - -### Compressors -When implementing a Compressor, RLinearAlgebra requires an mutable `Compressor` -structure, a mutable `CompressorRecipe` structure, a `complete_compressor` function, a -`update_compressor!` function, and for five input mul! functions (one for applying the -compressor to vectors, one for applying the adjoint of a compressor to a vector, -and two for applying the compressor to matrices). - -#### Compressor Structure -Every compression technique needs a place to store user-controlled parameters. -This will be accomplished by the immutable Compressor structure. -We present an example structure used for the Sparse Sign technique. - -``` -struct SparseSign <: Compressor - n_rows::Int64 - n_cols::Int64 - nnz::Int64 -end -``` -You will first notice that `n_rows` and `n_cols` are fields present in the Compressor, -these fields allow for the user to specify either the number of rows or the number of -columns they wish the compressor to have. **Both `n_rows` and `n_cols` are required for -every Compressor structure.** Beyond those fields the technique will dictate the other -parameters that should be made available to the user. In addition to the structure, there -should be a **constructor for the structure that accepts keyword inputs for each -field of the Compressor structure.** For example in the `SparseSign` case we define, -``` -function SparseSign(;n_rows::Int64 = 0, n_cols::Int64 = 0, nnz::Int64 = 8) - # Partially construct the sparse sign datatype - return SparseSign(n_rows, n_cols, nnz) -end -``` -#### CompressorRecipe Structure -To form the compressor from the user-inputted information, we need information about the -linear system. Once this information is attained preallocations of the necessary memory can -be done. These preallocations are then stored in the `CompressorRecipe` structure. Because -this structure has all of the preallocated memory for applying the compression technique it -is this structure that can be applied to matrices and vectors. - -As example, we have included the CompressorRecipe for the sparse sign compressor. This -structure importantly includes the size of the compressor in the `n_rows` and `n_cols` -fields -``` -mutable struct SparseSignRecipe <: CompressorRecipe - n_rows::Int64 - n_cols::Int64 - max_idx::Int64 - nnz::Int64 - scale::Float64 - idxs::Vector{Int64} - signs::Vector{Bool} -end -``` -Here we have the **required `n_rows` and `n_cols`** fields for all compressors. The -remaining fields are specific to the sparse sign compression technique. - -#### complete_compressor -To create the CompressorRecipe from linear system information and the user-controlled -parameters, we use the function `complete_compressor(::Compressor, ::AbsractMatrix)`, -if vector information is required we can also define -`complete_compressor(::Compressor, ::AbsractMatrix, ::AbstractVector)`. -An example of how this is done for the sparse sign case can be seen below. -``` -function complete_compressor(sparse_info::SparseSign, A::AbstractMatrix) - n_rows = sparse_info.n_rows - n_cols = sparse_info.n_cols - # FInd the zero dimension and set it to be the dimension of A - if n_rows == 0 && n_cols == 0 - # by default we will compress the row dimension to size 2 - n_cols = size(A, 1) - n_rows = 2 - # correct these sizes - initial_size = max(n_rows, n_cols) - sample_size = min(n_rows, n_cols) - elseif n_rows == 0 && n_cols > 0 - # Assuming that if n_rows is not specified we compress column dimension - n_rows = size(A, 2) - # If the user specifies one size as nonzero that is the sample size - sample_size = n_cols - initial_size = n_rows - elseif n_rows > 0 && n_cols == 0 - n_cols = size(A, 1) - sample_size = n_rows - initial_size = n_cols - else - if n_rows == size(A, 2) - initial_size = n_rows - sample_size = n_cols - elseif n_cols == size(A, 2) - initial_size = n_cols - sample_size == n_rows - else - @assert false "Either you inputted row or column dimension must match \\ - the column or row dimension of the matrix." - end - end - - nnz = (sparse_info.nnz == 8) ? min(8, sample_size) : sparse_info.nnz - @assert nnz <= sample_size "Number of non-zero indices, $nnz, must be less than \\ - compression dimension, $sample_size." - idxs = Vector{Int64}(undef, nnz * initial_size) - start = 1 - for i in 1:initial_size - # every grouping of nnz entries corresponds to each row/column in sample - stop = start + nnz - 1 - # Sample indices from the intial_size - @views sample!( - 1:sample_size, - idxs[start:stop], - replace = false, - ordered = true - ) - start = stop + 1 - end - - # Store signs as a boolean to save memory - signs = bitrand(nnz * initial_size) - scale = 1 / sqrt(nnz) - - return SparseSignRecipe(n_rows, n_cols, sample_size, nnz, scale, idxs, signs) -end -``` -The `complete_compressor` function assumes that if the user inputs only `n_rows` or `n_cols` -in the Compressor structure this is the desired compression dimension. If they input -neither, it creates a compressor with a compression dimension of two and -if the input both and neither is consistent with -a dimension of the inputted linear system it returns an error. Otherwise, it assumes the -inconsistent dimension is the compression dimension. Once the sizes of the compressor have -been determined it next allocates the memory necessary for storing the initial compressor -and packages these allocations with the size information into the `CompressorRecipe`. - -#### update_compressor! -To generate a new version of the compressor we can call the function `update_compressor!`, -this function simply changes the random components of the CompressorRecipe. In the sparse -sign case this means updating the nonzero indices and the signs as can be seen in the -following example code. -``` -function update_compressor!( - S::SparseSignRecipe, - A::AbstractMatrix, - b::AbstractVector, - x::AbstractVector - ) - # Sample_size will be the minimum of the two size dimensions of `S` - sample_size = min(S.n_rows, S.n_cols) - initial_size = max(S.n_rows, S.n_cols) - start = 1 - for i in 1:sample_size - # every grouping of nnz entries corresponds to each row/column in sample - stop = start + S.nnz - 1 - # Sample indices from the intial_size - @views sample!( - 1:sample_size, - S.idxs[start:stop], - replace = false, - ordered = true - ) - start = stop + 1 - end - # There is no inplace update of bitrand and using sample is slower - S.signs .= bitrand(S.nnz * initial_size) - return -end -``` - -#### mul! -The last pieces of code that every compression technique requires are the `mul!` functions. -For these functions we follow the conventions laid out in the LinearAlgebra library where -there are five inputs (C, A, S, alpha, beta) and it outputs `C = beta * C + alpha * A * S`. -The `mul!` functions that should be implemented are two for applying the compression matrix -to vectors, one in standard orientation and one for when the adjoint of the compressor is -applied to the vector. Additionally, two `mul!` functions should be implemented for when -the compression matrix is applied to a matrix, one for when the compression matrix, S, is -applied from the left, i.e. AS, and one for when the compression matrix is applied from the -right, i.e. SA. - -### Solvers -A Solver technique is any technique that aims to find a vector ``x`` such that either -``Ax = b`` or ``x = \\min_u \\|A u - b\\|_2^2``. Solvers rely on compression techniques, -logging techniques, error techniques, and sub-solver techniques. We first discuss -implementation requirements for the sub-techniques and then discuss how we can use these -when creating a solver structure. - -#### Loggers -Loggers are structures with two goals (1) log a progress value produced by an error metric -and (2) evaluate whether that error is sufficient for stopping. The user controlled inputs -for a logging technique are contained in the Logger structure. - -##### Logger -The `Logger` structure is where the user inputs any information required -to logging progress and stopping the method. **The Logger is required to have a field for -`max_it`, `threshold_info`, and `stopping_criterion`.** The `max_it` field is a field -for the maximum number of iterations of the method. The `stopping_criterion` is a field that -contains a function that returns a stopping decision based on the information in the -`LoggerRecipe` and the `Tuple` of information supplied by the user in the `threshold_info` -field. It is important to note that constructors for these techniques should have keyword -inputs with predefined defaults. We present an example of the Logger structure for a -`BasicLogger` below -``` -struct BasicLogger <: Logger - max_it::Int64 - collection_rate::Int64 - threshold_info::Union{Float64, Tuple} - stopping_criterion::Function -end -``` - -Aside from the required parameters the `BasicLogger` also features a `collection rate` -parameter to allow the user to specify how often they wish for the `LoggerRecipe` to log -progress. - -##### LoggerRecipe -The `LoggerRecipe` will contain the user-controlled parameters from the `Logger` as well as -memory for storing the logged information. All `LoggerRecipes` -**must contain a `max_it` field and a `converged` field,** where the `converged` field is a -boolean indicating if the method has converged. An example of a `LoggerRecipe` is presented -below for the `BasicLoggerRecipe`. This Logger has a vector for the history of the progress -metric, a field whose inclusion is strongly suggested. It also has `record_location` field -to keep track of where the next observed progress estimate should be placed depending -on the `collection_rate`. -``` -mutable struct BasicLoggerRecipe{F} <: LoggerRecipe where F<:Function - max_it::Int64 - err::Float64 - threshold_info::Union{Float64, Tuple} - iteration::Int64 - record_location::Int64 - collection_rate::Int64 - converged::Bool - stopping_criterion::F - hist::Vector{Float64} -end -``` - -##### complete_logger -As with the other techniques, `complete_logger` takes a `Logger` data structure and -performs the appropriate allocations to generate a `LoggerRecipe`. An example of this -function for BasicLogger is presented below. -``` -function complete_logger(logger::BasicLogger, A::AbstractMatrix) - # We will run for a number of iterations equal to 3 itmes the number of rows if maxit is - # not set - max_it = logger.max_it == 0 ? 3 * size(A, 1) : logger.max_it - - max_collection = Int(ceil(max_it / logger.collection_rate)) - # use one more than max it form collection - hist = zeros(max_collection + 1) - return BasicLoggerRecipe{typeof(logger.stopping_criterion)}(max_it, - 0.0, - logger.threshold_info, - 1, - 1, - logger.collection_rate, - false, - logger.stopping_criterion, - hist - ) -end -``` - -##### update_logger! -As with the compressors `update_logger!` performs an in-place update of the -LoggerRecipe using the inputted progress metric and iteration of the method. An example of -the `update_logger!` function for the BasicLoggerRecipe is included below. -``` -function update_logger!(logger::BasicLoggerRecipe, err::Float64, iteration::Int64) - logger.iteration = iteration - logger.err = err - if rem(iteration, logger.collection_rate) == 0 - logger.hist[logger.record_location] = err - logger.record_location += 1 - end - # Always check max_it stopping criterion - # Compute in this way to avoid bounds error from searching in the max_it + 1 location - logger.converged = iteration <= logger.max_it ? logger.stopping_criterion(logger) : - false - return - -end -``` - -##### Stopping Functions -As was noted in the description of the required fields for the `Logger` the user should -have the opportunity to input a stopping function that should take the input of a -LoggerRecipe to which it updates the value of the `converged` field if stopping should -occur. An example implementation of function for threshold stopping, stop when progress -the metric falls below a particular threshold is presented below. -``` -function threshold_stop(log::LoggerRecipe) - return log.err < log.threshold_info -end -``` -#### SolverErrors -For computing the progress of a solver it is important to include implementations of -particular error techniques. These typically will be techniques like the residual or -compressed residual, but could be more complicated techniques like an estimate of backwards -stability. - -##### SolverError -This is a structure that holds user-controlled parameters for a progress estimation -technique. For basic techniques like the residual where no user-controlled parameters are -required this will simply be an empty structure. We have included an example of a -`SolverError` structure for the residual computations. It is important to note that -constructors for these techniques should have keyword inputs with predefined defaults. -``` -struct FullResidual <: SolverError - -end -``` - -##### SolverErrorRecipe -This structure contains the user-controlled parameters from the `SolverError` as well -memory allocations of a size determined based on the linear system. An example for a -residual technique has been included below. - -``` -mutable struct FullResidualRecipe{V<:AbstractVector} <: SolverErrorRecipe - residual::V -end -``` -##### complete_error -To generate the `SolverErrorRecipe` from the information in the linear system and -`SolverError` we use the function `complete_error`. This function should be implemented to -take the inputs of the SolverError`, a matrix `A` representing the linear system, and a -vector `b` representing the constant vector of the linear system. An example of this -function for the residual error technique has been included below. - -``` -function complete_error(error::FullResidual, A::AbstractMatrix, b::AbstractVector) - return FullResidualRecipe{typeof(b)}(zeros(size(b,1))) -end -``` -##### compute_error -To excute the technique we call the function `compute_error` with the inputs of the -`SolverErrorRecipe`, `Solver`, coefficient matrix `A`, and constant vector `b`. This -function then performs the necessary computations to return a single value indication of the -progress of the solver. An example of this for the residual technique that returns the norm- -squared of the residual is included below. -``` -function compute_error( - error::FullResidualRecipe, - solver::KaczmarzRecipe, - A::AbstractMatrix, - b::AbstractVector - )::Float64 - copyto!(error.residual, b) - mul!(error.residual, A, solver.solution_vec, -1.0, 1.0) - return dot(error.residual, error.residual) -end -``` - -#### SubSolvers -Although, randomized solvers are used to solve a larger linear system. They typically -rely on using compressors to generate a compressed linear system that can be easily solved -using standard techniques. The specifics of the 'standard' techniques is typically not -specified. For instance, if the compressed system is a least squares problem one could solve -this system with a QR algorithm or LSQR and potentially get vastly different performance -results. To allow the user to experiment with different techniques for solving the -compressed linear system, we introduce the SubSolver data structures. - -##### SubSolver -This is a data structure that allows the user to specify how they wish to solve the -compressed linear systems generated in the solving process. When the solver type is a direct -method it is possible for there to be no user inputs in this data structure. For iterative -methods there could be extensive user-controlled parameters included in this structure. For -example, for a LSQR SubSolver the user could input the maximum of iterations, a -preconditioner type, or stopping thresholds. We have included an example of the `SubSolver` -structure for the `LQSolver`, which is an approach for solving undetermined linear systems -and does not have any user-controlled parameters associated with it. It is important to note -that constructors for these techniques should have keyword inputs with predefined defaults. -``` -struct LQSolver <: SubSolver - -end -``` - -##### SubSolverRecipe -This is a data structure that contains the preallocated memory necessary for solving the -linear system. - -##### complete_solver -This is a function that takes a `SubSolver` and the linear system as input and uses these -inputs to output a SubSolverRecipe. - -##### update_sub_solver -This is a function that updates the preallocated memory in the SubSolverRecipe with -the relevant information for the new compressed linear system. - -##### ldiv! -A function that uses the SubSolverRecipe to solve the compressed linear system. - -#### Solvers -With an understanding of all of these sub techniques, we can discuss how to use these -methods to implement a Solver technique. The first data structure required for a solver is -the `Solver` structure. - -##### Solver -The Solver data structure is a structure where the user can input values of user-controlled -parameters specific to a particular type of solver. This typically involves the user -inputting the structures associated with their desired Compressor, Logger, Error, and -SubSolver, as well as any parameters like step-sizes associated with the particular -randomized solver they are using. As an example, we have included the Solver structure -associated with the Kaczmarz solver. It is important to note that constructors for these -techniques should have keyword inputs with predefined defaults. -``` -mutable struct Kaczmarz <: Solver - alpha::Float64 - S::Compressor - log::Logger - error::SolverError - sub_solver::SubSolver -end -``` - -##### SolverRecipe -The SolverRecipe will contain all the preallocated memory associate with the solver, the -solver specific user-controlled parameters, and all recipes associated with the -sub-techniques included in the `Solver` structure. We have included an example for the -`KaczmarzRecipe` below. -``` -mutable struct KaczmarzRecipe{T<:Number, - V<:AbstractVector, - M<:AbstractMatrix, - VV<:SubArray, - 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 - vec_view::VV -end -``` -The first four fields are associated with the sub-techniques for the solver. The alpha -field is a user defined value and the remaining fields are preallocated space for storing -the result of the compression and the solution vector. - -##### complete_solver -The `complete_solver` function performs the necessary computations and allocations to change -a `Solver` structure into a `SolverRecipe`. In the example code below for a Kaczmarz solver -these computations include running `complete_[technique]` for the compression, logging, -error, and sub solver techniques, as well as allocating memory for storing the compressed -matrix and compressed vector, the solution vector, and update vector. **The `views` -allocated by this function should be replicated in other multi-compression solver structures -to allow for varying sizes of the compression matrix.** -``` -function complete_solver( - solver::Kaczmarz, - x::AbstractVector, - A::AbstractMatrix, - b::AbstractVector - ) - # 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 kaczmarz solver." - @assert isdefined(logger, :converged) "LoggerRecipe $(typeof(logger)) does not contain\ - the field 'converged' and is not valid for a kaczmarz 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 - sample_size = n_rows - initial_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, sample_size, cols_a) - compressed_vec = typeof(b)(undef, sample_size) - # 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, :) - vec_view = view(compressed_vec, 1:sample_size) - solution_vec = x - update_vec = typeof(x)(undef, cols_a) - return KaczmarzRecipe{eltype(A), - typeof(b), - typeof(A), - typeof(vec_view), - 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, - vec_view - ) -end -``` - -##### rsolve! -Every implementation of a Solver technique should include a `rsolve!` function that performs -in-place updates to a solution vector and `SolverRecipe`. An example of such an -implementation for a Kaczmarz solver is included below. To the greatest extent possible -the implementation should be written in a way that avoids new memory allocations. This means -making use in-place update functions like `mul!` or `ldiv!` rather than `*` or `\`. -``` -function rsolve!( - solver::KaczmarzRecipe, - x::AbstractVector, - A::AbstractMatrix, - b::AbstractVector - ) - solver.solution_vec = x - err = 0.0 - 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 - end - - # 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:rows_s, :) - solver.vec_view = view(solver.compressed_vec, 1:rows_s) - # compress the matrix and constant vector - mul!(solver.mat_view, solver.S, A) - mul!(solver.vec_view, solver.S, b) - # Compute the block residual - mul!(solver.vec_view, solver.mat_view, solver.solution_vec, -1.0, 1.0) - # 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.vec_view) - # Using over-relaxation parameter, alpha, to update solution - solver.solution_vec .+= solver.alpha .* solver.update_vec - end - - return solver.solution_vec, solver.log -end -``` - -### Approximators -Aside from solving linear systems, Randomized Linear Algebra has also been proven to be -extremely useful for generating low rank approximations to linear systems. These low-rank -approximations can then be used to solve linear systems or perform more efficient -matrix-matrix multiplications. The main types of low rank approximation methods implemented -in this version of the library are random range finder techniques like random SVD, CUR -type methods, and Nystrom Methods. Low rank approximations can be formed simply by calling -the `rapproximate` function. Once a Low-rank approximation has been formed it can then be -applied either as a preconditioner by calling the `ldiv!` function or multiplied by calling -the `mul!` function. Each of the low rank approximation technique requires the -implementation of the following data structures and functions. - -#### Approximator -This is a data structure that contains the user defined parameters for an approximator. An -example of this structure for the RangeFinder decomposition is included below. In the case -of the randomized range finder the only real user controlled parameter is the sketch size, -which is controlled by the `Compressor`. It should be noted that constructors for these -structures should be based around keyword inputs with preset defaults. -``` -mutable struct RangeFinder <: Approximator - S::Compressor - error::ErrorMethod -end -``` - -#### ApproximatorRecipe -This a data structure that contains preallocated memory and the user-controlled parameters -for a specific approximation method. An example of this data structure for a RangeFinder -decomposition is included below. -``` -mutable struct RangeFinderRecipe <: ApproximatorRecipe - S::CompressorRecipe - error::ErrorMethodRecipe - compressed_mat::AbstractMatrix - approx_range::AbstractMatrix -end -``` - -#### complete_approximator -The `complete_approximator` function takes the matrix `A` and the -`Approximator` data structure to output an `ApproximatorRecipe` with properly allocated -storage for the low-rank approximation. An example of this function for the -`RangeFinderRecipe` is included below. -``` -function complete_approximator(approx::RangeFinder, A::AbstractMatrix) - S = complete_compressor(approx.S, A) - err = complete_error(approx.error, A) - s_rows, s_cols = size(S) - a_rows, a_cols = size(A) - compressed_mat = Matrix{eltype(A)}(undef, a_rows, s_cols) - approx_range = Matrix{eltype(A)}(undef, a_rows, s_cols) - return RangeFinderRecipe(S, err, compressed_mat, approx_range) -end -``` - -#### rapproximate! -A function that returns an `ApproximatorRecipe` and approximation error value for a -particular approximation method. The returned `ApproximatorRecipe` can then be used for -matrix multiplication or preconditioning through the use of the `mul!` and `ldiv!` functions -respectively. An example of this function for the `RangeFinderRecipe` is included below. -``` -function r_approximate!( - approximator::RangeFinderRecipe - A::AbstractMatrix -) - m, n = size(A) - update_compressor!(aproximator.S) - # compuress the matrix - mul!(compressed_mat, A, aproximator.S) - # Array is required to compute the skinny qr - approximator.approx_range .= Array(qr(compressed_mat).Q) - err = compute_error(aproximator.error, A) - return approximator, error -end -``` - -#### ldiv! -A function that solves the system `Mx = b` for `x` where M is a low rank approximation -matrix. This is useful for preconditioning linear systems. When there is no obvious way to -use the low rank approximation to solve this system the implementation will be the same as -the implementation for `mul!`. - -#### mul! -A function that multiplies a low rank approximation with a matrix. This should be -implemented as the five input `mul!` function. For these functions we follow the conventions -laid out in the LinearAlgebra library where there are five inputs (C, A, S, alpha, beta) and -it outputs `C = beta * C + alpha * A * S`. - -### Approximation Error -For the `Approximator`s an important sub-techniques are those that verify the accuracy of a -particular approximation. These methods can be exact, as in the case of computing -``\|A - QQ'A\|_F``, where ``Q`` is a row-space approximator or approximate such as the -``\|AS - QQ'AS\|_F`` where ``S`` is a Gaussian matrix with 10 column vectors. - -#### ApproximatorError -The `ApproximatorError` data structure is a data structure that takes the user controlled -parameters for a method that computes the approximation error, e.g ``A - QQ'A`` for a -particular approximation method. In cases where this error is an exact approximation no -user-controlled parameters may be needed and the `ApproximatorError` can be implemented as -an empty data structure. If user-controlled parameters are necessary then constructors -should be implemented that take in keyword arguments with defaults. We have included an -example data structure for a method that computes the projected error, ``A - QQ'A``, below. -``` -mutable struct ProjectedError <: ApproximatorError - -end - -``` - -#### ApproximatorErrorRecipe -An `ApproximatorErrorRecipe` contains the user-controlled parameters and preallocated memory -for a method that computes the approximation error, e.g `A - QQ'A` for a particular -approximation method. -``` -mutable struct ProjectedErrorRecipe{T, M{T}} <: ApproximatorErrorRecipe - where M <: AbstractMatrix - error::Float64 - large_buff_mat::M - small_buffer_mat::M -end -``` -#### complete_error -The `complete_error` function takes the information from a `ApproximatorError` and an -`AbstractMatrix` to create an `ApproximatorErrorRecipe`. An example for the -`ProjectedError` structure is included below. -``` -function complete_error(error::ProjectedError, S::CompressorRecipe, A::AbstractMatrix) - row_s, col_s = size(S) - row_a, col_a = size(A) - T = eltype(A) - M = Matrix{T} - small_buffer_mat = M(undef, col_s, col_a) - large_buffer_mat = M(undef, row, col_a) - return ProjectedErrorRecipe{T, M}(0.0, large_buffer_mat, small_buffer_mat) -end -``` - -#### compute_error -A function that computes the error of a particular approximation method with respect to the -matrix `A` for a particular approximation technique. An example for `ProjectedError` is -included below. -``` -function compute_error( - error::ProjectedErrorRecipe, - approximator::RandRangeFinderRecipe, - A::AbstractMatrix - ) - mul!(error.small_buffer_mat, approximator.row_space', A) - mul!(error.large_buffer_mat, approximator.row_space, error.small_buffer_mat) - error.large_buffer_mat .-= A - error.error = norm(error.large_buffer_mat) - return error.error -end -``` diff --git a/docs/src/dev/style_guide.md b/docs/src/dev/style_guide.md deleted file mode 100644 index bbd1c0a3..00000000 --- a/docs/src/dev/style_guide.md +++ /dev/null @@ -1,60 +0,0 @@ -# Style Guide -```@contents -Pages=["style_guide.md"] -``` -When writing code for `RLinearAlgebra.jl` we expect the code to be written in accordance -with the [BLUE](https://github.com/JuliaDiff/BlueStyle) style. - -## Documentation -This section describes the writing style that should be used when writing documentation for -`RLinearAlgebra.jl.` Many of these ideas for these suggestions -come from [JUMP](https://jump.dev/JuMP.jl/stable/developers/style/). -Overall when documenting the code one should follow these recommendations: - - Be concise - - Prefer lists over long sentences - - Use numbers when describing an ordered set of ideas - - Use bullets when these is no specific order - -### Docstrings - - Every new **function** and **data structure** needs to have a docstring - - Use properly punctuated complete sentences - -Below, we provide an example of a function docstring and a data structure docstring. - -#### Function Docstring -``` -""" - myFunction(args; kwargs...) - -A couple of sentences describing the function. These sentences should describe what inputs -are required and what is output by the function. - -### Arguments -- `arg1`, description of arg 1 - -### Outputs -The result of calling the function. This should be either the data structure that is -modified or what is returned. - -A citation from the package DocumenterCitations. -""" - -``` - -#### Data Structure Docstring -``` -""" - YourStructure <: YourStructuresSuperType - -A brief sentence describing the purpose of the structure. - -A citation in MLA format if the function comes from another author's work. - -### Fields -- `S::FieldType`, brief description of field purpose - -Include a sentence or two describing how the constructors work. Please be sure to include -the default values of the constructor. -""" - -``` diff --git a/docs/src/tutorials/least_square.md b/docs/src/tutorials/least_squares.md similarity index 100% rename from docs/src/tutorials/least_square.md rename to docs/src/tutorials/least_squares.md From c359f9d1c9e4ed14e3d03509020b49c0e7920a3f Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 15 Oct 2025 11:04:00 -0500 Subject: [PATCH 32/42] docs/reformulate --- docs/make.jl | 15 +- .../consistent_system/consistent_system.md | 154 ++++++++++++++++++ .../consistent_system_compressor.md} | 38 +++-- docs/src/tutorials/introduction.md | 6 +- 4 files changed, 187 insertions(+), 26 deletions(-) create mode 100644 docs/src/tutorials/consistent_system/consistent_system.md rename docs/src/tutorials/{least_squares.md => consistent_system/consistent_system_compressor.md} (86%) diff --git a/docs/make.jl b/docs/make.jl index dfb41ec8..e92d609e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,7 +18,10 @@ makedocs( "Home" => "index.md", "Tutorials" => [ "Introduction" => "tutorials/introduction.md", - "Least squares" => "tutorials/least_squares.md" + "Consistent Linear System" => [ + "tutorials/consistent_system/consistent_system.md", + "tutorials/consistent_system/consistent_system_compressor.md", + ], ], "API Reference" => [ "Compressors" => [ @@ -42,16 +45,6 @@ makedocs( ], ], ], - "Contributing" => [ - "Contributing Overview" => "dev/contributing.md", - "Design of Library" => "dev/design.md", - "Checklists" => [ - "dev/checklists.md", - "Compressors" => "dev/checklists/compressors.md", - "Loggers" => "dev/checklists/loggers.md" - ], - "Style Guide" => "dev/style_guide.md", - ], "References" => "references.md", ] ) diff --git a/docs/src/tutorials/consistent_system/consistent_system.md b/docs/src/tutorials/consistent_system/consistent_system.md new file mode 100644 index 00000000..b0b18908 --- /dev/null +++ b/docs/src/tutorials/consistent_system/consistent_system.md @@ -0,0 +1,154 @@ +# Solving a Consistent Linear System + +This guide demonstrates how to use `RLinearAlgebra.jl` package to find the solution to a **consistent linear system** of the form: + +$$Ax = b$$ + +--- +## Problem Setup + +Let's define a specific linear system $Ax = b$. + +To verify the accuracy of the final result, suppose that we know the true solution of the system, $x_{\text{true}}$, and then use it and a random generated matrix $A$ to generate the vector $b$. + +To achieve this, we need to import the required libraries and create the matrix `A` +and vector `b` as defined above. +We will also set an initial guess, `x_init`, for the solver. + +```@example ConsistentExample +# Import relevant libraries +using RLinearAlgebra, LinearAlgebra + +# Define the dimensions of the linear system +num_rows, num_cols = 1000, 20 + +# Create the matrix A and a known true solution x_true +A = randn(Float64, num_rows, num_cols); +x_true = randn(Float64, num_cols); + +# Calculate the right-hand side vector b from A and x_true +b = A * x_true; + +# Set an initial guess for the solution vector x (typically a zero vector) +x_init = zeros(Float64, num_cols); + +println("Dimensions:") +println(" - Matrix A: ", size(A)) +println(" - Vector b: ", size(b)) +println(" - True solution x_true: ", size(x_true)) +println(" - Initial guess x_init: ", size(x_true)) +``` + +--- +## Solve the system + +As simple as you can imagine, `RLinearAlgebra.jl` can solve the problem in just a +few lines of codes: + +```@example ConsistentExample +logger = BasicLogger( + max_it = 500 +) +kaczmarz_solver = Kaczmarz(log = logger) +solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) +rsolve!(solver_recipe, x_init, A, b) +solution = x_init; +println("Solution to the system: \n", solution) +``` +Done! How simple it is! + +let's check how close our calculated solution is to the known `x_true`. +We can do this by calculating the Euclidean norm of the difference between the two vectors. +A small error norm indicates a successful approximation. + +```@example ConsistentExample +# We can inspect the logger's history to see the convergence +error_history = solver_recipe.log.hist; +println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) +println(" - Final error: ", error_history[solver_recipe.log.record_location]) + +# Calculate the norm of the error +error_norm = norm(solution - x_true) +println(" - Norm of the error between the solution and x_true: ", error_norm) +``` +As you can see, by using the modular Kaczmarz solver, we were able to configure a +randomized block-based method and find a solution vector that is very close to +the true solution. + +Let's go line by line to see what are the codes doing. + +--- +## Create solver +Here, we choose to use the [Kaczmarz solver](@ref Kaczmarz). +We can configure it by passing in "ingredient" objects for each of its main functions: + compressing the system, logging progress, and checking for errors. + +Start with only the simplest component, let's configure just the maximum iteration that +our algorithm can go. The configuration is located in the `logger` structure, which +is responsible to record the error history, and tell the solver when to stop. Here, we will use the `BasicLogger`. + +```@example ConsistentExample +# Configure the maximum iteration to be 500 +logger = BasicLogger( + max_it = 500 +) +``` + + +Now, we assemble our configured components (compressor `S`, logger `L`) into the main +Kaczmarz solver object. +We will use the default methods for error checking and the sub-solver to be +LQ factorization ([LQSolver](@ref LQSolver)). + +```@example ConsistentExample +# Create the Kaczmarz solver object by passing in the ingredients +kaczmarz_solver = Kaczmarz() +``` + +Before we can run the solver, we must call `complete_solver`. +This function takes the solver configurations and the specific problem data `A, b, x_init` +and creates a `KaczmarzRecipe`. +The recipe pre-allocates all the necessary memory buffers for efficient computation. + +```@example ConsistentExample +# Create the solver recipe by combining the solver and the problem data +solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) +``` + +With the recipe fully prepared, we can now call `rsolve!` to run the Kaczmarz algorithm. +The function will iterate until the `stopping criterion` in the `logger` is met. + +The `rsolve!` function will modify `x_init` in-place, updating it with the +calculated solution. + +```@example ConsistentExample +# Run the solver! +rsolve!(solver_recipe, x_init, A, b) + +# The solution is now stored in the updated x_init vector +solution = x_init; +``` + + +--- +## 3. Verify the result + +Finally, let's check how close our calculated solution is to the known `x_true`. +We can do this by calculating the Euclidean norm of the difference between +the two vectors. A small error norm indicates a successful approximation. + +```@example ConsistentExample +# We can inspect the logger's history to see the convergence +error_history = solver_recipe.log.hist; +println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) +println(" - Final error: ", error_history[solver_recipe.log.record_location]) + +# Calculate the norm of the error +error_norm = norm(solution - x_true) +println(" - Norm of the error between the solution and x_true: ", error_norm) +``` +As you can see, by using the modular Kaczmarz solver, we were able to configure a +randomized block-based method and find a solution vector that is very close to +the true solution. + + diff --git a/docs/src/tutorials/least_squares.md b/docs/src/tutorials/consistent_system/consistent_system_compressor.md similarity index 86% rename from docs/src/tutorials/least_squares.md rename to docs/src/tutorials/consistent_system/consistent_system_compressor.md index bcaba598..b672d6e5 100644 --- a/docs/src/tutorials/least_squares.md +++ b/docs/src/tutorials/consistent_system/consistent_system_compressor.md @@ -1,4 +1,4 @@ -# Use Case: Solving a Least-Squares Problem with the Sparse Sign Method +# Solving a Consistent Linear System This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve an overdetermined linear system (i.e., a least-squares problem) of the form: @@ -6,6 +6,20 @@ $$\min_{x} \|Ax - b\|_2^2$$ We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve the problem. +This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve a **consistent linear system**. Because the problem is constructed to have a known, exact solution, it serves as a perfect test case. + +We will use an iterative solver that is broadly applicable to any least-squares problem, which finds a solution by minimizing the squared norm of the residual: + +$$\min_{x} \|Ax - b\|_2^2$$ + +We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve the problem. + + +This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to find the exact solution to a **consistent linear system** of the form: + +$$Ax = b$$ + +We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve for the vector $x$. --- ## 1. Problem Setup @@ -19,7 +33,7 @@ To verify the accuracy of the final result, suppose that we know the true soluti To achieve this, we need to import the required libraries and create the matrix `A` and vector `b` as defined above. We will also set an initial guess, `x_init`, for the solver. -```@example SparseSignExample +```@example ConsistentExample # Import relevant libraries using RLinearAlgebra, Random, LinearAlgebra @@ -57,7 +71,7 @@ The idea of randomized methods is to reduce the scale of the original problem wh We will configure a compression matrix `S` that compresses the 100 rows of the original system down to 30 rows. -```@example SparseSignExample +```@example ConsistentExample # The goal is to compress the 1000 rows of A to 300 rows compression_dim = 300 # We want each row of the compression matrix S to have 5 non-zero elements @@ -82,7 +96,7 @@ sparse_compressor = SparseSign( After configuring the compressor, we need to combine it with our specific matrix `A` to create a `SparseSignRecipe`. This recipe contains the generated sparse matrix and all necessary information to perform the compression efficiently. -```@example SparseSignExample +```@example ConsistentExample # Pass the compressor configuration and the original matrix A to # create the final compression recipe. S = complete_compressor(sparse_compressor, A) @@ -97,7 +111,7 @@ println(" - Compression matrix's nonzero entry values: ", S.scale) println(" - Compression matrix: ", S.op) ``` If the compression dimension of `300` rows is considered too large, it can be changed to `10` by updating the compressor configuration and rebuilding the recipe as follows: -```@example SparseSignExample +```@example ConsistentExample # Change the dimension of the compressor. Similarly, you can use the same idea # for other configurations' changes. sparse_compressor.compression_dim = 10 @@ -111,7 +125,7 @@ println("Compression matrix's number of rows: ", S.n_rows) While the solver can use the `S` recipe to perform multiplications on-the-fly, it can sometimes be useful to form the compressed system explicitly. We can use `*` for this. -```@example SparseSignExample +```@example ConsistentExample # Form the compressed system SAx = Sb SA = S * A Sb = S * b @@ -134,7 +148,7 @@ To monitor the solver, we will use a `BasicLogger`. This object will serve two p We will configure it to stop after a maximum of `50` iterations or if the calculated error drops below a tolerance of `1e-6`. And we use `collection_rate = 5` to configure the frequence of error recording to be every $5$ steps. -```@example SparseSignExample +```@example ConsistentExample # Configure the logger to control the solver's execution logger = BasicLogger( max_it = 50, @@ -147,7 +161,7 @@ logger = BasicLogger( ### (b) Build the Kaczmarz Solver Now, we assemble our configured components (compressor `S`, logger `L`) into the main Kaczmarz solver object. We will use the default methods for error checking and the sub-solver to be LQ factorization ([LQSolver](@ref LQSolver)). -```@example SparseSignExample +```@example ConsistentExample # Create the Kaczmarz solver object by passing in the ingredients kaczmarz_solver = Kaczmarz( compressor = sparse_compressor, @@ -157,7 +171,7 @@ kaczmarz_solver = Kaczmarz( ``` Before we can run the solver, we must call `complete_solver`. This function takes the solver configurations and the specific problem data `A, b, x_init` and creates a `KaczmarzRecipe`. The recipe pre-allocates all the necessary memory buffers for efficient computation. -```@example SparseSignExample +```@example ConsistentExample # Create the solver recipe by combining the solver and the problem data solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) ``` @@ -169,7 +183,7 @@ With the recipe fully prepared, we can now call `rsolve!` to run the Kaczmarz al The `rsolve!` function will modify `x_init` in-place, updating it with the calculated solution. -```@example SparseSignExample +```@example ConsistentExample # Run the solver! rsolve!(solver_recipe, x_init, A, b) @@ -182,11 +196,11 @@ solution = x_init; Finally, let's check how close our calculated solution is to the known `x_true`. We can do this by calculating the Euclidean norm of the difference between the two vectors. A small error norm indicates a successful approximation. -```@example SparseSignExample +```@example ConsistentExample # We can inspect the logger's history to see the convergence error_history = solver_recipe.log.hist; println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) -println(" - Final error: ", error_history[solver_recipe.log.record_location - 1]) +println(" - Final error: ", error_history[solver_recipe.log.record_location]) # Calculate the norm of the error error_norm = norm(solution - x_true) diff --git a/docs/src/tutorials/introduction.md b/docs/src/tutorials/introduction.md index 0e36ff5c..3dec10c4 100644 --- a/docs/src/tutorials/introduction.md +++ b/docs/src/tutorials/introduction.md @@ -1,12 +1,12 @@ # Introduction -The purpose of these tutorials is to use examples help - new users quickly get hands on the usage of `RLinearAlgebra.jl`. +The purpose of these tutorials is to use examples help new users quickly get hands +experience using `RLinearAlgebra.jl`. ## How tutorials are structured Problem sets: -- Least squre problem, solving $$\min_{x} \|Ax - b\|_2^2$$ +- Consistent system problem, solving $x$ s.t. $$Ax = b$$. From 20fea5211e0bc7e79bf8eecd00bab2fc7f7c1ff4 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 15 Oct 2025 11:20:19 -0500 Subject: [PATCH 33/42] docs/reformulate --- .../consistent_system/consistent_system.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/src/tutorials/consistent_system/consistent_system.md b/docs/src/tutorials/consistent_system/consistent_system.md index b0b18908..e7ea6763 100644 --- a/docs/src/tutorials/consistent_system/consistent_system.md +++ b/docs/src/tutorials/consistent_system/consistent_system.md @@ -78,11 +78,15 @@ the true solution. Let's go line by line to see what are the codes doing. --- -## Create solver -Here, we choose to use the [Kaczmarz solver](@ref Kaczmarz). +## Steps to solve the problem + +Here, we choose to use the [Kaczmarz solver](@ref Kaczmarz) to solve the problem. We can configure it by passing in "ingredient" objects for each of its main functions: compressing the system, logging progress, and checking for errors. + +### Configure the logger + Start with only the simplest component, let's configure just the maximum iteration that our algorithm can go. The configuration is located in the `logger` structure, which is responsible to record the error history, and tell the solver when to stop. Here, we will use the `BasicLogger`. @@ -94,11 +98,11 @@ logger = BasicLogger( ) ``` +### Create the solver -Now, we assemble our configured components (compressor `S`, logger `L`) into the main +Now, we assemble our configured components (`logger`) into the main Kaczmarz solver object. -We will use the default methods for error checking and the sub-solver to be -LQ factorization ([LQSolver](@ref LQSolver)). +We will use the default compressor, logger and sub-solver. ```@example ConsistentExample # Create the Kaczmarz solver object by passing in the ingredients From 8c513317caecd34dbbd11ea201810a64f585dafc Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 15 Oct 2025 11:37:08 -0500 Subject: [PATCH 34/42] docs/reformulate --- .../consistent_system/consistent_system.md | 65 +++++++------------ 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/docs/src/tutorials/consistent_system/consistent_system.md b/docs/src/tutorials/consistent_system/consistent_system.md index e7ea6763..5e64c3c5 100644 --- a/docs/src/tutorials/consistent_system/consistent_system.md +++ b/docs/src/tutorials/consistent_system/consistent_system.md @@ -1,6 +1,7 @@ # Solving a Consistent Linear System -This guide demonstrates how to use `RLinearAlgebra.jl` package to find the solution to a **consistent linear system** of the form: +This guide demonstrates how to use `RLinearAlgebra.jl` package to find the solution to +a **consistent linear system** of the form: $$Ax = b$$ @@ -9,7 +10,9 @@ $$Ax = b$$ Let's define a specific linear system $Ax = b$. -To verify the accuracy of the final result, suppose that we know the true solution of the system, $x_{\text{true}}$, and then use it and a random generated matrix $A$ to generate the vector $b$. +To verify the accuracy of the final result, suppose that we know the true solution +to the system, $x_{\text{true}}$, and then use it and a random generated +matrix $A$ to generate the vector $b$. To achieve this, we need to import the required libraries and create the matrix `A` and vector `b` as defined above. @@ -46,9 +49,7 @@ As simple as you can imagine, `RLinearAlgebra.jl` can solve the problem in just few lines of codes: ```@example ConsistentExample -logger = BasicLogger( - max_it = 500 -) +logger = BasicLogger(max_it = 500) kaczmarz_solver = Kaczmarz(log = logger) solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) rsolve!(solver_recipe, x_init, A, b) @@ -88,14 +89,14 @@ We can configure it by passing in "ingredient" objects for each of its main func ### Configure the logger Start with only the simplest component, let's configure just the maximum iteration that -our algorithm can go. The configuration is located in the `logger` structure, which -is responsible to record the error history, and tell the solver when to stop. Here, we will use the `BasicLogger`. +our algorithm can go. The configuration is located in the [`logger`](@ref Logger) +structure, which is responsible to record the error history, and tell the +solver when to stop. +Here, we will use the [`BasicLogger`](@ref BasicLogger). -```@example ConsistentExample +```julia # Configure the maximum iteration to be 500 -logger = BasicLogger( - max_it = 500 -) +logger = BasicLogger(max_it = 500) ``` ### Create the solver @@ -104,28 +105,30 @@ Now, we assemble our configured components (`logger`) into the main Kaczmarz solver object. We will use the default compressor, logger and sub-solver. -```@example ConsistentExample +```julia # Create the Kaczmarz solver object by passing in the ingredients -kaczmarz_solver = Kaczmarz() +kaczmarz_solver = Kaczmarz(log = logger) ``` -Before we can run the solver, we must call `complete_solver`. +Before we can run the solver, we must call [`complete_solver`](@ref complete_solver). This function takes the solver configurations and the specific problem data `A, b, x_init` -and creates a `KaczmarzRecipe`. +and creates a [`KaczmarzRecipe`](@ref KaczmarzRecipe). The recipe pre-allocates all the necessary memory buffers for efficient computation. -```@example ConsistentExample +```julia # Create the solver recipe by combining the solver and the problem data solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) ``` -With the recipe fully prepared, we can now call `rsolve!` to run the Kaczmarz algorithm. -The function will iterate until the `stopping criterion` in the `logger` is met. +With the recipe fully prepared, we can now call [`rsolve!`](@ref rsolve!) to run the Kaczmarz algorithm. +The function will iterate until the stopping criterion in the `logger` is met. + +### Solve the system using the solver -The `rsolve!` function will modify `x_init` in-place, updating it with the +The [`rsolve!`](@ref rsolve!) function will modify `x_init` in-place, updating it with the calculated solution. -```@example ConsistentExample +```julia # Run the solver! rsolve!(solver_recipe, x_init, A, b) @@ -134,25 +137,3 @@ solution = x_init; ``` ---- -## 3. Verify the result - -Finally, let's check how close our calculated solution is to the known `x_true`. -We can do this by calculating the Euclidean norm of the difference between -the two vectors. A small error norm indicates a successful approximation. - -```@example ConsistentExample -# We can inspect the logger's history to see the convergence -error_history = solver_recipe.log.hist; -println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) -println(" - Final error: ", error_history[solver_recipe.log.record_location]) - -# Calculate the norm of the error -error_norm = norm(solution - x_true) -println(" - Norm of the error between the solution and x_true: ", error_norm) -``` -As you can see, by using the modular Kaczmarz solver, we were able to configure a -randomized block-based method and find a solution vector that is very close to -the true solution. - - From 543f3e5a0da3ec7fb2a9a1b70dd4ef7ceb433c72 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 15 Oct 2025 23:16:52 -0500 Subject: [PATCH 35/42] docs/reformulate --- .../consistent_system/consistent_system.md | 48 +++++++++---------- .../consistent_system_compressor.md | 3 +- src/Solvers/Loggers/basic_logger.jl | 2 +- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/docs/src/tutorials/consistent_system/consistent_system.md b/docs/src/tutorials/consistent_system/consistent_system.md index 5e64c3c5..759486c0 100644 --- a/docs/src/tutorials/consistent_system/consistent_system.md +++ b/docs/src/tutorials/consistent_system/consistent_system.md @@ -6,7 +6,7 @@ a **consistent linear system** of the form: $$Ax = b$$ --- -## Problem Setup +## Problem Setup and solve the system Let's define a specific linear system $Ax = b$. @@ -18,41 +18,33 @@ To achieve this, we need to import the required libraries and create the matrix and vector `b` as defined above. We will also set an initial guess, `x_init`, for the solver. -```@example ConsistentExample -# Import relevant libraries +```julia using RLinearAlgebra, LinearAlgebra - -# Define the dimensions of the linear system -num_rows, num_cols = 1000, 20 - -# Create the matrix A and a known true solution x_true +num_rows, num_cols = 1000, 20; A = randn(Float64, num_rows, num_cols); x_true = randn(Float64, num_cols); - -# Calculate the right-hand side vector b from A and x_true +x_init = zeros(Float64, num_cols); b = A * x_true; +``` -# Set an initial guess for the solution vector x (typically a zero vector) +```@setup ConsistentExample +using RLinearAlgebra, LinearAlgebra +num_rows, num_cols = 1000, 20; +A = randn(Float64, num_rows, num_cols); +x_true = randn(Float64, num_cols); x_init = zeros(Float64, num_cols); - -println("Dimensions:") -println(" - Matrix A: ", size(A)) -println(" - Vector b: ", size(b)) -println(" - True solution x_true: ", size(x_true)) -println(" - Initial guess x_init: ", size(x_true)) +b = A * x_true; ``` ---- -## Solve the system - -As simple as you can imagine, `RLinearAlgebra.jl` can solve the problem in just a -few lines of codes: +As simple as you can imagine, `RLinearAlgebra.jl` can solve this system in just a +few lines of codes and high efficiency: ```@example ConsistentExample -logger = BasicLogger(max_it = 500) +logger = BasicLogger(max_it = 10) kaczmarz_solver = Kaczmarz(log = logger) solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) rsolve!(solver_recipe, x_init, A, b) + solution = x_init; println("Solution to the system: \n", solution) ``` @@ -72,6 +64,9 @@ println(" - Final error: ", error_history[solver_recipe.log.record_location]) error_norm = norm(solution - x_true) println(" - Norm of the error between the solution and x_true: ", error_norm) ``` + + + As you can see, by using the modular Kaczmarz solver, we were able to configure a randomized block-based method and find a solution vector that is very close to the true solution. @@ -120,13 +115,14 @@ The recipe pre-allocates all the necessary memory buffers for efficient computat solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) ``` -With the recipe fully prepared, we can now call [`rsolve!`](@ref rsolve!) to run the Kaczmarz algorithm. +With the recipe fully prepared, we can now call [`rsolve!`](@ref rsolve!) +to run the Kaczmarz algorithm. The function will iterate until the stopping criterion in the `logger` is met. ### Solve the system using the solver -The [`rsolve!`](@ref rsolve!) function will modify `x_init` in-place, updating it with the -calculated solution. +The [`rsolve!`](@ref rsolve!) function will modify `x_init` in-place, updating +it with the calculated solution. ```julia # Run the solver! diff --git a/docs/src/tutorials/consistent_system/consistent_system_compressor.md b/docs/src/tutorials/consistent_system/consistent_system_compressor.md index b672d6e5..bf2c4ded 100644 --- a/docs/src/tutorials/consistent_system/consistent_system_compressor.md +++ b/docs/src/tutorials/consistent_system/consistent_system_compressor.md @@ -1,4 +1,4 @@ -# Solving a Consistent Linear System +# Compressor configurations This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve an overdetermined linear system (i.e., a least-squares problem) of the form: @@ -20,6 +20,7 @@ This guide demonstrates how to use the `SparseSign` compression method from the $$Ax = b$$ We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve for the vector $x$. + --- ## 1. Problem Setup diff --git a/src/Solvers/Loggers/basic_logger.jl b/src/Solvers/Loggers/basic_logger.jl index 21cbcdd5..6aebae49 100644 --- a/src/Solvers/Loggers/basic_logger.jl +++ b/src/Solvers/Loggers/basic_logger.jl @@ -93,7 +93,7 @@ function update_logger!(logger::BasicLoggerRecipe, error::Float64, iteration::In logger.error = error # Always check max_it stopping criterion # Compute in this way to avoid bounds error from searching in the max_it + 1 location - logger.converged = iteration <= logger.max_it ? + logger.converged = iteration < logger.max_it ? logger.stopping_criterion(logger) : true From 8863bcb8655c9c7c5603a6b1ec3bec1ef8610407 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 22 Oct 2025 10:58:58 -0500 Subject: [PATCH 36/42] docs/tutorial --- .../consistent_system/consistent_system.md | 118 ++++++------ .../consistent_system_compressor.md | 175 +++++++++--------- 2 files changed, 154 insertions(+), 139 deletions(-) diff --git a/docs/src/tutorials/consistent_system/consistent_system.md b/docs/src/tutorials/consistent_system/consistent_system.md index 759486c0..a9a8b36f 100644 --- a/docs/src/tutorials/consistent_system/consistent_system.md +++ b/docs/src/tutorials/consistent_system/consistent_system.md @@ -1,25 +1,29 @@ # Solving a Consistent Linear System -This guide demonstrates how to use `RLinearAlgebra.jl` package to find the solution to -a **consistent linear system** of the form: +This guide demonstrates how to use `RLinearAlgebra.jl` package to solve a +**consistent linear system**—a system where at least one solution +exists—expressed in the form: $$Ax = b$$ +We'll walk through setting up the problem, using a solver, and verifying the result. + --- -## Problem Setup and solve the system +## Problem setup and solve the system -Let's define a specific linear system $Ax = b$. +First, let's define our linear system $Ax = b$. -To verify the accuracy of the final result, suppose that we know the true solution -to the system, $x_{\text{true}}$, and then use it and a random generated -matrix $A$ to generate the vector $b$. +To easily verify the accuracy of our solver, we'll construct a problem where the true +solution, $x_{\text{true}}$, is known beforehand. We'll start by creating a random +matrix $A$ and a known solution vector $x_{\text{true}}$. Then, we can generate the +right-hand side vector $b$ by computing $b = Ax_{\text{true}}$. -To achieve this, we need to import the required libraries and create the matrix `A` -and vector `b` as defined above. -We will also set an initial guess, `x_init`, for the solver. +The following Julia code imports the necessary libraries, sets up the dimensions, and +creates $A$, $x_{\text{true}}$, and $b$. We also initialize a starting guess, `x_init`, +for our iterative solver. ```julia -using RLinearAlgebra, LinearAlgebra +using LinearAlgebra num_rows, num_cols = 1000, 20; A = randn(Float64, num_rows, num_cols); x_true = randn(Float64, num_cols); @@ -28,7 +32,7 @@ b = A * x_true; ``` ```@setup ConsistentExample -using RLinearAlgebra, LinearAlgebra +using LinearAlgebra num_rows, num_cols = 1000, 20; A = randn(Float64, num_rows, num_cols); x_true = randn(Float64, num_cols); @@ -40,7 +44,8 @@ As simple as you can imagine, `RLinearAlgebra.jl` can solve this system in just few lines of codes and high efficiency: ```@example ConsistentExample -logger = BasicLogger(max_it = 10) +using RLinearAlgebra +logger = BasicLogger(max_it = 300) kaczmarz_solver = Kaczmarz(log = logger) solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) rsolve!(solver_recipe, x_init, A, b) @@ -48,84 +53,78 @@ rsolve!(solver_recipe, x_init, A, b) solution = x_init; println("Solution to the system: \n", solution) ``` -Done! How simple it is! +**Done! How simple it is!** -let's check how close our calculated solution is to the known `x_true`. -We can do this by calculating the Euclidean norm of the difference between the two vectors. -A small error norm indicates a successful approximation. -```@example ConsistentExample -# We can inspect the logger's history to see the convergence -error_history = solver_recipe.log.hist; -println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) -println(" - Final error: ", error_history[solver_recipe.log.record_location]) +Let's check how close our calculated `solution` is to the known `x_true`. +We can measure the accuracy by calculating the Euclidean norm of the difference +between the two vectors. A small norm indicates that our solver found a good approximation. +```@example ConsistentExample # Calculate the norm of the error error_norm = norm(solution - x_true) println(" - Norm of the error between the solution and x_true: ", error_norm) ``` - - - As you can see, by using the modular Kaczmarz solver, we were able to configure a randomized block-based method and find a solution vector that is very close to the true solution. -Let's go line by line to see what are the codes doing. + +Let's break down the solver code line by line to understand what each part does. --- -## Steps to solve the problem +## Codes breakdown -Here, we choose to use the [Kaczmarz solver](@ref Kaczmarz) to solve the problem. -We can configure it by passing in "ingredient" objects for each of its main functions: - compressing the system, logging progress, and checking for errors. +As shown in the code, we used the [`Kaczmarz` solver](@ref Kaczmarz). A key feature of +**RLinearAlgebra.jl** is its modularity; you can customize the solver's behavior by passing +in different "component" objects for tasks, such as system compression, progress logging, +and termination checks. + +For this example, we kept it simple by only customizing the maximum iteration located +in [`Logger`](@ref Logger) component. Let's break down each step. ### Configure the logger -Start with only the simplest component, let's configure just the maximum iteration that -our algorithm can go. The configuration is located in the [`logger`](@ref Logger) -structure, which is responsible to record the error history, and tell the -solver when to stop. -Here, we will use the [`BasicLogger`](@ref BasicLogger). +We start with the simplest component: the [`Logger`](@ref Logger). The +[`Logger`](@ref Logger) is +responsible for tracking metrics (such as the error history) and telling the solver +when to stop. For this guide, we use the default [`BasicLogger`](@ref BasicLogger) +and configure +it with a single stopping criterion: a maximum number of iterations. ```julia -# Configure the maximum iteration to be 500 -logger = BasicLogger(max_it = 500) +# Configure the maximum iteration to be 300 +logger = BasicLogger(max_it = 300) ``` ### Create the solver -Now, we assemble our configured components (`logger`) into the main -Kaczmarz solver object. -We will use the default compressor, logger and sub-solver. +Before running the solver on our specific problem (`A, b, x_init`), we must prepare it +using the [`complete_solver`](@ref complete_solver) function. This function creates +a [`KaczmarzRecipe`](@ref KaczmarzRecipe), which combines the solver +configuration with the problem data. + +Crucially, this "recipe" pre-allocates all necessary memory buffers, which is a +key step for ensuring efficient and high-performance computation. ```julia # Create the Kaczmarz solver object by passing in the ingredients kaczmarz_solver = Kaczmarz(log = logger) -``` - -Before we can run the solver, we must call [`complete_solver`](@ref complete_solver). -This function takes the solver configurations and the specific problem data `A, b, x_init` -and creates a [`KaczmarzRecipe`](@ref KaczmarzRecipe). -The recipe pre-allocates all the necessary memory buffers for efficient computation. - -```julia # Create the solver recipe by combining the solver and the problem data solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) ``` -With the recipe fully prepared, we can now call [`rsolve!`](@ref rsolve!) -to run the Kaczmarz algorithm. -The function will iterate until the stopping criterion in the `logger` is met. - ### Solve the system using the solver -The [`rsolve!`](@ref rsolve!) function will modify `x_init` in-place, updating -it with the calculated solution. +Finally, we call [`rsolve!`](@ref rsolve!) to execute the algorithm. The `!` at the end +of the function name is a Julia convention indicating that the function will inplace +update part of its arguments. In this case, `rsolve!` modifies `x_init` in-place, +filling it with the final solution vector. The solver will iterate until a stopping +criterion in the `logger` is met, i.e. iteration goes up to $300$. ```julia -# Run the solver! +# Run the inplace solver! rsolve!(solver_recipe, x_init, A, b) # The solution is now stored in the updated x_init vector @@ -133,3 +132,12 @@ solution = x_init; ``` + + + + + + + + + diff --git a/docs/src/tutorials/consistent_system/consistent_system_compressor.md b/docs/src/tutorials/consistent_system/consistent_system_compressor.md index bf2c4ded..e5f6ab6a 100644 --- a/docs/src/tutorials/consistent_system/consistent_system_compressor.md +++ b/docs/src/tutorials/consistent_system/consistent_system_compressor.md @@ -1,40 +1,25 @@ -# Compressor configurations +# Deeper Dive: Modular Components -This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve an overdetermined linear system (i.e., a least-squares problem) of the form: +In the previous guide, we showed how to solve a consistent linear system in just a few +lines of codes. That example used the default configurations of the +[`Kaczmarz` solver](@ref Kaczmarz) solver, which is highly effective for many standard +problems. -$$\min_{x} \|Ax - b\|_2^2$$ +However, the true power of **RLinearAlgebra.jl** lies in its high degree of modularity +and flexibility. You can fine-tune the solver's behavior by combining different +ingradients, like cooking a fine dish, to tackle specific challenges, improve +performance, or implement more complex algorithms. -We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve the problem. -This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to solve a **consistent linear system**. Because the problem is constructed to have a known, exact solution, it serves as a perfect test case. -We will use an iterative solver that is broadly applicable to any least-squares problem, which finds a solution by minimizing the squared norm of the residual: +We will follow the design philosophy of **RLinearAlgebra.jl** by composing different +modules ([`Compressor`](@ref Compressor), [`Logger`](@ref Logger), [`Solver`](@ref Solver), +etc.) to customize a solver and solve the same consistent linear system, -$$\min_{x} \|Ax - b\|_2^2$$ +$$Ax = b.$$ -We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve the problem. - -This guide demonstrates how to use the `SparseSign` compression method from the `RLinearAlgebra.jl` package to find the exact solution to a **consistent linear system** of the form: - -$$Ax = b$$ - -We will follow the design philosophy of `RLinearAlgebra.jl` by composing different modules (`Solver`, `Compressor`, `Logger`, etc.) to build and solve for the vector $x$. - ---- -## 1. Problem Setup - -Let's define a specific linear system $Ax = b$. - -To verify the accuracy of the final result, suppose that we know the true solution of the system, $x_{\text{true}}$, and then use it and a random generated matrix $A$ to generate the vector $b$. - -* **Matrix `A`**: A random $1000 \times 20$ matrix. -* **Vector `b`**: Calculated as $b = A x_{\text{true}}$, with dimensions $1000 \times 1$. -* **Goal**: Find a solution $x$ that is as close as possible to $x_{\text{true}}$. - -To achieve this, we need to import the required libraries and create the matrix `A` and vector `b` as defined above. We will also set an initial guess, `x_init`, for the solver. - -```@example ConsistentExample +```@setup ConsistentExample # Import relevant libraries using RLinearAlgebra, Random, LinearAlgebra @@ -51,26 +36,25 @@ b = A * x_true; # Set an initial guess for the solution vector x (typically a zero vector) x_init = zeros(Float64, num_cols); - -println("Dimensions:") -println(" - Matrix A: ", size(A)) -println(" - Vector b: ", size(b)) -println(" - True solution x_true: ", size(x_true)) -println(" - Initial guess x_init: ", size(x_true)) ``` ---- -## 2. Create compressors -In practice, we may encounter a much larger $A$ matrix than what we have here. Solving the problem with such a large matrix can slow down the performance of iterative algorithms that we will use to solve the least square problem. Therefore, we will use a randomized sketching technique to compress the matrix A and the corresponding vector b to a lower dimension, while preserving the essential geometric information of the system. +--- +## 1. Configure [`Compressor`](@ref Compressor) -Here, we will use the sparse sign method. +For large-scale problems, the matrix $A$ can be massive, slowing down iterative algorithms. +We can use a randomized sketching technique to "compress" $A$ and $b$ to a lower dimension +while preserving the essential information of the system, s.t. we can solve the system +fast without the loss of accuracy. -### (a) Configure the `SparseSign` Compressor +Here, we'll configure a [`SparseSign`](@ref SparseSign) compressor as an example. +This compressor generates a sparse matrix $S$, whose non-zero elements are +1 or -1 +(with scaling). -The idea of randomized methods is to reduce the scale of the original problem when the dimention of matrix $A$ is too big, using a random "sketch" or "compression" matrix, $S$. Here, we choose `SparseSign` as our `Compressor`. This compressor generates a sparse matrix whose non-zero elements are +1 or -1 (with scaling). More information can be found [here](@ref SparseSign). +### (a) Configure the [`SparseSign`](@ref SparseSign) Compressor -We will configure a compression matrix `S` that compresses the 100 rows of the original system down to 30 rows. +We will configure a compression matrix that reduces the 1000 rows of our original +system down to a more manageable 300 rows. ```@example ConsistentExample # The goal is to compress the 1000 rows of A to 300 rows @@ -79,23 +63,38 @@ compression_dim = 300 non_zeros = 5 # Configure the SparseSign compressor -# - cardinality=Left(): Indicates the compression matrix S will be -# left-multiplied with A (SAx = Sb). -# - compression_dim: The compressed dimension (number of rows). -# - nnz: The number of non-zero elements per column (left)/row (right) in S. -# - type: The element type for the compression matrix. sparse_compressor = SparseSign( - cardinality=Left(), - compression_dim=compression_dim, - nnz=non_zeros, - type=Float64 + cardinality=Left(), # S will be left-multiplied: SAx = Sb + compression_dim=compression_dim, # The compressed dimension (number of rows) + nnz=non_zeros, # The number of non-zero elements per row in S + type=Float64 # The element type for the compression matrix ) ``` +If the compression dimension of `300` rows is considered too large, it can be changed to `10` by updating the compressor configuration and rebuilding the recipe as follows: + +```@example ConsistentExample +# Change the dimension of the compressor. Similarly, you can use the same idea +# for other configurations' changes. +sparse_compressor.compression_dim = 10 +``` + +The `sparse_compressor` is containing all the [`SparseSign`](@ref SparseSign) +configurations that we need. + +While the solver can use the `sparse_compressor` to perform the compression method +on-the-fly, we can stop here to configure other "ingradients". However, +it can sometimes be useful to form the compression matrix and the compressed +system explicitly to get an idea of your compression matrix. Therefore, we will +continue playing with it. + --- -### (b) Build the `SparseSign` recipe +### (b) Build the [`SparseSignRecipe`](@ref SparseSignRecipe) -After configuring the compressor, we need to combine it with our specific matrix `A` to create a `SparseSignRecipe`. This recipe contains the generated sparse matrix and all necessary information to perform the compression efficiently. +After defining the compressor's parameters, we combine it with our matrix $A$ to +create a [`SparseSignRecipe`](@ref SparseSignRecipe). This "recipe" +pre-calculates the sparse matrix `S` and prepares everything needed for +efficient compression. ```@example ConsistentExample # Pass the compressor configuration and the original matrix A to @@ -111,20 +110,10 @@ println(" - The number of nonzeros in each column (left)/row (right) of compress println(" - Compression matrix's nonzero entry values: ", S.scale) println(" - Compression matrix: ", S.op) ``` -If the compression dimension of `300` rows is considered too large, it can be changed to `10` by updating the compressor configuration and rebuilding the recipe as follows: -```@example ConsistentExample -# Change the dimension of the compressor. Similarly, you can use the same idea -# for other configurations' changes. -sparse_compressor.compression_dim = 10 - -# Rebuild the compressor recipe -S = complete_compressor(sparse_compressor, A) -println("Compression matrix's number of rows: ", S.n_rows) -``` ### (c) Apply the sparse sign matrix to the system -While the solver can use the `S` recipe to perform multiplications on-the-fly, it can sometimes be useful to form the compressed system explicitly. We can use `*` for this. +We can use `*` to apply this sparse matrix `S` to the system. ```@example ConsistentExample # Form the compressed system SAx = Sb @@ -137,17 +126,16 @@ println(" - Vector Sb: ", size(Sb)) ``` --- -## 3. Create solver - -With the problem and compressor defined, the next step is to choose and configure a solver. Here, we choose to use the -[Kaczmarz solver](@ref Kaczmarz). We configure it by passing in "ingredient" objects for each of its main functions: compressing the system (already done), logging progress, and checking for errors. - -### (a) Configure the logger and stopping rules +## 2. Configure [`Logger`](@ref Logger) -To monitor the solver, we will use a `BasicLogger`. This object will serve two purposes: record the error history, and tell the solver when to stop. +To monitor the solver and control its execution, we will configure a +[`BasicLogger`](@ref BasicLogger) . +This object serves two purposes: tracking metrics (like the error history) and +defining stopping rules. -We will configure it to stop after a maximum of `50` iterations or if the calculated error drops below a tolerance of `1e-6`. And we use `collection_rate = 5` -to configure the frequence of error recording to be every $5$ steps. +We'll configure it to stop after a maximum of `50` iterations or if the residual +error drops below `1e-6`. We will also set `collection_rate = 5` to record the +error every $5$ iterations. ```@example ConsistentExample # Configure the logger to control the solver's execution @@ -159,9 +147,14 @@ logger = BasicLogger( ``` --- -### (b) Build the Kaczmarz Solver -Now, we assemble our configured components (compressor `S`, logger `L`) into the main Kaczmarz solver object. We will use the default methods for error checking and the sub-solver to be LQ factorization ([LQSolver](@ref LQSolver)). +## 3. Configure [`Solver`](@ref Solver) +Now we assemble our configured ingradients—the `sparse_compressor` and the `logger`—into +the main [`Kaczmarz` solver](@ref Kaczmarz) object. For any component we don't specify, +a default will be used. Here, we'll explicitly specify the [LQSolver](@ref LQSolver) +as our sub-solver. + +### (a) Configure the [`Kaczmarz` solver](@ref Kaczmarz) ```@example ConsistentExample # Create the Kaczmarz solver object by passing in the ingredients kaczmarz_solver = Kaczmarz( @@ -170,19 +163,31 @@ kaczmarz_solver = Kaczmarz( sub_solver = LQSolver() ) ``` -Before we can run the solver, we must call `complete_solver`. This function takes the solver configurations and the specific problem data `A, b, x_init` and creates a `KaczmarzRecipe`. The recipe pre-allocates all the necessary memory buffers for efficient computation. + +### (b) Create the solver recipe + +Just as with the compressor, we must call [`complete_solver`](@ref complete_solver) to +create a final "recipe". This function takes the solver configuration and +the specific problem data (`A, b, x_init`) and pre-allocates all memory needed +for an efficient run. ```@example ConsistentExample # Create the solver recipe by combining the solver and the problem data solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) ``` + + --- -## 4. Solve the Compressed System +## 4. Solve and Verify the Result -With the recipe fully prepared, we can now call `rsolve!` to run the Kaczmarz algorithm. The function will iterate until the `stopping criterion` in the `logger` is met. +### (a) Solve the System -The `rsolve!` function will modify `x_init` in-place, updating it with the calculated solution. +We call [`rsolve!`](@ref rsolve!) to run the [`Kaczmarz` solver](@ref Kaczmarz). +The `!` in the name indicates that the function modifies its arguments in-place. +Here, `x_init` will be updated with the solution vector. +The algorithm will run until a stopping criterion from our +`logger` is met. ```@example ConsistentExample # Run the solver! @@ -192,19 +197,21 @@ rsolve!(solver_recipe, x_init, A, b) solution = x_init; ``` ---- -## 5. Verify the result +### (b). Verify the result -Finally, let's check how close our calculated solution is to the known `x_true`. We can do this by calculating the Euclidean norm of the difference between the two vectors. A small error norm indicates a successful approximation. +Finally, let's check how close our calculated solution is to the known +`x_true` and inspect the `logger` to see how the solver performed. ```@example ConsistentExample # We can inspect the logger's history to see the convergence error_history = solver_recipe.log.hist; println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) -println(" - Final error: ", error_history[solver_recipe.log.record_location]) +println(" - Final residual error, ||Ax-b||_2: ", error_history[solver_recipe.log.record_location]) # Calculate the norm of the error error_norm = norm(solution - x_true) println(" - Norm of the error between the solution and x_true: ", error_norm) ``` -As you can see, by using the modular Kaczmarz solver, we were able to configure a randomized block-based method and find a solution vector that is very close to the true solution. \ No newline at end of file + +As you can see, by composing different modules, we configured a randomized +solver that found a solution vector very close to the true solution. \ No newline at end of file From 52d83227c740dbd48c37e0479c26c7afef4f5a37 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 29 Oct 2025 12:09:47 -0500 Subject: [PATCH 37/42] 10.29 --- docs/make.jl | 2 +- .../consistent_system/consistent_system.md | 115 ++--------------- .../consistent_system_compressor.md | 120 ++++++++++++------ 3 files changed, 91 insertions(+), 146 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 1a600c93..cdff8d34 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,6 +18,7 @@ makedocs( "tutorials/consistent_system/consistent_system.md", "tutorials/consistent_system/consistent_system_compressor.md", ], + ], "Manual" => [ "Introduction" => "manual/introduction.md", "Compression" => "manual/compression.md", @@ -39,7 +40,6 @@ makedocs( ], ], "References" => "references.md", - ], ] ) diff --git a/docs/src/tutorials/consistent_system/consistent_system.md b/docs/src/tutorials/consistent_system/consistent_system.md index a9a8b36f..14064b9a 100644 --- a/docs/src/tutorials/consistent_system/consistent_system.md +++ b/docs/src/tutorials/consistent_system/consistent_system.md @@ -1,135 +1,42 @@ # Solving a Consistent Linear System This guide demonstrates how to use `RLinearAlgebra.jl` package to solve a -**consistent linear system**—a system where at least one solution -exists—expressed in the form: +**consistent linear system**---a system where at least one solution +exists---expressed in the form: -$$Ax = b$$ +$$Ax = b.$$ We'll walk through setting up the problem, using a solver, and verifying the result. ---- -## Problem setup and solve the system -First, let's define our linear system $Ax = b$. +First, let's define our linear system $Ax = b$ with some known solution `x_true`. -To easily verify the accuracy of our solver, we'll construct a problem where the true -solution, $x_{\text{true}}$, is known beforehand. We'll start by creating a random -matrix $A$ and a known solution vector $x_{\text{true}}$. Then, we can generate the -right-hand side vector $b$ by computing $b = Ax_{\text{true}}$. - -The following Julia code imports the necessary libraries, sets up the dimensions, and -creates $A$, $x_{\text{true}}$, and $b$. We also initialize a starting guess, `x_init`, -for our iterative solver. ```julia using LinearAlgebra -num_rows, num_cols = 1000, 20; +num_rows, num_cols = 1000, 50; A = randn(Float64, num_rows, num_cols); x_true = randn(Float64, num_cols); -x_init = zeros(Float64, num_cols); b = A * x_true; ``` ```@setup ConsistentExample using LinearAlgebra -num_rows, num_cols = 1000, 20; +num_rows, num_cols = 1000, 50; A = randn(Float64, num_rows, num_cols); x_true = randn(Float64, num_cols); -x_init = zeros(Float64, num_cols); b = A * x_true; ``` - -As simple as you can imagine, `RLinearAlgebra.jl` can solve this system in just a -few lines of codes and high efficiency: +`RLinearAlgebra.jl` can solve this system in just a few lines of codes: ```@example ConsistentExample using RLinearAlgebra -logger = BasicLogger(max_it = 300) -kaczmarz_solver = Kaczmarz(log = logger) -solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) -rsolve!(solver_recipe, x_init, A, b) - -solution = x_init; -println("Solution to the system: \n", solution) -``` -**Done! How simple it is!** - - -Let's check how close our calculated `solution` is to the known `x_true`. -We can measure the accuracy by calculating the Euclidean norm of the difference -between the two vectors. A small norm indicates that our solver found a good approximation. - -```@example ConsistentExample -# Calculate the norm of the error -error_norm = norm(solution - x_true) -println(" - Norm of the error between the solution and x_true: ", error_norm) -``` -As you can see, by using the modular Kaczmarz solver, we were able to configure a -randomized block-based method and find a solution vector that is very close to -the true solution. - - -Let's break down the solver code line by line to understand what each part does. - ---- -## Codes breakdown - -As shown in the code, we used the [`Kaczmarz` solver](@ref Kaczmarz). A key feature of -**RLinearAlgebra.jl** is its modularity; you can customize the solver's behavior by passing -in different "component" objects for tasks, such as system compression, progress logging, -and termination checks. - -For this example, we kept it simple by only customizing the maximum iteration located -in [`Logger`](@ref Logger) component. Let's break down each step. - - -### Configure the logger - -We start with the simplest component: the [`Logger`](@ref Logger). The -[`Logger`](@ref Logger) is -responsible for tracking metrics (such as the error history) and telling the solver -when to stop. For this guide, we use the default [`BasicLogger`](@ref BasicLogger) -and configure -it with a single stopping criterion: a maximum number of iterations. - -```julia -# Configure the maximum iteration to be 300 -logger = BasicLogger(max_it = 300) +solver = Kaczmarz(log = BasicLogger(max_it = 300)) +solution = zeros(Float64, num_cols) +rsolve!(solver, solution, A, b) ``` +`rsolve!` puts the solution in the vector `solution`. -### Create the solver - -Before running the solver on our specific problem (`A, b, x_init`), we must prepare it -using the [`complete_solver`](@ref complete_solver) function. This function creates -a [`KaczmarzRecipe`](@ref KaczmarzRecipe), which combines the solver -configuration with the problem data. - -Crucially, this "recipe" pre-allocates all necessary memory buffers, which is a -key step for ensuring efficient and high-performance computation. - -```julia -# Create the Kaczmarz solver object by passing in the ingredients -kaczmarz_solver = Kaczmarz(log = logger) -# Create the solver recipe by combining the solver and the problem data -solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) -``` - -### Solve the system using the solver - -Finally, we call [`rsolve!`](@ref rsolve!) to execute the algorithm. The `!` at the end -of the function name is a Julia convention indicating that the function will inplace -update part of its arguments. In this case, `rsolve!` modifies `x_init` in-place, -filling it with the final solution vector. The solver will iterate until a stopping -criterion in the `logger` is met, i.e. iteration goes up to $300$. - -```julia -# Run the inplace solver! -rsolve!(solver_recipe, x_init, A, b) - -# The solution is now stored in the updated x_init vector -solution = x_init; -``` diff --git a/docs/src/tutorials/consistent_system/consistent_system_compressor.md b/docs/src/tutorials/consistent_system/consistent_system_compressor.md index e5f6ab6a..ccf4e5b4 100644 --- a/docs/src/tutorials/consistent_system/consistent_system_compressor.md +++ b/docs/src/tutorials/consistent_system/consistent_system_compressor.md @@ -21,11 +21,11 @@ $$Ax = b.$$ ```@setup ConsistentExample # Import relevant libraries -using RLinearAlgebra, Random, LinearAlgebra +using RLinearAlgebra, LinearAlgebra # Define the dimensions of the linear system -num_rows, num_cols = 1000, 20 +num_rows, num_cols = 1000, 50 # Create the matrix A and a known true solution x_true A = randn(Float64, num_rows, num_cols); @@ -33,14 +33,11 @@ x_true = randn(Float64, num_cols); # Calculate the right-hand side vector b from A and x_true b = A * x_true; - -# Set an initial guess for the solution vector x (typically a zero vector) -x_init = zeros(Float64, num_cols); ``` --- -## 1. Configure [`Compressor`](@ref Compressor) +## Configure [`Compressor`](@ref Compressor) For large-scale problems, the matrix $A$ can be massive, slowing down iterative algorithms. We can use a randomized sketching technique to "compress" $A$ and $b$ to a lower dimension @@ -51,14 +48,14 @@ Here, we'll configure a [`SparseSign`](@ref SparseSign) compressor as an example This compressor generates a sparse matrix $S$, whose non-zero elements are +1 or -1 (with scaling). -### (a) Configure the [`SparseSign`](@ref SparseSign) Compressor +### Configure the [`SparseSign`](@ref SparseSign) Compressor We will configure a compression matrix that reduces the 1000 rows of our original -system down to a more manageable 300 rows. +system down to a more manageable $30$ rows. ```@example ConsistentExample -# The goal is to compress the 1000 rows of A to 300 rows -compression_dim = 300 +# The goal is to compress the 1000 rows of A to 30 rows +compression_dim = 30 # We want each row of the compression matrix S to have 5 non-zero elements non_zeros = 5 @@ -71,26 +68,30 @@ sparse_compressor = SparseSign( ) ``` -If the compression dimension of `300` rows is considered too large, it can be changed to `10` by updating the compressor configuration and rebuilding the recipe as follows: +If the compression dimension of `30` rows is considered too large, it can be changed to `10` by updating the compressor configuration and rebuilding the recipe as follows: -```@example ConsistentExample +```julia # Change the dimension of the compressor. Similarly, you can use the same idea # for other configurations' changes. -sparse_compressor.compression_dim = 10 +sparse_compressor.compression_dim = 10; +``` + +```@setup ConsistentExample +sparse_compressor.compression_dim = 10; ``` The `sparse_compressor` is containing all the [`SparseSign`](@ref SparseSign) configurations that we need. +--- +### (Optional) Build [SparseSignRecipe](@ref SparseSignRecipe) and apply it to the system + While the solver can use the `sparse_compressor` to perform the compression method on-the-fly, we can stop here to configure other "ingradients". However, it can sometimes be useful to form the compression matrix and the compressed system explicitly to get an idea of your compression matrix. Therefore, we will continue playing with it. ---- -### (b) Build the [`SparseSignRecipe`](@ref SparseSignRecipe) - After defining the compressor's parameters, we combine it with our matrix $A$ to create a [`SparseSignRecipe`](@ref SparseSignRecipe). This "recipe" pre-calculates the sparse matrix `S` and prepares everything needed for @@ -108,11 +109,8 @@ println(" - Compression matrix's number of rows: ", S.n_rows) println(" - Compression matrix's number of columns: ", S.n_cols) println(" - The number of nonzeros in each column (left)/row (right) of compression matrix: ", S.nnz) println(" - Compression matrix's nonzero entry values: ", S.scale) -println(" - Compression matrix: ", S.op) +println(" - Compression matrix: ", typeof(S.op), size(S.op)) ``` - -### (c) Apply the sparse sign matrix to the system - We can use `*` to apply this sparse matrix `S` to the system. ```@example ConsistentExample @@ -125,36 +123,61 @@ println(" - Matrix SA: ", size(SA)) println(" - Vector Sb: ", size(Sb)) ``` + --- -## 2. Configure [`Logger`](@ref Logger) +## Configure [`Logger`](@ref Logger) To monitor the solver and control its execution, we will configure a [`BasicLogger`](@ref BasicLogger) . This object serves two purposes: tracking metrics (like the error history) and defining stopping rules. -We'll configure it to stop after a maximum of `50` iterations or if the residual +We'll configure it to stop after a maximum of `500` iterations or if the residual error drops below `1e-6`. We will also set `collection_rate = 5` to record the error every $5$ iterations. ```@example ConsistentExample # Configure the logger to control the solver's execution logger = BasicLogger( - max_it = 50, + max_it = 500, threshold = 1e-6, collection_rate = 5 ) ``` +### (Optional) Build the [BasicLoggerRecipe](@ref BasicLoggerRecipe) + +Just like the compressor, this `logger` is just a set of instructions. +To make it "ready" to store data, we could call [complete_logger](@ref complete_logger) on it: + + +```@example ConsistentExample +# We can create the recipe manually, though this is rarely needed +logger_recipe = complete_logger(logger) +``` +This `logger_recipe` is the object that actually contains the hist vector for storing the error history, the current iteration, and the converged status. + +Again, you almost never need to call `complete_logger` yourself. +Because the solver (which we will configure next) or the [`rsolve!`](@ref rsolve!) +(the function that solves the system) +handles it for us. When we call [`complete_solver`](@ref complete_solver) or the +[`rsolve!`](@ref rsolve!), it will automatically find the +`logger` inside the solver, call `complete_logger` on it, +and store the resulting `logger_recipe` inside the final `solver_recipe`. + --- -## 3. Configure [`Solver`](@ref Solver) + + + + +## Configure [`Solver`](@ref Solver) Now we assemble our configured ingradients—the `sparse_compressor` and the `logger`—into the main [`Kaczmarz` solver](@ref Kaczmarz) object. For any component we don't specify, a default will be used. Here, we'll explicitly specify the [LQSolver](@ref LQSolver) as our sub-solver. -### (a) Configure the [`Kaczmarz` solver](@ref Kaczmarz) +### Configure the [`Kaczmarz` solver](@ref Kaczmarz) ```@example ConsistentExample # Create the Kaczmarz solver object by passing in the ingredients kaczmarz_solver = Kaczmarz( @@ -164,49 +187,64 @@ kaczmarz_solver = Kaczmarz( ) ``` -### (b) Create the solver recipe +### (Optional) create the [SolverRecipe](@ref SolverRecipe) + +This is the step where everything comes together. +Just as with the compressor, when we call +[`complete_solver`](@ref complete_solver), it takes the +`kaczmarz_solver` configurations and the problem data, and then: + +1. Pre-allocates memory for everything needed in the algorithm. -Just as with the compressor, we must call [`complete_solver`](@ref complete_solver) to -create a final "recipe". This function takes the solver configuration and -the specific problem data (`A, b, x_init`) and pre-allocates all memory needed -for an efficient run. +2. Finds the `sparse_compressor` config inside `kaczmarz_solver` and calls `complete_compressor` to create the `SparseSignRecipe`. + +3. Finds the `logger` inside `kaczmarz_solver` and calls `complete_logger` to create the `BasicLoggerRecipe`. + +4. Bundles all these "recipes" into a single, ready-to-use solver_recipe object. ```@example ConsistentExample +# Set the solution vector x (typically a zero vector) +solution = zeros(Float64, num_cols); # Create the solver recipe by combining the solver and the problem data -solver_recipe = complete_solver(kaczmarz_solver, x_init, A, b) +solver_recipe = complete_solver(kaczmarz_solver, solution, A, b); ``` +However, again, this step can also be skipped and directly pass the +solver config `kaczmarz_solver` into the function that can solve the system. --- -## 4. Solve and Verify the Result +## Solve and Verify the Result -### (a) Solve the System +### Solve the System We call [`rsolve!`](@ref rsolve!) to run the [`Kaczmarz` solver](@ref Kaczmarz). The `!` in the name indicates that the function modifies its arguments in-place. -Here, `x_init` will be updated with the solution vector. +Here, `solution` will be updated with the solution vector. The algorithm will run until a stopping criterion from our `logger` is met. ```@example ConsistentExample +# Set the solution vector x (typically a zero vector) +solution = zeros(Float64, num_cols); + # Run the solver! -rsolve!(solver_recipe, x_init, A, b) +_, solver_history = rsolve!(kaczmarz_solver, solution, A, b); -# The solution is now stored in the updated x_init vector -solution = x_init; +# The solution is now stored in the updated solution vector +solution ``` -### (b). Verify the result +### Verify the result Finally, let's check how close our calculated solution is to the known `x_true` and inspect the `logger` to see how the solver performed. ```@example ConsistentExample # We can inspect the logger's history to see the convergence -error_history = solver_recipe.log.hist; -println(" - Solver stopped at iteration: ", solver_recipe.log.iteration) -println(" - Final residual error, ||Ax-b||_2: ", error_history[solver_recipe.log.record_location]) +error_history = solver_history.log.hist; +println(" - Solver stopped at iteration: ", solver_history.log.iteration) +println(" - Final residual error, ||Ax-b||_2: ", error_history[solver_history.log.record_location]) # Calculate the norm of the error error_norm = norm(solution - x_true) From f492568d43a290208bab174398adf81c477b8d3c Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 5 Nov 2025 10:00:07 -0600 Subject: [PATCH 38/42] docs: fold the menu --- docs/make.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/make.jl b/docs/make.jl index cdff8d34..77c491b9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -8,6 +8,9 @@ bib = CitationBibliography( ) makedocs( sitename = "RLinearAlgebra", + format = Documenter.HTML( + collapselevel=1, + ), plugins=[bib], modules = [RLinearAlgebra], pages = [ From 4b85022cade600c13667d5c18c56d1ed7d97d621 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 5 Nov 2025 10:16:55 -0600 Subject: [PATCH 39/42] docs/copilot --- docs/src/tutorials/consistent_system/consistent_system.md | 2 +- .../consistent_system/consistent_system_compressor.md | 8 ++++---- docs/src/tutorials/introduction.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/tutorials/consistent_system/consistent_system.md b/docs/src/tutorials/consistent_system/consistent_system.md index 14064b9a..83dd0f81 100644 --- a/docs/src/tutorials/consistent_system/consistent_system.md +++ b/docs/src/tutorials/consistent_system/consistent_system.md @@ -27,7 +27,7 @@ A = randn(Float64, num_rows, num_cols); x_true = randn(Float64, num_cols); b = A * x_true; ``` -`RLinearAlgebra.jl` can solve this system in just a few lines of codes: +`RLinearAlgebra.jl` can solve this system in just a few lines of code: ```@example ConsistentExample using RLinearAlgebra diff --git a/docs/src/tutorials/consistent_system/consistent_system_compressor.md b/docs/src/tutorials/consistent_system/consistent_system_compressor.md index ccf4e5b4..3cd863ab 100644 --- a/docs/src/tutorials/consistent_system/consistent_system_compressor.md +++ b/docs/src/tutorials/consistent_system/consistent_system_compressor.md @@ -1,13 +1,13 @@ # Deeper Dive: Modular Components In the previous guide, we showed how to solve a consistent linear system in just a few -lines of codes. That example used the default configurations of the +lines of code. That example used the default configurations of the [`Kaczmarz` solver](@ref Kaczmarz) solver, which is highly effective for many standard problems. However, the true power of **RLinearAlgebra.jl** lies in its high degree of modularity and flexibility. You can fine-tune the solver's behavior by combining different -ingradients, like cooking a fine dish, to tackle specific challenges, improve +ingredients, like cooking a fine dish, to tackle specific challenges, improve performance, or implement more complex algorithms. @@ -87,7 +87,7 @@ configurations that we need. ### (Optional) Build [SparseSignRecipe](@ref SparseSignRecipe) and apply it to the system While the solver can use the `sparse_compressor` to perform the compression method -on-the-fly, we can stop here to configure other "ingradients". However, +on-the-fly, we can stop here to configure other "ingredients". However, it can sometimes be useful to form the compression matrix and the compressed system explicitly to get an idea of your compression matrix. Therefore, we will continue playing with it. @@ -172,7 +172,7 @@ and store the resulting `logger_recipe` inside the final `solver_recipe`. ## Configure [`Solver`](@ref Solver) -Now we assemble our configured ingradients—the `sparse_compressor` and the `logger`—into +Now we assemble our configured ingredients—the `sparse_compressor` and the `logger`—into the main [`Kaczmarz` solver](@ref Kaczmarz) object. For any component we don't specify, a default will be used. Here, we'll explicitly specify the [LQSolver](@ref LQSolver) as our sub-solver. diff --git a/docs/src/tutorials/introduction.md b/docs/src/tutorials/introduction.md index 3dec10c4..7529a8c4 100644 --- a/docs/src/tutorials/introduction.md +++ b/docs/src/tutorials/introduction.md @@ -1,6 +1,6 @@ # Introduction -The purpose of these tutorials is to use examples help new users quickly get hands +The purpose of these tutorials is to use examples to help new users quickly get hands-on experience using `RLinearAlgebra.jl`. ## How tutorials are structured From 86020449503a87a051b310aa3f434dc3ad44bbce Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 5 Nov 2025 10:24:17 -0600 Subject: [PATCH 40/42] docs/least_squares --- docs/make.jl | 4 ++++ docs/src/tutorials/least_squares/least_squares.md | 0 docs/src/tutorials/least_squares/least_squares_configure.md | 0 3 files changed, 4 insertions(+) create mode 100644 docs/src/tutorials/least_squares/least_squares.md create mode 100644 docs/src/tutorials/least_squares/least_squares_configure.md diff --git a/docs/make.jl b/docs/make.jl index 77c491b9..5f09d7b3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -21,6 +21,10 @@ makedocs( "tutorials/consistent_system/consistent_system.md", "tutorials/consistent_system/consistent_system_compressor.md", ], + "Least Squares" => [ + "tutorials/least_squares/least_squares.md", + "tutorials/least_squares/least_squares_configure.md", + ], ], "Manual" => [ "Introduction" => "manual/introduction.md", diff --git a/docs/src/tutorials/least_squares/least_squares.md b/docs/src/tutorials/least_squares/least_squares.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/src/tutorials/least_squares/least_squares_configure.md b/docs/src/tutorials/least_squares/least_squares_configure.md new file mode 100644 index 00000000..e69de29b From 7a7732b743bfe210824916c7a7b5f80d531d1e8a Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 5 Nov 2025 10:31:04 -0600 Subject: [PATCH 41/42] docs/change title --- docs/make.jl | 2 +- ...ystem_compressor.md => consistent_system_configure.md} | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) rename docs/src/tutorials/consistent_system/{consistent_system_compressor.md => consistent_system_configure.md} (98%) diff --git a/docs/make.jl b/docs/make.jl index 77c491b9..09784df5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -19,7 +19,7 @@ makedocs( "Introduction" => "tutorials/introduction.md", "Consistent Linear System" => [ "tutorials/consistent_system/consistent_system.md", - "tutorials/consistent_system/consistent_system_compressor.md", + "tutorials/consistent_system/consistent_system_configure.md", ], ], "Manual" => [ diff --git a/docs/src/tutorials/consistent_system/consistent_system_compressor.md b/docs/src/tutorials/consistent_system/consistent_system_configure.md similarity index 98% rename from docs/src/tutorials/consistent_system/consistent_system_compressor.md rename to docs/src/tutorials/consistent_system/consistent_system_configure.md index 3cd863ab..9353ac9b 100644 --- a/docs/src/tutorials/consistent_system/consistent_system_compressor.md +++ b/docs/src/tutorials/consistent_system/consistent_system_configure.md @@ -1,4 +1,4 @@ -# Deeper Dive: Modular Components +# Modular Customization: Beyond Defaults In the previous guide, we showed how to solve a consistent linear system in just a few lines of code. That example used the default configurations of the @@ -148,14 +148,16 @@ logger = BasicLogger( ### (Optional) Build the [BasicLoggerRecipe](@ref BasicLoggerRecipe) Just like the compressor, this `logger` is just a set of instructions. -To make it "ready" to store data, we could call [complete_logger](@ref complete_logger) on it: +To make it "ready" to store data, we could call [complete_logger](@ref complete_logger) +on it: ```@example ConsistentExample # We can create the recipe manually, though this is rarely needed logger_recipe = complete_logger(logger) ``` -This `logger_recipe` is the object that actually contains the hist vector for storing the error history, the current iteration, and the converged status. +This `logger_recipe` is the object that actually contains the hist vector for +storing the error history, the current iteration, and the converged status. Again, you almost never need to call `complete_logger` yourself. Because the solver (which we will configure next) or the [`rsolve!`](@ref rsolve!) From b3d7dbbeff404fe4d63f8ce49752f3b055160dd5 Mon Sep 17 00:00:00 2001 From: Tunan Wang Date: Wed, 5 Nov 2025 10:50:53 -0600 Subject: [PATCH 42/42] docs/least_squares_short_introduction --- docs/src/tutorials/introduction.md | 1 + .../tutorials/least_squares/least_squares.md | 43 +++++++++++++++++++ .../least_squares/least_squares_configure.md | 5 +++ 3 files changed, 49 insertions(+) diff --git a/docs/src/tutorials/introduction.md b/docs/src/tutorials/introduction.md index 7529a8c4..ab23e1f2 100644 --- a/docs/src/tutorials/introduction.md +++ b/docs/src/tutorials/introduction.md @@ -7,6 +7,7 @@ experience using `RLinearAlgebra.jl`. Problem sets: - Consistent system problem, solving $x$ s.t. $$Ax = b$$. +- Least squares problem, solving $x = \arg\displaystyle\min_{x}\|Ax - b\|_2^2$. diff --git a/docs/src/tutorials/least_squares/least_squares.md b/docs/src/tutorials/least_squares/least_squares.md index e69de29b..17235024 100644 --- a/docs/src/tutorials/least_squares/least_squares.md +++ b/docs/src/tutorials/least_squares/least_squares.md @@ -0,0 +1,43 @@ +# Solving a Least-Squares Problem + +This guide demonstrates how to use `RLinearAlgebra.jl` package to find the +**least-squares solution** to a linear system. This is typically used for +*inconsistent* systems, where no exact $Ax=b$ solution exists. + +The goal is to find the vector $x$ that minimizes the squared Euclidean norm +of the residual: + +$$\min_{x} \|Ax - b\|_2^2$$ + +We'll walk through a simple example of setting up and solving such a problem. + +First, let's define an *inconsistent* linear system. We'll do this by creating a +known `x_true` and adding noise to make $b \approx Ax_{\text{true}}$. + +```julia +using LinearAlgebra +num_rows, num_cols = 1000, 50; +A = randn(Float64, num_rows, num_cols); +x_true = randn(Float64, num_cols); +noise = 0.1 * randn(Float64, num_rows); +b = (A * x_true) + noise; +``` + +```@setup LeastSquaresExample +using LinearAlgebra +num_rows, num_cols = 1000, 50; +A = randn(Float64, num_rows, num_cols); +x_true = randn(Float64, num_cols); +noise = 0.1 * randn(Float64, num_rows); +b = (A * x_true) + noise; +``` +`RLinearAlgebra.jl` can find the least-squares solution using the same +simple interface as in the consistent system example: + +```@example LeastSquaresExample +using RLinearAlgebra +solver = Kaczmarz(log = BasicLogger(max_it = 300)) +solution = zeros(Float64, num_cols) +rsolve!(solver, solution, A, b) +``` +`rsolve!` finds the least-squares solution and stores it in the solution vector. diff --git a/docs/src/tutorials/least_squares/least_squares_configure.md b/docs/src/tutorials/least_squares/least_squares_configure.md index e69de29b..ff3f5c55 100644 --- a/docs/src/tutorials/least_squares/least_squares_configure.md +++ b/docs/src/tutorials/least_squares/least_squares_configure.md @@ -0,0 +1,5 @@ +# Modular Customization: More Compressors + + +This doc will introduce more compressors, and left the other modules, +such as loggers, approximators, solvers, etc. later. \ No newline at end of file