From 53c4e557ba9e4abe715250a0e0633a3fb79e551e Mon Sep 17 00:00:00 2001 From: Nathaniel pritchard Date: Mon, 10 Feb 2025 19:23:24 +0000 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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