src/Iterate.jl

changeset 34
22a64e826ee7
parent 22
d5e10d963303
--- 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
+

mercurial