From ca52a1e76ccf2c31e739f2dcd62f92727c78a50f Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sun, 12 Oct 2025 06:44:09 +0530 Subject: [PATCH 01/11] Tutorial update --- docs/src/tutorial.md | 69 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 876cd0a0..2f825a88 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -1 +1,68 @@ -You can check an [Introduction to LinearOperators.jl](https://jso.dev/tutorials/introduction-to-linear-operators/) on our site, [jso.dev](https://jso.dev/). +# Tutorial + +This page follows the Documenter.jl tutorial layout. It includes runnable examples for common usage patterns in LinearOperators.jl. + +```@contents +Pages = ["tutorial.md"] +``` + +## Getting started + +Calling into LinearOperators is simple: + +```@docs +using LinearOperators +``` + +## Simple examples + +Construct an operator from a matrix and use it like a matrix: + +```@example ex_matrix +using LinearOperators + +A = [1.0 2.0; 3.0 4.0] +op = LinearOperator(A) +y = op * [1.0, 1.0] +M = Matrix(op) +println("y = ", y) +println("Matrix(op) = \n", M) +``` + +Create function-based operators (showing the 5-arg mul! signature): + +```@example ex_fun +n = 4 +function mymul!(res, v, α, β) + if β == 0 + res .= α .* v .* (1:n) + else + res .= α .* v .* (1:n) .+ β .* res + end +end +opfun = LinearOperator(Float64, n, n, false, false, mymul!) +println(opfun * ones(n)) +``` + +Diagonal operators (use `opDiagonal`): + +```@example ex_diag +d = [2.0, 3.0, 4.0] +D = opDiagonal(d) +println(D * ones(3)) +``` + +Composing operators with vertical concatenation: + +```@example ex_cat +A = rand(3,3); B = rand(3,3) +opA = LinearOperator(A); opB = LinearOperator(B) +opcat = [opA; opB] +println(size(opcat)) +``` + +## Return values and tips + +- The `LinearOperator` type implements `*` and can be converted to a `Matrix` when necessary. +- Prefer function-based operators when you want to avoid materializing large matrices. +- See the full introduction: [Introduction to LinearOperators.jl](https://jso.dev/tutorials/introduction-to-linear-operators/) From ce862061403122afc72542cddd9b678952ebdef7 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sun, 19 Oct 2025 02:53:45 +0530 Subject: [PATCH 02/11] Storage-type promotion --- src/cat.jl | 22 ++++++++++++++++++---- src/operations.jl | 22 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/cat.jl b/src/cat.jl index 764c7893..7fc4bd5d 100644 --- a/src/cat.jl +++ b/src/cat.jl @@ -46,8 +46,15 @@ function hcat(A::AbstractLinearOperator, B::AbstractLinearOperator) hcat_ctprod!(res, adjoint(A), adjoint(B), Ancol, Ancol + Bncol, w, α, β) args5 = (has_args5(A) && has_args5(B)) S = promote_type(storage_type(A), storage_type(B)) - isconcretetype(S) || - throw(LinearOperatorException("storage types cannot be promoted to a concrete type")) + if !isconcretetype(S) + if isconcretetype(storage_type(A)) + S = storage_type(A) + elseif isconcretetype(storage_type(B)) + S = storage_type(B) + else + S = Vector{T} + end + end CompositeLinearOperator(T, nrow, ncol, false, false, prod!, tprod!, ctprod!, args5, S = S) end @@ -105,8 +112,15 @@ function vcat(A::AbstractLinearOperator, B::AbstractLinearOperator) vcat_ctprod!(res, adjoint(A), adjoint(B), Anrow, Anrow + Bnrow, w, α, β) args5 = (has_args5(A) && has_args5(B)) S = promote_type(storage_type(A), storage_type(B)) - isconcretetype(S) || - throw(LinearOperatorException("storage types cannot be promoted to a concrete type")) + if !isconcretetype(S) + if isconcretetype(storage_type(A)) + S = storage_type(A) + elseif isconcretetype(storage_type(B)) + S = storage_type(B) + else + S = Vector{T} + end + end CompositeLinearOperator(T, nrow, ncol, false, false, prod!, tprod!, ctprod!, args5, S = S) end diff --git a/src/operations.jl b/src/operations.jl index 18259d40..a65e3407 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -140,8 +140,15 @@ function *(op1::AbstractLinearOperator, op2::AbstractLinearOperator) throw(LinearOperatorException("shape mismatch")) end S = promote_type(storage_type(op1), storage_type(op2)) - isconcretetype(S) || - throw(LinearOperatorException("storage types cannot be promoted to a concrete type")) + if !isconcretetype(S) + if isconcretetype(storage_type(op1)) + S = storage_type(op1) + elseif isconcretetype(storage_type(op2)) + S = storage_type(op2) + else + S = Vector{T} + end + end #tmp vector for products vtmp = fill!(S(undef, m2), zero(T)) utmp = fill!(S(undef, n1), zero(T)) @@ -211,8 +218,15 @@ function +(op1::AbstractLinearOperator, op2::AbstractLinearOperator) herm = (ishermitian(op1) && ishermitian(op2)) args5 = (has_args5(op1) && has_args5(op2)) S = promote_type(storage_type(op1), storage_type(op2)) - isconcretetype(S) || - throw(LinearOperatorException("storage types cannot be promoted to a concrete type")) + if !isconcretetype(S) + if isconcretetype(storage_type(op1)) + S = storage_type(op1) + elseif isconcretetype(storage_type(op2)) + S = storage_type(op2) + else + S = Vector{T} + end + end return CompositeLinearOperator(T, m1, n1, symm, herm, prod!, tprod!, ctprod!, args5, S = S) end From a4f8f70f15f676649a544cf7162b624d6ba8c7a6 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sun, 19 Oct 2025 02:57:02 +0530 Subject: [PATCH 03/11] Update tutorial.md --- docs/src/tutorial.md | 69 +------------------------------------------- 1 file changed, 1 insertion(+), 68 deletions(-) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 2f825a88..876cd0a0 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -1,68 +1 @@ -# Tutorial - -This page follows the Documenter.jl tutorial layout. It includes runnable examples for common usage patterns in LinearOperators.jl. - -```@contents -Pages = ["tutorial.md"] -``` - -## Getting started - -Calling into LinearOperators is simple: - -```@docs -using LinearOperators -``` - -## Simple examples - -Construct an operator from a matrix and use it like a matrix: - -```@example ex_matrix -using LinearOperators - -A = [1.0 2.0; 3.0 4.0] -op = LinearOperator(A) -y = op * [1.0, 1.0] -M = Matrix(op) -println("y = ", y) -println("Matrix(op) = \n", M) -``` - -Create function-based operators (showing the 5-arg mul! signature): - -```@example ex_fun -n = 4 -function mymul!(res, v, α, β) - if β == 0 - res .= α .* v .* (1:n) - else - res .= α .* v .* (1:n) .+ β .* res - end -end -opfun = LinearOperator(Float64, n, n, false, false, mymul!) -println(opfun * ones(n)) -``` - -Diagonal operators (use `opDiagonal`): - -```@example ex_diag -d = [2.0, 3.0, 4.0] -D = opDiagonal(d) -println(D * ones(3)) -``` - -Composing operators with vertical concatenation: - -```@example ex_cat -A = rand(3,3); B = rand(3,3) -opA = LinearOperator(A); opB = LinearOperator(B) -opcat = [opA; opB] -println(size(opcat)) -``` - -## Return values and tips - -- The `LinearOperator` type implements `*` and can be converted to a `Matrix` when necessary. -- Prefer function-based operators when you want to avoid materializing large matrices. -- See the full introduction: [Introduction to LinearOperators.jl](https://jso.dev/tutorials/introduction-to-linear-operators/) +You can check an [Introduction to LinearOperators.jl](https://jso.dev/tutorials/introduction-to-linear-operators/) on our site, [jso.dev](https://jso.dev/). From 3dfa5ff415aa8d745f972ed3e5a43b2c3de06e73 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Wed, 22 Oct 2025 04:48:59 +0530 Subject: [PATCH 04/11] Centralized storage selection is now used by both operations.jl and cat.jl consistently (tests still pass functionally; only the known two tiny allocation assertions remain). --- src/abstract.jl | 17 +++++++++++++++++ src/cat.jl | 22 ++-------------------- src/operations.jl | 22 ++-------------------- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index c3940a33..2f33b0f7 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -179,6 +179,23 @@ storage_type(op::Adjoint) = storage_type(parent(op)) storage_type(op::Transpose) = storage_type(parent(op)) storage_type(op::Diagonal) = typeof(parent(op)) +@inline function _select_storage_type( + op1::AbstractLinearOperator, + op2::AbstractLinearOperator, + T::Type, +) + S = promote_type(storage_type(op1), storage_type(op2)) + if isconcretetype(S) + return S + elseif isconcretetype(storage_type(op1)) + return storage_type(op1) + elseif isconcretetype(storage_type(op2)) + return storage_type(op2) + else + return Vector{T} + end +end + """ reset!(op) diff --git a/src/cat.jl b/src/cat.jl index 7fc4bd5d..b20e1be4 100644 --- a/src/cat.jl +++ b/src/cat.jl @@ -45,16 +45,7 @@ function hcat(A::AbstractLinearOperator, B::AbstractLinearOperator) ctprod! = @closure (res, w, α, β) -> hcat_ctprod!(res, adjoint(A), adjoint(B), Ancol, Ancol + Bncol, w, α, β) args5 = (has_args5(A) && has_args5(B)) - S = promote_type(storage_type(A), storage_type(B)) - if !isconcretetype(S) - if isconcretetype(storage_type(A)) - S = storage_type(A) - elseif isconcretetype(storage_type(B)) - S = storage_type(B) - else - S = Vector{T} - end - end + S = _select_storage_type(A, B, T) CompositeLinearOperator(T, nrow, ncol, false, false, prod!, tprod!, ctprod!, args5, S = S) end @@ -111,16 +102,7 @@ function vcat(A::AbstractLinearOperator, B::AbstractLinearOperator) ctprod! = @closure (res, w, α, β) -> vcat_ctprod!(res, adjoint(A), adjoint(B), Anrow, Anrow + Bnrow, w, α, β) args5 = (has_args5(A) && has_args5(B)) - S = promote_type(storage_type(A), storage_type(B)) - if !isconcretetype(S) - if isconcretetype(storage_type(A)) - S = storage_type(A) - elseif isconcretetype(storage_type(B)) - S = storage_type(B) - else - S = Vector{T} - end - end + S = _select_storage_type(A, B, T) CompositeLinearOperator(T, nrow, ncol, false, false, prod!, tprod!, ctprod!, args5, S = S) end diff --git a/src/operations.jl b/src/operations.jl index a65e3407..b02803d3 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -139,16 +139,7 @@ function *(op1::AbstractLinearOperator, op2::AbstractLinearOperator) if m2 != n1 throw(LinearOperatorException("shape mismatch")) end - S = promote_type(storage_type(op1), storage_type(op2)) - if !isconcretetype(S) - if isconcretetype(storage_type(op1)) - S = storage_type(op1) - elseif isconcretetype(storage_type(op2)) - S = storage_type(op2) - else - S = Vector{T} - end - end + S = _select_storage_type(op1, op2, T) #tmp vector for products vtmp = fill!(S(undef, m2), zero(T)) utmp = fill!(S(undef, n1), zero(T)) @@ -217,16 +208,7 @@ function +(op1::AbstractLinearOperator, op2::AbstractLinearOperator) symm = (issymmetric(op1) && issymmetric(op2)) herm = (ishermitian(op1) && ishermitian(op2)) args5 = (has_args5(op1) && has_args5(op2)) - S = promote_type(storage_type(op1), storage_type(op2)) - if !isconcretetype(S) - if isconcretetype(storage_type(op1)) - S = storage_type(op1) - elseif isconcretetype(storage_type(op2)) - S = storage_type(op2) - else - S = Vector{T} - end - end + S = _select_storage_type(op1, op2, T) return CompositeLinearOperator(T, m1, n1, symm, herm, prod!, tprod!, ctprod!, args5, S = S) end From 4f61445419861d7412b700e121fe88e551a27a3f Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 21 Feb 2026 05:03:59 +0530 Subject: [PATCH 05/11] Update src/operations.jl Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/operations.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/operations.jl b/src/operations.jl index de3399e4..896248fc 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -140,6 +140,11 @@ function *(op1::AbstractLinearOperator, op2::AbstractLinearOperator) throw(LinearOperatorException("shape mismatch")) end S = _select_storage_type(op1, op2, T) + # Ensure that the selected storage type is concrete; fall back to a + # standard vector storage when promotion yields a non-concrete type. + if !isconcretetype(S) + S = Vector{T} + end #tmp vector for products vtmp = fill!(S(undef, m2), zero(T)) utmp = fill!(S(undef, n1), zero(T)) From 2f5bd21d140775c7767d39fbdbdecd02bfaebe34 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 27 Feb 2026 14:14:15 +0530 Subject: [PATCH 06/11] Throw informative error instead of implicit storage type casting Remove _select_storage_type function that was silently falling back to alternative storage types when promotion failed. This implicit behavior could cause severe performance degradation (e.g., GPU->CPU fallback). Now throws a helpful LinearOperatorException when storage types cannot be promoted to a concrete type, guiding users to ensure compatible storage types across operators. This aligns with Julia's philosophy of making performance characteristics explicit and prevents mysterious performance issues. --- src/abstract.jl | 17 ----------------- src/operations.jl | 23 +++++++++++++++++------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index ceba9886..7f8ba015 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -231,23 +231,6 @@ storage_type(op::Adjoint) = storage_type(parent(op)) storage_type(op::Transpose) = storage_type(parent(op)) storage_type(op::Diagonal) = typeof(parent(op)) -@inline function _select_storage_type( - op1::AbstractLinearOperator, - op2::AbstractLinearOperator, - T::Type, -) - S = promote_type(storage_type(op1), storage_type(op2)) - if isconcretetype(S) - return S - elseif isconcretetype(storage_type(op1)) - return storage_type(op1) - elseif isconcretetype(storage_type(op2)) - return storage_type(op2) - else - return Vector{T} - end -end - """ reset!(op) diff --git a/src/operations.jl b/src/operations.jl index 896248fc..ebb56141 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -139,11 +139,15 @@ function *(op1::AbstractLinearOperator, op2::AbstractLinearOperator) if m2 != n1 throw(LinearOperatorException("shape mismatch")) end - S = _select_storage_type(op1, op2, T) - # Ensure that the selected storage type is concrete; fall back to a - # standard vector storage when promotion yields a non-concrete type. + S = promote_type(storage_type(op1), storage_type(op2)) if !isconcretetype(S) - S = Vector{T} + throw( + LinearOperatorException( + "storage types $(storage_type(op1)) and $(storage_type(op2)) " * + "cannot be promoted to a concrete type. " * + "Ensure both operators use compatible storage types (e.g., both GPU or both CPU).", + ), + ) end #tmp vector for products vtmp = fill!(S(undef, m2), zero(T)) @@ -214,8 +218,15 @@ function +(op1::AbstractLinearOperator, op2::AbstractLinearOperator) herm = (ishermitian(op1) && ishermitian(op2)) args5 = (has_args5(op1) && has_args5(op2)) S = promote_type(storage_type(op1), storage_type(op2)) - isconcretetype(S) || - throw(LinearOperatorException("storage types cannot be promoted to a concrete type")) + if !isconcretetype(S) + throw( + LinearOperatorException( + "storage types $(storage_type(op1)) and $(storage_type(op2)) " * + "cannot be promoted to a concrete type. " * + "Ensure both operators use compatible storage types (e.g., both GPU or both CPU).", + ), + ) + end return CompositeLinearOperator(T, m1, n1, symm, herm, prod!, tprod!, ctprod!, args5, S) end From 98d622afb1a79c8c5ecb1a9008fbacdf205a5fa1 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 27 Feb 2026 22:42:05 +0530 Subject: [PATCH 07/11] Add in-place LinearOperator update support for issue #196 --- src/abstract.jl | 52 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_linop.jl | 25 ++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/abstract.jl b/src/abstract.jl index 7f8ba015..07632ac6 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -24,6 +24,8 @@ const LinearOperatorIndexType{I} = # import methods we overload import Base.eltype, Base.isreal, Base.size, Base.show +import Base.copy!, Base.copyto! +import Base.Broadcast: BroadcastStyle, DefaultArrayStyle, Broadcasted, broadcastable, materialize! import LinearAlgebra.Symmetric, LinearAlgebra.issymmetric, LinearAlgebra.Hermitian, LinearAlgebra.ishermitian @@ -231,6 +233,56 @@ storage_type(op::Adjoint) = storage_type(parent(op)) storage_type(op::Transpose) = storage_type(parent(op)) storage_type(op::Diagonal) = typeof(parent(op)) +broadcastable(op::AbstractLinearOperator) = Ref(op) +BroadcastStyle(::Type{<:AbstractLinearOperator}) = DefaultArrayStyle{0}() + +function copyto!( + dest::LinearOperator{T, S, I, F, Ft, Fct}, + src::LinearOperator{T, S, I, F, Ft, Fct}, +) where {T, S, I <: Integer, F, Ft, Fct} + dest.nrow = src.nrow + dest.ncol = src.ncol + dest.symmetric = src.symmetric + dest.hermitian = src.hermitian + dest.prod! = src.prod! + dest.tprod! = src.tprod! + dest.ctprod! = src.ctprod! + dest.nprod = src.nprod + dest.ntprod = src.ntprod + dest.nctprod = src.nctprod + dest.args5 = src.args5 + dest.use_prod5! = src.use_prod5! + dest.Mv5 = src.Mv5 + dest.Mtu5 = src.Mtu5 + dest.allocated5 = src.allocated5 + return dest +end + +function copyto!(dest::LinearOperator, src::LinearOperator) + throw( + LinearOperatorException( + "cannot update a LinearOperator in-place from an incompatible operator type. " * + "Use assignment (`op = new_op`) instead.", + ), + ) +end + +copy!(dest::LinearOperator, src::LinearOperator) = copyto!(dest, src) + +function materialize!(dest::LinearOperator, bc::Broadcasted{DefaultArrayStyle{0}}) + (bc.f === identity && length(bc.args) == 1) || + throw( + LinearOperatorException( + "only broadcast assignment of a single LinearOperator is supported (e.g., `op .= new_op`).", + ), + ) + src_arg = bc.args[1] + src = src_arg isa Ref ? src_arg[] : src_arg + src isa LinearOperator || + throw(LinearOperatorException("right-hand side of `op .= ...` must be a LinearOperator")) + return copyto!(dest, src) +end + """ reset!(op) diff --git a/test/test_linop.jl b/test/test_linop.jl index 71039448..6b0aad57 100644 --- a/test/test_linop.jl +++ b/test/test_linop.jl @@ -111,6 +111,31 @@ function test_linop() @test Matrix(Hermitian(op6)) == (A6 + adjoint(A6)) / 2 end + @testset "In-place operator update" begin + A = rand(5, 5) + B = rand(5, 5) + x = rand(5) + + opA = LinearOperator(A) + opB = LinearOperator(B) + + copyto!(opA, opB) + @test opA * x ≈ B * x + + C = rand(5, 5) + opC = LinearOperator(C) + copy!(opA, opC) + @test opA * x ≈ C * x + + opA .= opB + @test opA * x ≈ B * x + + @test_throws LinearOperatorException opA .+= opB + + op_complex = LinearOperator(rand(ComplexF64, 5, 5)) + @test_throws LinearOperatorException copyto!(opA, op_complex) + end + @testset "Constructor with specified structure" begin v = simple_vector(Float64, nrow) A = simple_matrix(ComplexF64, nrow, nrow) From 843ca098e6ad9a0f3d98bb134dd5ecc9cf92a109 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 27 Feb 2026 23:13:31 +0530 Subject: [PATCH 08/11] Relax copyto! type constraints to only require matching element and storage types --- src/abstract.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index b1add3ce..9d9a2ac7 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -189,9 +189,9 @@ broadcastable(op::AbstractLinearOperator) = Ref(op) BroadcastStyle(::Type{<:AbstractLinearOperator}) = DefaultArrayStyle{0}() function copyto!( - dest::LinearOperator{T, S, I, F, Ft, Fct}, - src::LinearOperator{T, S, I, F, Ft, Fct}, -) where {T, S, I <: Integer, F, Ft, Fct} + dest::LinearOperator{T, S}, + src::LinearOperator{T, S}, +) where {T, S} dest.nrow = src.nrow dest.ncol = src.ncol dest.symmetric = src.symmetric @@ -213,8 +213,10 @@ end function copyto!(dest::LinearOperator, src::LinearOperator) throw( LinearOperatorException( - "cannot update a LinearOperator in-place from an incompatible operator type. " * - "Use assignment (`op = new_op`) instead.", + "cannot update a LinearOperator in-place: incompatible element types " * + "$(eltype(dest)) and $(eltype(src)), or storage types " * + "$(typeof(dest.Mv5)) and $(typeof(src.Mv5)). " * + "Use assignment (`dest = src`) instead.", ), ) end From 74dde904afc48000ab7756907e181772e2737a73 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 28 Feb 2026 00:46:57 +0530 Subject: [PATCH 09/11] Remove const modifiers to enable in-place operator updates The recent struct changes added const modifiers to LinearOperator fields for performance. However, this makes copyto! impossible since const fields cannot be modified after construction. This commit removes const from the struct fields to enable the in-place update feature requested in issue #196. While this may have minor performance implications, it is necessary for the .= syntax to work. Also updated copyto! implementation to match current struct fields (Mv/Mtu instead of Mv5/Mtu5/args5/use_prod5/allocated5). --- src/abstract.jl | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index 9d9a2ac7..43f8415c 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -46,13 +46,13 @@ other operators, with matrices and with scalars. Operators may be transposed and conjugate-transposed using the usual Julia syntax. """ mutable struct LinearOperator{T, S, I <: Integer, F, Ft, Fct} <: AbstractLinearOperator{T} - const nrow::I - const ncol::I - const symmetric::Bool - const hermitian::Bool - const prod!::F - const tprod!::Ft - const ctprod!::Fct + nrow::I + ncol::I + symmetric::Bool + hermitian::Bool + prod!::F + tprod!::Ft + ctprod!::Fct nprod::I ntprod::I nctprod::I @@ -202,11 +202,8 @@ function copyto!( dest.nprod = src.nprod dest.ntprod = src.ntprod dest.nctprod = src.nctprod - dest.args5 = src.args5 - dest.use_prod5! = src.use_prod5! - dest.Mv5 = src.Mv5 - dest.Mtu5 = src.Mtu5 - dest.allocated5 = src.allocated5 + dest.Mv = src.Mv + dest.Mtu = src.Mtu return dest end @@ -215,7 +212,7 @@ function copyto!(dest::LinearOperator, src::LinearOperator) LinearOperatorException( "cannot update a LinearOperator in-place: incompatible element types " * "$(eltype(dest)) and $(eltype(src)), or storage types " * - "$(typeof(dest.Mv5)) and $(typeof(src.Mv5)). " * + "$(typeof(dest.Mv)) and $(typeof(src.Mv)). " * "Use assignment (`dest = src`) instead.", ), ) From b0f434b618c3efbc01dd6a942564f9b488aa8ca2 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 28 Feb 2026 00:53:51 +0530 Subject: [PATCH 10/11] Fix operator addition to use correct constructor After struct changes, operations.jl was calling the old CompositeLinearOperator constructor with args5 parameter which no longer exists. Updated to use the simplified LinearOperator{T,S} constructor. --- src/operations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations.jl b/src/operations.jl index ad05cf9d..c9890d3a 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -218,7 +218,7 @@ function +(op1::AbstractLinearOperator, op2::AbstractLinearOperator) ), ) end - return CompositeLinearOperator(T, m1, n1, symm, herm, prod!, tprod!, ctprod!, args5, S) + return LinearOperator{T, S}(m1, n1, symm, herm, prod!, tprod!, ctprod!) end # Operator + matrix. From 440d716ad7767d4b40b78ca61223082ecf271a5a Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 14 Mar 2026 00:54:55 +0530 Subject: [PATCH 11/11] Update operations.jl --- src/operations.jl | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/operations.jl b/src/operations.jl index c9890d3a..94c4e76d 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -209,15 +209,8 @@ function +(op1::AbstractLinearOperator, op2::AbstractLinearOperator) symm = (issymmetric(op1) && issymmetric(op2)) herm = (ishermitian(op1) && ishermitian(op2)) S = promote_type(storage_type(op1), storage_type(op2)) - if !isconcretetype(S) - throw( - LinearOperatorException( - "storage types $(storage_type(op1)) and $(storage_type(op2)) " * - "cannot be promoted to a concrete type. " * - "Ensure both operators use compatible storage types (e.g., both GPU or both CPU).", - ), - ) - end + isconcretetype(S) || + throw(LinearOperatorException("storage types cannot be promoted to a concrete type")) return LinearOperator{T, S}(m1, n1, symm, herm, prod!, tprod!, ctprod!) end