Skip to content

Commit

Permalink
Introduce support of subsolver records. (#377)
Browse files Browse the repository at this point in the history
* Initial sketch for proper support of subsolver records.
* Refactor :Subsolver to the less ambiguous :WhenActive indicator. Rename `RecordSubsolverRecordings` to `RecordSubsolver` and use `:Subsolver` there instead (in recordings, not debug).
* First working subsolver record version.
* fix docs.
* Test coverage I.
* Resolve #378 here.
* Test coverage II
* improve test coverage.
* Improve documentation.
* bump version.
  • Loading branch information
kellertuer authored Apr 10, 2024
1 parent 0d899bf commit f546183
Show file tree
Hide file tree
Showing 15 changed files with 784 additions and 291 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.julia-version }}
arch: x64
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
- uses: julia-actions/setup-julia@v2
with:
version: 1
- name: Install JuliaFormatter and format
Expand Down
22 changes: 21 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,27 @@ All notable Changes to the Julia package `Manopt.jl` will be documented in this
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.59] - May 7, 2024
## [0.4.60] – April 10, 2024

### Added

* `RecordWhenActive` to allow records to be deactivated during runtime, symbol `:WhenActive`
* `RecordSubsolver` to record the result of a subsolver recording in the main solver, symbol `:Subsolver`
* `RecordStoppingReason` to record the reason a solver stopped
* made the `RecordFactory` more flexible and quite similar to `DebugFactory`, such that it is now also easy to specify recordings at the end of solver runs. This can especially be used to record final states of sub solvers.

### Changed

* being a bit more strict with internal tools and made the factories for record non-exported, so this is the same as for debug.

### Fixed

* The name `:Subsolver` to generate `DebugWhenActive` was misleading, it is now called `:WhenActive` – referring to “print debug only when set active, e.g. by the parent (main) solver”.
* the old version of specifying `Symbol => RecordAction` for later access was ambiguous, since
it could also mean to store the action in the dictionary under that symbol. Hence the order for access
was switched to `RecordAction => Symbol` to resolve that ambiguity.

## [0.4.59] - April 7, 2024

### Added

Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Manopt"
uuid = "0fc0a36d-df90-57f3-8f93-d78a9fc72bb5"
authors = ["Ronny Bergmann <[email protected]>"]
version = "0.4.59"
version = "0.4.60"

[deps]
ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
Expand Down
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ In Julia you can get started by just typing
using Pkg; Pkg.add("Manopt");
```

and then checkout the [Get started: optimize!](https://manoptjl.org/stable/tutorials/Optimize!/) tutorial.
and then checkout the [Get started: optimize!](https://manoptjl.org/stable/tutorials/Optimize/) tutorial.

## Related packages

Expand Down
3 changes: 2 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ tutorials_menu =
# (e) finally make docs
bib = CitationBibliography(joinpath(@__DIR__, "src", "references.bib"); style=:alpha)
links = InterLinks(
"ManifoldsBase" => ("https://juliamanifolds.github.io/ManifoldsBase.jl/stable/")
"ManifoldsBase" => ("https://juliamanifolds.github.io/ManifoldsBase.jl/stable/"),
"Manifolds" => ("https://juliamanifolds.github.io/Manifolds.jl/stable/"),
)
makedocs(;
format=Documenter.HTML(;
Expand Down
26 changes: 20 additions & 6 deletions docs/src/plans/record.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,33 @@ CurrentModule = Manopt
To record values during the iterations of a solver run, there are in general two possibilities.
On the one hand, the high-level interfaces provide a `record=` keyword, that accepts several different inputs. For more details see [How to record](../tutorials/HowToRecord.md).

For example recording the gradient from the [`GradientDescentState`](@ref) is
automatically available, as explained in the [`gradient_descent`](@ref) solver.
## [Record Actions & the solver state decorator](@id subsec-record-states)

## [Record solver states](@id subsec-record-states)
```@autodocs
Modules = [Manopt]
Pages = ["plans/record.jl"]
Order = [:type]
```

## Access functions

```@autodocs
Modules = [Manopt]
Pages = ["plans/record.jl"]
Order = [:type, :function]
Private = true
Order = [:function]
Public = true
Private = false
```

see [recording values](@ref sec-record) for details on the decorated solver.
## Internal factory functions

```@autodocs
Modules = [Manopt]
Pages = ["plans/record.jl"]
Order = [:function]
Public = false
Private = true
```

Further specific [`RecordAction`](@ref)s can be found when specific types of [`AbstractManoptSolverState`](@ref) define them on their corresponding site.

Expand Down
5 changes: 3 additions & 2 deletions src/Manopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -543,15 +543,16 @@ export DebugWarnIfLagrangeMultiplierIncreases
export DebugWarnIfGradientNormTooLarge, DebugMessages
#
# Records - and access functions
export get_record, get_record_state, get_record_action, has_record
export get_record, get_record_state, get_record_action, has_record, getindex
export RecordAction
export RecordActionFactory, RecordFactory
export RecordGroup, RecordEvery
export RecordChange, RecordCost, RecordIterate, RecordIteration
export RecordEntry, RecordEntryChange, RecordTime
export RecordGradient, RecordGradientNorm, RecordStepsize
export RecordSubsolver, RecordWhenActive, RecordStoppingReason
export RecordPrimalBaseChange,
RecordPrimalBaseIterate, RecordPrimalChange, RecordPrimalIterate
export RecordStoppingReason, RecordWhenActive, RecordSubsolver
export RecordDualBaseChange, RecordDualBaseIterate, RecordDualChange, RecordDualIterate
export RecordProximalParameter
#
Expand Down
34 changes: 16 additions & 18 deletions src/plans/debug.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@ Since usual debug is happening after the iteration, 1 is the default.
# Constructor
DebugEvery(d::DebugAction, every=1, always_update=true, activation_offset=1)
Initialise the DebugEvery.
"""
mutable struct DebugEvery <: DebugAction
debug::DebugAction
Expand Down Expand Up @@ -738,27 +736,27 @@ deactivate this debug
# Fields
* `active`: a boolean that can (de-)activated from outside to turn on/off debug
* `always_update`: whether or not to call the order debugs with iteration `-1` in active state
* `always_update`: whether or not to call the order debugs with iteration `<=0` inactive state
# Constructor
DebugWhenActive(d::DebugAction, active=true, always_update=true)
Initialise the DebugSubsolver.
"""
mutable struct DebugWhenActive <: DebugAction
debug::DebugAction
mutable struct DebugWhenActive{D<:DebugAction} <: DebugAction
debug::D
active::Bool
always_update::Bool
function DebugWhenActive(d::DebugAction, active::Bool=true, always_update::Bool=true)
return new(d, active, always_update)
function DebugWhenActive(
d::D, active::Bool=true, always_update::Bool=true
) where {D<:DebugAction}
return new{D}(d, active, always_update)
end
end
function (dwa::DebugWhenActive)(p::AbstractManoptProblem, st::AbstractManoptSolverState, i)
if dwa.active
dwa.debug(p, st, i)
elseif dwa.always_update
dwa.debug(p, st, -1)
elseif (i <= 0) && (dwa.always_update)
dwa.debug(p, st, i)
end
end
function show(io::IO, dwa::DebugWhenActive)
Expand Down Expand Up @@ -1070,15 +1068,15 @@ end
Generate a dictionary of [`DebugAction`](@ref)s.
First all `Symbol`s `String`, [`DebugAction`](@ref)s and numbers are collected,
excluding `:Stop` and `:Subsolver`.
excluding `:Stop` and `:WhenActive`.
This collected vector is added to the `:Iteration => [...]` pair.
`:Stop` is added as `:StoppingCriterion` to the `:Stop => [...]` pair.
If necessary, these pairs are created
For each `Pair` of a `Symbol` and a `Vector`, the [`DebugGroupFactory`](@ref)
is called for the `Vector` and the result is added to the debug dictonaries entry
with said symbold. This is wrapped into the [`DebugWhenActive`](@ref),
when the `:Subsolver` symbol is present
when the `:WhenActive` symbol is present
# Return value
Expand Down Expand Up @@ -1118,7 +1116,7 @@ function DebugFactory(a::Vector{<:Any})
# filter out :Iteration defaults
# filter numbers & stop & pairs (pairs handles separately, numbers at the end)
iter_entries = filter(
x -> !isa(x, Pair) && (x [:Stop, :Subsolver]) && !isa(x, Int), a
x -> !isa(x, Pair) && (x [:Stop, :WhenActive]) && !isa(x, Int), a
)
# Filter pairs
b = filter(x -> isa(x, Pair), a)
Expand Down Expand Up @@ -1148,7 +1146,7 @@ function DebugFactory(a::Vector{<:Any})
for d in b
offset = d.first === :BeforeIteration ? 0 : 1
dbg = DebugGroupFactory(d.second; activation_offset=offset)
(:Subsolver in a) && (dbg = DebugWhenActive(dbg))
(:WhenActive in a) && (dbg = DebugWhenActive(dbg))
# Add DebugEvery to all but Start and Stop
(!(d.first in [:Start, :Stop]) && (ae > 0)) && (dbg = DebugEvery(dbg, ae))
dictionary[d.first] = dbg
Expand All @@ -1172,12 +1170,12 @@ If this results in more than one [`DebugAction`](@ref) a [`DebugGroup`](@ref) of
If any integers are present, the last of these is used to wrap the group in a
[`DebugEvery`](@ref)`(k)`.
If `:SubSolver` is present, the resulting Action is wrappedn in [`DebugWhenActive`](@ref),
If `:WhenActive` is present, the resulting Action is wrappedn in [`DebugWhenActive`](@ref),
making it deactivatable by its parent solver.
"""
function DebugGroupFactory(a::Vector; activation_offset=1)
group = DebugAction[]
for d in filter(x -> !isa(x, Int) && (x [:Subsolver]), a) # filter Ints, &Sub
for d in filter(x -> !isa(x, Int) && (x [:WhenActive]), a) # filter Ints, &Active
push!(group, DebugActionFactory(d))
end
l = length(group)
Expand All @@ -1192,7 +1190,7 @@ function DebugGroupFactory(a::Vector; activation_offset=1)
if length(e) > 0
debug = DebugEvery(debug, last(e); activation_offset=activation_offset)
end
(:Subsolver in a) && (debug = (DebugWhenActive(debug)))
(:WhenActive in a) && (debug = (DebugWhenActive(debug)))
return debug
end
DebugGroupFactory(a; kwargs...) = DebugGroupFactory([a]; kwargs...)
Expand Down
Loading

2 comments on commit f546183

@kellertuer
Copy link
Member Author

Choose a reason for hiding this comment

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

@JuliaRegistrator register

Release notes:

Added

  • RecordWhenActive to allow records to be deactivated during runtime, symbol :WhenActive
  • RecordSubsolver to record the result of a subsolver recording in the main solver, symbol :Subsolver
  • RecordStoppingReason to record the reason a solver stopped
  • made the RecordFactory more flexible and quite similar to DebugFactory, such that it is now also easy to specify recordings at the end of solver runs. This can especially be used to record final states of sub solvers.

Changed

  • being a bit more strict with internal tools and made the factories for record non-exported, so this is the same as for debug.

Fixed

  • The name :Subsolver to generate DebugWhenActive was misleading, it is now called :WhenActive – referring to “print debug only when set active, e.g. by the parent (main) solver”.
  • the old version of specifying Symbol => RecordAction for later access was ambiguous, since
    it could also mean to store the action in the dictionary under that symbol. Hence the order for access
    was switched to RecordAction => Symbol to resolve that ambiguity.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/104611

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.60 -m "<description of version>" f5461838e7b6d03107522fcfe3a544aba306d222
git push origin v0.4.60

Please sign in to comment.