# HG changeset patch # User Tuomo Valkonen # Date 1638870067 -7200 # Node ID 22a64e826ee7289faeb7481bb810df7992578d56 # Parent a60d2f12ef93e9af5463cbd62ad8d794ba1dd0c2 New logger and iteration interface diff -r a60d2f12ef93 -r 22a64e826ee7 src/AlgTools.jl --- a/src/AlgTools.jl Mon Dec 06 11:52:10 2021 +0200 +++ b/src/AlgTools.jl Tue Dec 07 11:41:07 2021 +0200 @@ -20,8 +20,10 @@ """ module AlgTools +include("FunctionalProgramming.jl") include("StructTools.jl") include("LinkedLists.jl") +include("Logger.jl") include("Iterate.jl") include("VectorMath.jl") include("Util.jl") diff -r a60d2f12ef93 -r 22a64e826ee7 src/Iterate.jl --- 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 + diff -r a60d2f12ef93 -r 22a64e826ee7 src/Logger.jl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Logger.jl Tue Dec 07 11:41:07 2021 +0200 @@ -0,0 +1,96 @@ +# +# This module implements logging of intermediate computational results for +# generating convergence graphs, etc. +# + +""" +Logging routines for intermediate computational results. +Includes `Log`, `log!`, `if_log!`, and `write_log`. +""" +module Logger + +import DelimitedFiles: writedlm + +############## +# Our exports +############## + +export Log, + log!, + if_log!, + write_log + +########## +# Logging +########## + +""" +`struct Log{T}` + +A log of items of type `T` along with log configuration. +The constructor takes no arguments; create the log with `Log{T}()` for `T` your +data type, e.g., `Float64`. +""" +struct Log{T} + log :: Dict{Int, T} + + Log{T}() where T = new{T}(Dict{Int, T}()) +end + +""" +`if_log!(f :: Functdion, log :: Log{T}, i :: Int)` + +If based on the log settings, `i` is a verbose iteration, store the value of `f()` +in the log at index `i`. Typically to be used with `do`-notation: + +```julia +if_log!(log, iteration) do + # calculate value to be logged +end +``` +""" +function if_log!(f :: Function, log :: Log{T}, i :: Int) where T + if mod(i, log.verbose_iterations)==0 + log!(f, log, i) + #printstyled("$(i): $(val)\n", color=:light_black) + end +end + +""" +`log!(f :: Function, log :: Log{T}, i :: Int) ` + +Store the value `f()` in the log at index `i`. +""" +function log!(f :: Function, log :: Log{T}, i :: Int) where T + val = f() + push!(log.log, i => val) +end + +############## +# Data export +############## + +""" +`write_log(filename :: AbstractString, log :: Log{T})` + +Write a `Log{T}` as a CSV file using `DelimitedFiles`. +If T is a structural type, the field names are used as header fields. +Otherwise a single `value` field is attempted to be written. +""" +function write_log(filename :: AbstractString, log :: Log{T}) where T + k = fieldnames(T) + @assert(:iter ∉ k) + + open(filename, "w") do io + # Write header + writedlm(io, isempty(k) ? [:iter :value] : ((:iter, k...),)) + # Write values + iters = sort(collect(keys(log.log))) + for i ∈ iters + v = log.log[i] + writedlm(io, isempty(k) ? [i v] : ((i, (getfield(v, j) for j ∈ k)...),)) + end + end +end + +end # module