Wed, 22 Dec 2021 11:14:38 +0200
Add metaprogramming tools and fast multidimensional loops.
################################# # Tools for iterative algorithms ################################# __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 make_iterate, make_iterate_log, simple_iterate, simple_iterate_log, default_iterate, logrepr ########################### # Internal helper routines ########################### """ `logrepr(v)` 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: ```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 if !res break end end end """ `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