src/Iterate.jl

Wed, 22 Dec 2021 11:14:38 +0200

author
Tuomo Valkonen <tuomov@iki.fi>
date
Wed, 22 Dec 2021 11:14:38 +0200
changeset 35
d881275c6564
parent 34
22a64e826ee7
permissions
-rw-r--r--

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

mercurial