Skip to content
This repository has been archived by the owner on Jul 19, 2023. It is now read-only.

Linear Combinations of Operators #55

Merged
merged 10 commits into from
Mar 6, 2018
5 changes: 5 additions & 0 deletions src/DiffEqOperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ __precompile__()
module DiffEqOperators

import LinearMaps: LinearMap, AbstractLinearMap
using LinearMaps: LinearCombination, IdentityMap
import Base: *, getindex
using DiffEqBase, StaticArrays
import DiffEqBase: update_coefficients, update_coefficients!
Expand All @@ -21,6 +22,10 @@ include("derivative_operators/derivative_operator.jl")
include("derivative_operators/abstract_operator_functions.jl")
include("derivative_operators/boundary_operators.jl")

### Linear Combination of Operators
include("operator_combination.jl")

export DiffEqScalar, DiffEqArrayOperator
export AbstractDerivativeOperator, DerivativeOperator, UpwindOperator, FiniteDifference
export normbound
end # module
13 changes: 3 additions & 10 deletions src/array_operator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,11 @@ Base.issymmetric(L::DiffEqArrayOperator) = L._issymmetric
Base.ishermitian(L::DiffEqArrayOperator) = L._ishermitian
Base.isposdef(L::DiffEqArrayOperator) = L._isposdef
DiffEqBase.is_constant(L::DiffEqArrayOperator) = L.update_func == DEFAULT_UPDATE_FUNC
function Base.expm(L::DiffEqArrayOperator)
tmp = full(L.A) # If not lazy then this is a no-op
tmp .*= L.α.coeff
out = expm(tmp)
if tmp === L.A
L.A ./= L.α.coeff # Undo change if not lazy
end
out
end
Base.full(L::DiffEqArrayOperator) = full(L.A) * L.α.coeff
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.* this so that way it's compatible with coefficient arrays. That'll come up in space-dependent coefficients and quasi-linear PDEs.

Base.expm(L::DiffEqArrayOperator) = expm(full(L))
DiffEqBase.has_expm(L::DiffEqArrayOperator) = true
Base.size(L::DiffEqArrayOperator) = size(L.A)
Base.norm(L::DiffEqArrayOperator, p::Real=2) = norm(L.A, p)
Base.norm(L::DiffEqArrayOperator, p::Real=2) = norm(L.A, p) * abs(L.α.coeff)
DiffEqBase.update_coefficients!(L::DiffEqArrayOperator,t,u) = (L.update_func(L.A,t,u); L.α = L.α(t); nothing)
DiffEqBase.update_coefficients(L::DiffEqArrayOperator,t,u) = (L.update_func(L.A,t,u); L.α = L.α(t); L)

Expand Down
8 changes: 8 additions & 0 deletions src/derivative_operators/abstract_operator_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,11 @@ function Base.sparse(A::AbstractDerivativeOperator{T}) where T
end
return mat
end

#=
Fallback methods that use the full representation of the operator
=#
Base.expm(A::AbstractDerivativeOperator{T}) where T = expm(full(A))
Base.:/(A::AbstractVecOrMat, B::AbstractDerivativeOperator) = A / full(B)
Base.:/(A::AbstractDerivativeOperator, B::AbstractVecOrMat) = full(A) / B
# Base.:\ is also defined
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC isn't / defined in terms of \ with a transpose? It may be better to do that one directly. I would use @which to dive into the Base code and see how / is called.

Copy link
Contributor Author

@MSeeker1340 MSeeker1340 Mar 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented out this portion and found that

using DiffEqOperators
N = 20
A = DerivativeOperator{Float64}(2,2,1.0,N,:Dirichlet0,:Dirichlet0)
mat = rand(N,N)

@which A / mat
no method found for the specified argument types

@which mat' \ A'
\(x, y) at operators.jl:457

The definition is

\(x,y) = (y'/x')'

So Julia does in fact define \ in terms of / for generic types. However for regular matrices the relationship is inverted:

# In linalg\generic.jl:820
(/)(A::AbstractVecOrMat, B::AbstractVecOrMat) = (B' \ A')'

Do you think I should define / using this convention, or just go with the generic one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might need both. The transpose operator will be hard to do in general, and right now I think it's a no-op which only holds for some BCs and since we have the operator as n x n. Transpose on the dense matrix will always work though.

52 changes: 52 additions & 0 deletions src/operator_combination.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#=
Most of the functionality for linear combination of operators is already
covered in LinearMaps.jl.
=#

(L::LinearCombination)(u,p,t) = L*u
(L::LinearCombination)(du,u,p,t) = A_mul_B!(du,L,u)

#=
The fallback implementation in LinearMaps.jl effectively computes A*eye(N),
which is very inefficient.

Instead, build up the full matrix for each operator iteratively.
=#
# TODO: Type dispatch for this is incorrect at the moment
# function Base.full(A::LinearCombination{T,Tuple{Vararg{O}},Ts}) where {T,O<:Union{AbstractDiffEqLinearOperator,IdentityMap},Ts}
# out = zeros(T,size(A))
# for i = 1:length(A.maps)
# c = A.coeffs[i]
# op = A.maps[i]
# if isa(op, IdentityMap)
# @. out += c * eye(size(A,1))
# else
# @. out += c * full(op)
# end
# end
# return out
# end

#=
Fallback methods that use the full representation
=#
Base.expm(A::LinearCombination) = expm(full(A))
Base.:/(A::AbstractVecOrMat, B::LinearCombination) = A / full(B)
Base.:/(A::LinearCombination, B::AbstractVecOrMat) = full(A) / B

Base.norm(A::IdentityMap{T}, p::Real=2) where T = real(one(T))
Base.norm(A::LinearCombination, p::Real=2) = norm(full(A), p)
Copy link
Member

@ChrisRackauckas ChrisRackauckas Mar 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lazy way to do normest2 which will be required when A doesn't fit into memory, but we can handle that later. Probably worth a comment and an issue though.

#=
The norm of A+B is difficult to calculate, but in many applications we only
need an estimate of the norm (e.g. for error analysis) so it makes sense to
compute the upper bound given by the triangle inequality

|A + B| <= |A| + |B|

For derivative operators A and B, their Inf norm can be calculated easily
and thus so is the Inf norm bound of A + B.
=#
normbound(a::Number, p::Real=2) = abs(a)
normbound(A::AbstractArray, p::Real=2) = norm(A, p)
normbound(A::Union{AbstractDiffEqLinearOperator,IdentityMap}, p::Real=2) = norm(A, p)
normbound(A::LinearCombination, p::Real=2) = sum(abs.(A.coeffs) .* normbound.(A.maps, p))
20 changes: 20 additions & 0 deletions test/array_operators_interface.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using DiffEqOperators
using Base.Test

N = 5
srand(0); A = rand(N,N); u = rand(N)
L = DiffEqArrayOperator(A)
a = 3.5
La = L * a

@test La * u ≈ (a*A) * u
@test lufact(La) \ u ≈ (a*A) \ u
@test norm(La) ≈ norm(a*A)
@test expm(La) ≈ expm(a*A)
@test La[2,3] ≈ A[2,3] # should this be La[2,3] == a*A[2,3]?

update_func = (_A,t,u) -> _A .= t * A
t = 3.0
Atmp = zeros(N,N)
Lt = DiffEqArrayOperator(Atmp, a, update_func)
@test Lt(u,nothing,t) ≈ (a*t*A) * u
22 changes: 22 additions & 0 deletions test/derivative_operators_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,25 @@ end
@test G*B ≈ 8*ones(N,M) atol=1e-2
@test A*G*B ≈ zeros(N,M) atol=1e-2
end

@testset "Linear combinations of operators" begin
# Only tests the additional functionality defined in "operator_combination.jl"
N = 10
srand(0); LA = DiffEqArrayOperator(rand(N,N))
LD = DerivativeOperator{Float64}(2,2,1.0,N,:Dirichlet0,:Dirichlet0)
L = 1.1*LA - 2.2*LD + 3.3*I
# Builds full(L) the brute-force way
fullL = zeros(N,N)
v = zeros(N)
for i = 1:N
v[i] = 1.0
fullL[:,i] = L*v
v[i] = 0.0
end
@test full(L) ≈ fullL
@test expm(L) ≈ expm(fullL)
for p in [1,2,Inf]
@test norm(L,p) ≈ norm(fullL,p)
@test normbound(L,p) ≈ 1.1*norm(LA,p) + 2.2*norm(LD,p) + 3.3
end
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ using Base.Test
using SpecialMatrices, SpecialFunctions

tic()
@time @testset "Array Operators Interface" begin include("array_operators_interface.jl") end
@time @testset "Derivative Operators Interface" begin include("derivative_operators_interface.jl") end
@time @testset "Dirichlet BCs" begin include("dirichlet.jl") end
@time @testset "Periodic BCs" begin include("periodic.jl") end
Expand Down