Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ jobs:
benchmark:
name: Benchmark
runs-on: ubuntu-latest

# needed to allow julia-actions/cache to delete old caches that it has created
permissions:
actions: write
contents: read

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
- uses: julia-actions/setup-julia@v1
with:
version: '1'
- uses: julia-actions/cache@v2
- run: git fetch origin '+refs/heads/master:refs/remotes/origin/master'
- run: git branch master origin/master
- run: |
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ jobs:
build:
name: Documentation
runs-on: ubuntu-latest

# needed to allow julia-actions/cache to delete old caches that it has created
permissions:
actions: write
contents: read

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
- uses: julia-actions/setup-julia@latest
with:
version: '1'
- uses: julia-actions/cache@v2
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
Expand Down
40 changes: 25 additions & 15 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,40 @@ on:
- master
pull_request:
workflow_dispatch:

jobs:
build:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
name: Julia ${{ matrix.julia-version }} - ${{ matrix.os }} - ${{ matrix.julia-arch }}
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
version:
- '1'
- '1.6'
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- x64
julia-version: ['lts', '1']
julia-arch: [x64]
os: [ubuntu-latest, windows-latest, macOS-latest]

# needed to allow julia-actions/cache to delete old caches that it has created
permissions:
actions: write
contents: read

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
- uses: julia-actions/setup-julia@latest
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
version: ${{ matrix.julia-version }}
arch: ${{ matrix.julia-arch }}
- uses: julia-actions/cache@v2
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
- uses: julia-actions/julia-uploadcodecov@latest
- uses: julia-actions/julia-runtest@v1
with:
coverage: true
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v5
# Upload coverage only from one job (Linux, Julia latest version)
if: matrix.os == 'ubuntu-latest' && matrix.julia-version == '1'
with:
files: lcov.info
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
11 changes: 9 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "ProximalOperators"
uuid = "a725b495-10eb-56fe-b38b-717eba820537"
version = "0.16.1"
version = "0.17.0"

[deps]
IterativeSolvers = "42fd0dbc-a981-5370-80f2-aaf504508153"
Expand All @@ -11,11 +11,18 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
SuiteSparse = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"
TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e"

[weakdeps]
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"

[extensions]
RecursiveArrayToolsExt = "RecursiveArrayTools"

[compat]
IterativeSolvers = "0.8 - 0.9"
LinearAlgebra = "1.4"
OSQP = "0.3 - 0.8"
ProximalCore = "0.1"
ProximalCore = "0.2"
RecursiveArrayTools = "2, 3"
SparseArrays = "1.4"
SuiteSparse = "1.4"
TSVD = "0.3 - 0.4"
Expand Down
19 changes: 19 additions & 0 deletions ext/RecursiveArrayToolsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module RecursiveArrayToolsExt
using RecursiveArrayTools
using ProximalOperators
import ProximalCore: prox, prox!, gradient, gradient!

(f::PrecomposedSlicedSeparableSum)(x::ArrayPartition) = f(x.x)
prox!(y::ArrayPartition, f::PrecomposedSlicedSeparableSum, x::ArrayPartition, gamma) = prox!(y.x, f, x.x, gamma)

(g::SeparableSum)(xs::ArrayPartition) = g(xs.x)
prox!(ys::ArrayPartition, g::SeparableSum, xs::ArrayPartition, gamma::Number) = prox!(ys.x, g, xs.x, gamma)
prox!(ys::ArrayPartition, g::SeparableSum, xs::ArrayPartition, gammas::Tuple) = prox!(ys.x, g, xs.x, gammas)
function prox(g::SeparableSum, xs::ArrayPartition, gamma=1)
y, fy = prox(g, xs.x, gamma)
return ArrayPartition(y), fy
end
gradient!(grads::ArrayPartition, g::SeparableSum, xs::ArrayPartition) = gradient!(grads.x, g, xs.x)
gradient(g::SeparableSum, xs::ArrayPartition) = gradient(g, xs.x)

end # module RecursiveArrayToolsExt
31 changes: 22 additions & 9 deletions src/ProximalOperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,27 @@ module ProximalOperators

using LinearAlgebra
import ProximalCore: prox, prox!, gradient, gradient!
import ProximalCore: is_convex, is_generalized_quadratic
import ProximalCore:
is_convex,
is_strongly_convex,
is_generalized_quadratic,
is_proximable,
is_separable,
is_singleton_indicator,
is_cone_indicator,
is_affine_indicator,
is_set_indicator,
is_smooth,
is_locally_smooth,
is_support

const RealOrComplex{R <: Real} = Union{R, Complex{R}}
const HermOrSym{T, S} = Union{Hermitian{T, S}, Symmetric{T, S}}
const RealBasedArray{R} = AbstractArray{C, N} where {C <: RealOrComplex{R}, N}
const TupleOfArrays{R} = Tuple{RealBasedArray{R}, Vararg{RealBasedArray{R}}}
const ArrayOrTuple{R} = Union{RealBasedArray{R}, TupleOfArrays{R}}
const TransposeOrAdjoint{M} = Union{Transpose{C,M} where C, Adjoint{C,M} where C}
const Maybe{T} = Union{T, Nothing}
const RealOrComplex{R<:Real} = Union{R,Complex{R}}
const HermOrSym{T,S} = Union{Hermitian{T,S},Symmetric{T,S}}
const RealBasedArray{R} = AbstractArray{C,N} where {C<:RealOrComplex{R},N}
const TupleOfArrays{R} = Tuple{RealBasedArray{R},Vararg{RealBasedArray{R}}}
const ArrayOrTuple{R} = Union{RealBasedArray{R},TupleOfArrays{R}}
const TransposeOrAdjoint{M} = Union{Transpose{C,M} where C,Adjoint{C,M} where C}
const Maybe{T} = Union{T,Nothing}

export prox, prox!, gradient, gradient!

Expand All @@ -23,7 +35,6 @@ include("utilities/linops.jl")
include("utilities/symmetricpacked.jl")
include("utilities/uniformarrays.jl")
include("utilities/normdiff.jl")
include("utilities/traits.jl")

# Basic functions

Expand Down Expand Up @@ -80,6 +91,8 @@ include("calculus/precomposeDiagonal.jl")
include("calculus/regularize.jl")
include("calculus/separableSum.jl")
include("calculus/slicedSeparableSum.jl")
include("calculus/precomposedSlicedSeparableSum.jl")
include("calculus/reshapeInput.jl")
include("calculus/sqrDistL2.jl")
include("calculus/tilt.jl")
include("calculus/translate.jl")
Expand Down
12 changes: 6 additions & 6 deletions src/calculus/conjugate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ struct Conjugate{T}
end
end

is_prox_accurate(::Type{Conjugate{T}}) where T = is_prox_accurate(T)
is_proximable(::Type{Conjugate{T}}) where T = is_proximable(T)
is_convex(::Type{Conjugate{T}}) where T = true
is_cone(::Type{Conjugate{T}}) where T = is_cone(T) && is_convex(T)
is_cone_indicator(::Type{Conjugate{T}}) where T = is_cone_indicator(T) && is_convex(T)
is_smooth(::Type{Conjugate{T}}) where T = is_strongly_convex(T)
is_strongly_convex(::Type{Conjugate{T}}) where T = is_smooth(T)
is_generalized_quadratic(::Type{Conjugate{T}}) where T = is_generalized_quadratic(T)
is_set(::Type{Conjugate{T}}) where T = is_convex(T) && is_support(T)
is_positively_homogeneous(::Type{Conjugate{T}}) where T = is_convex(T) && is_set(T)
is_set_indicator(::Type{Conjugate{T}}) where T = is_convex(T) && is_support(T)
is_positively_homogeneous(::Type{Conjugate{T}}) where T = is_convex(T) && is_set_indicator(T)

Conjugate(f::T) where T = Conjugate{T}(f)

Expand All @@ -37,7 +37,7 @@ Conjugate(f::T) where T = Conjugate{T}(f)
function prox!(y, g::Conjugate, x, gamma)
# Moreau identity
v = prox!(y, g.f, x/gamma, 1/gamma)
if is_set(g)
if is_set_indicator(g)
v = real(eltype(x))(0)
else
v = real(dot(x, y)) - gamma * real(dot(y, y)) - v
Expand All @@ -50,7 +50,7 @@ end

function prox_naive(g::Conjugate, x, gamma)
y, v = prox_naive(g.f, x/gamma, 1/gamma)
return x - gamma * y, if is_set(g) real(eltype(x))(0) else real(dot(x, y)) - gamma * real(dot(y, y)) - v end
return x - gamma * y, if is_set_indicator(g) real(eltype(x))(0) else real(dot(x, y)) - gamma * real(dot(y, y)) - v end
end

# TODO: hard-code conjugation rules? E.g. precompose/epicompose
5 changes: 3 additions & 2 deletions src/calculus/distL2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct DistL2{R, T}
ind::T
lambda::R
function DistL2{R, T}(ind::T, lambda::R) where {R, T}
if !is_set(ind)
if !is_set_indicator(ind)
error("`ind` must be a convex set")
end
if lambda <= 0
Expand All @@ -25,7 +25,8 @@ struct DistL2{R, T}
end
end

is_prox_accurate(::Type{DistL2{R, T}}) where {R, T} = is_prox_accurate(T)
is_proximable(::Type{DistL2{R, T}}) where {R, T} = is_proximable(T)
is_locally_smooth(::Type{DistL2{R, T}}) where {R, T} = is_proximable(T)
is_convex(::Type{DistL2{R, T}}) where {R, T} = is_convex(T)

DistL2(ind::T, lambda::R=1) where {R, T} = DistL2{R, T}(ind, lambda)
Expand Down
4 changes: 2 additions & 2 deletions src/calculus/pointwiseMinimum.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ PointwiseMinimum(fs...) = PointwiseMinimum{typeof(fs)}(fs)

component_types(::Type{PointwiseMinimum{T}}) where T = fieldtypes(T)

@generated is_set(::Type{T}) where T <: PointwiseMinimum = return all(is_set, component_types(T)) ? :(true) : :(false)
@generated is_cone(::Type{T}) where T <: PointwiseMinimum = return all(is_cone, component_types(T)) ? :(true) : :(false)
@generated is_set_indicator(::Type{T}) where T <: PointwiseMinimum = return all(is_set_indicator, component_types(T)) ? :(true) : :(false)
@generated is_cone_indicator(::Type{T}) where T <: PointwiseMinimum = return all(is_cone_indicator, component_types(T)) ? :(true) : :(false)

function (g::PointwiseMinimum{T})(x) where T
return minimum(f(x) for f in g.fs)
Expand Down
11 changes: 6 additions & 5 deletions src/calculus/postcompose.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ struct Postcompose{T, R, S}
end
end

is_prox_accurate(::Type{<:Postcompose{T}}) where T = is_prox_accurate(T)
is_proximable(::Type{<:Postcompose{T}}) where T = is_proximable(T)
is_separable(::Type{<:Postcompose{T}}) where T = is_separable(T)
is_convex(::Type{<:Postcompose{T}}) where T = is_convex(T)
is_set(::Type{<:Postcompose{T}}) where T = is_set(T)
is_singleton(::Type{<:Postcompose{T}}) where T = is_singleton(T)
is_cone(::Type{<:Postcompose{T}}) where T = is_cone(T)
is_affine(::Type{<:Postcompose{T}}) where T = is_affine(T)
is_set_indicator(::Type{<:Postcompose{T}}) where T = is_set_indicator(T)
is_singleton_indicator(::Type{<:Postcompose{T}}) where T = is_singleton_indicator(T)
is_cone_indicator(::Type{<:Postcompose{T}}) where T = is_cone_indicator(T)
is_affine_indicator(::Type{<:Postcompose{T}}) where T = is_affine_indicator(T)
is_smooth(::Type{<:Postcompose{T}}) where T = is_smooth(T)
is_locally_smooth(::Type{<:Postcompose{T}}) where T = is_locally_smooth(T)
is_generalized_quadratic(::Type{<:Postcompose{T}}) where T = is_generalized_quadratic(T)
is_strongly_convex(::Type{<:Postcompose{T}}) where T = is_strongly_convex(T)

Expand Down
21 changes: 14 additions & 7 deletions src/calculus/precompose.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ struct Precompose{T, M, U, V}
end
end

is_prox_accurate(::Type{<:Precompose{T}}) where T = is_prox_accurate(T)
is_proximable(::Type{<:Precompose{T}}) where T = is_proximable(T)
is_convex(::Type{<:Precompose{T}}) where T = is_convex(T)
is_set(::Type{<:Precompose{T}}) where T = is_set(T)
is_singleton(::Type{<:Precompose{T}}) where T = is_singleton(T)
is_cone(::Type{<:Precompose{T}}) where T = is_cone(T)
is_affine(::Type{<:Precompose{T}}) where T = is_affine(T)
is_set_indicator(::Type{<:Precompose{T}}) where T = is_set_indicator(T)
is_singleton_indicator(::Type{<:Precompose{T}}) where T = is_singleton_indicator(T)
is_cone_indicator(::Type{<:Precompose{T}}) where T = is_cone_indicator(T)
is_affine_indicator(::Type{<:Precompose{T}}) where T = is_affine_indicator(T)
is_smooth(::Type{<:Precompose{T}}) where T = is_smooth(T)
is_locally_smooth(::Type{<:Precompose{T}}) where T = is_locally_smooth(T)
is_generalized_quadratic(::Type{<:Precompose{T}}) where T = is_generalized_quadratic(T)
is_strongly_convex(::Type{<:Precompose{T}}) where T = is_strongly_convex(T)

Expand All @@ -56,7 +57,10 @@ function (g::Precompose)(x)
end

function gradient!(y, g::Precompose, x)
res = g.L*x .+ g.b
res = g.L*x
if g.b != 0
res .+= g.b
end
gradres = similar(res)
v = gradient!(gradres, g.f, res)
mul!(y, adjoint(g.L), gradres)
Expand All @@ -74,7 +78,10 @@ function prox!(y, g::Precompose, x, gamma)
# prox_f(x) = prox_h(x + b) - b
# Then one can apply the above mentioned result to g(x) = f(Lx).
#
res = g.L*x .+ g.b
res = g.L * x
if g.b != 0
res .+= g.b
end
proxres = similar(res)
v = prox!(proxres, g.f, res, g.mu.*gamma)
proxres .-= res
Expand Down
11 changes: 6 additions & 5 deletions src/calculus/precomposeDiagonal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ struct PrecomposeDiagonal{T, R, S}
end

is_separable(::Type{<:PrecomposeDiagonal{T}}) where T = is_separable(T)
is_prox_accurate(::Type{<:PrecomposeDiagonal{T}}) where T = is_prox_accurate(T)
is_proximable(::Type{<:PrecomposeDiagonal{T}}) where T = is_proximable(T)
is_convex(::Type{<:PrecomposeDiagonal{T}}) where T = is_convex(T)
is_set(::Type{<:PrecomposeDiagonal{T}}) where T = is_set(T)
is_singleton(::Type{<:PrecomposeDiagonal{T}}) where T = is_singleton(T)
is_cone(::Type{<:PrecomposeDiagonal{T}}) where T = is_cone(T)
is_affine(::Type{<:PrecomposeDiagonal{T}}) where T = is_affine(T)
is_set_indicator(::Type{<:PrecomposeDiagonal{T}}) where T = is_set_indicator(T)
is_singleton_indicator(::Type{<:PrecomposeDiagonal{T}}) where T = is_singleton_indicator(T)
is_cone_indicator(::Type{<:PrecomposeDiagonal{T}}) where T = is_cone_indicator(T)
is_affine_indicator(::Type{<:PrecomposeDiagonal{T}}) where T = is_affine_indicator(T)
is_smooth(::Type{<:PrecomposeDiagonal{T}}) where T = is_smooth(T)
is_locally_smooth(::Type{<:PrecomposeDiagonal{T}}) where T = is_locally_smooth(T)
is_generalized_quadratic(::Type{<:PrecomposeDiagonal{T}}) where T = is_generalized_quadratic(T)
is_strongly_convex(::Type{<:PrecomposeDiagonal{T}}) where T = is_strongly_convex(T)

Expand Down
Loading