--- a/src/Iterate.jl Mon Dec 06 11:52:10 2021 +0200 +++ b/src/Iterate.jl Tue Dec 07 11:41:07 2021 +0200 @@ -4,56 +4,213 @@ __precompile__() +""" +`module AlgTools.iterate` + +This module implements looping and status display utilities for iterate algorithms. It includes: +- `make_iterate` and `make_iterate_log` for constructing functions for iterating +- `simple_iterate` and `simple_iterage_log` for iteration +- `default_iterate = make_iterate()` default implementation +- `logrepr` +""" module Iterate using Printf +using ..Logger +using ..FunctionalProgramming ############## # Our exports ############## -export simple_iterate +export make_iterate, + make_iterate_log, + simple_iterate, + simple_iterate_log, + default_iterate, + logrepr + +########################### +# Internal helper routines +########################### + +""" + +`logrepr(v)` -######################################################################## -# Simple itertion function, calling `step()` `params.maxiter` times and +Create the displayed presentation for log items. Override for nice (condensed) +presentation of rich log items, or define `show` if it is not defined for your type. +""" +function logrepr(v :: Any) + return "«", v, "»" +end + +function logrepr(v :: Number) + return "J=", v +end + +@inline function calculate_and_report(calc_objective, iter, maxiter) + v = calc_objective() + s = @sprintf("%d/%d J=%f%s\n", iter, params.maxiter, v, extra) + print_styled(iter, "/", maxiter, logrepr(v)...; color=:light_black) + + return v +end + +######################################################################### +# Simple iteration function, calling `step()` `params.maxiter` times and # reporting objective value every `params.verbose_iter` iterations. # The function `step` should take as its argument a function that itself # takes as its argument a function that calculates the objective value # on demand. -######################################################################## +######################################################################### + +""" +`simple_iterate(step :: Function; maxiter = 1000, verbose_iter=100)` + +Iterate the function `step` for at most `maxiter` iterations, displaying +status every `verbose_iter` iterations. The function `step` should accept one parameter, +called `verbose` below, and return `true` if the iterations are to continue, `false` +otherwise. The `verbose` parameter of `step` is itself a function that should be +called with a function that will on demand, as determined by `verbose`, calculate +the function value or other reported data. For example: -function simple_verbosity(iter, params, calc_objective) - if params.verbose_iter!=0 && mod(iter, params.verbose_iter) == 0 - v, extra₀ = calc_objective() - if isa(extra₀, AbstractString) - extra = " [$extra₀]" - else - extra = "" +```julia +simple_iterate() do verbose + # perform iterations + verbose() do + # calculate function value or other displayed data v + return + end + return true +end +``` +""" +function simple_iterate(step :: Function ; maxiter=1000, verbose_iter=100) + for iter=1:maxiter + res = step() do calc_objective + if verbose_iter!=0 && mod(iter, verbose_iter) == 0 + calculate_and_report(calc_objective, iter, params.maxiter) + end + true end - @printf("%d/%d J=%f%s\n", iter, params.maxiter, v, extra) - return true - end -end - -function simple_iterate(step :: Function, - params :: NamedTuple) - for iter=1:params.maxiter - step() do calc_objective - simple_verbosity(iter, params, calc_objective) + if !res + break end end end -function simple_iterate(step :: Function, - datachannel :: Channel{T}, - params :: NamedTuple) where T - for iter=1:params.maxiter - d = take!(datachannel) - step(d) do calc_objective - simple_verbosity(iter, params, calc_objective) +""" +`simple_iterate(step :: Function, ch :: Channel{D}; ...)` + +Version of `simple_iterate` that on each step reads a new value of type `D` from the channel `ch`. +These are passed as the second parameter of `step`. The behaviour is otherwise as with +the basic version of `simple_iterate`. +""" +function simple_iterate(step :: Function, ch :: Channel{D}; kwargs...) where D + simple_iterate(kwargs...) do calc_objective + step(calc_objective, take!(ch)) + end +end + +# constructor interface +""" +`make_iterate(; ...)` and `make_iterate(ch :: Channel{D}; ...)` + +These are constructor versions of `simple_iterate`, each returning a function +that takes as its sole parameter the `step` parameter of `simple_iterate`. +For example, + +```julia +iterate = make_iterate(verbose_iter=10, maxiter=10000) + +iterate() do verbose + # use as the example in the documentation of `simple_iterate` +end +``` +""" +make_iterate = curryflip(simple_iterate) + +# backwards-compatibility +simple_iterate(step :: Function, params :: NamedTuple) = + simple_iterate(step; params...) +simple_iterate(step :: Function, ch :: Channel{D}, params :: NamedTuple) where D = + simple_iterate(step, ch; params...) + +# Default parameterisation for use as default keyword argument in algorithm +default_iterate = make_iterate() + +######################################################################### +# Logging iteration function. +######################################################################### + +""" +`simple_iterate_log(step :: Function, log :: Log{T}; maxiter = 1000, verbose_iter=100)` + +Iterate the function `step` for at most `maxiter` iterations, logging and displaying +results as determined by `verbose_iter`. The function `step` should accept one parameter, +called `verbose` below, and return `true` if the iterations are to continue, `false` +otherwise. The `verbose` parameter of `step` is itself a function that should be +called with a function that will on demand, as determined by the `log`, calculate +the function value or other logged and displayed data. For example: + +```julia +log = Log{Float64} + +simple_iterate_log(log) do verbose + # perform iterations + verbose() do + # calculate function value or other logged data v + return v + end + return true +end +``` +""" +function simple_iterate_log(step :: Function, log :: Log{T}; maxiter=1000) where T + for iter=1:maxiter + res = step() do calc_objective + if_log!(log, iter) do + calculate_and_report(calc_objective, iter, params.maxiter) + end + true + end + if !res + break end end end +""" +`simple_iterate_log(step :: Function, log :: Log{T}, ch :: Channel{D}; ...)` + +Version of `simple_iterate_log` that on each step reads a new value of type `D` from the channel `ch`. +These are passed as the second parameter of `step`. The behaviour is otherwise as with +the basic version of `simple_iterate_log`. +""" +function simple_iterate_log(step :: Function, log :: Log{T}, ch :: Channel{D}; kwargs...) where {T, D} + simple_iterate_log(log; kwargs...) do calc_objective + step(calc_objective, take!(ch)) + end end +""" +`make_iterate_log(log :: log{T}; ...)` and `make_iterate_log(log :: Log{T}, ch :: Channel{D}; ...)` + +These are constructor versions of `simple_iterate_log`, each returning a function +that takes as its sole parameter the `step` parameter of `simple_iterate_log`. +For example, + +```julia +log = Log{Float64} +iterate = make_iterate_log(log, verbose_iter=10, maxiter=10000) + +iterate() do verbose + # proceed as in the example in the documentation of `simple_iterate` +end +``` +""" +make_iterate_log = curryflip(simple_iterate_log) + +end +