|
1 # |
|
2 # This module implements logging of intermediate computational results for |
|
3 # generating convergence graphs, etc. |
|
4 # |
|
5 |
|
6 """ |
|
7 Logging routines for intermediate computational results. |
|
8 Includes `Log`, `log!`, `if_log!`, and `write_log`. |
|
9 """ |
|
10 module Logger |
|
11 |
|
12 import DelimitedFiles: writedlm |
|
13 |
|
14 ############## |
|
15 # Our exports |
|
16 ############## |
|
17 |
|
18 export Log, |
|
19 log!, |
|
20 if_log!, |
|
21 write_log |
|
22 |
|
23 ########## |
|
24 # Logging |
|
25 ########## |
|
26 |
|
27 """ |
|
28 `struct Log{T}` |
|
29 |
|
30 A log of items of type `T` along with log configuration. |
|
31 The constructor takes no arguments; create the log with `Log{T}()` for `T` your |
|
32 data type, e.g., `Float64`. |
|
33 """ |
|
34 struct Log{T} |
|
35 log :: Dict{Int, T} |
|
36 |
|
37 Log{T}() where T = new{T}(Dict{Int, T}()) |
|
38 end |
|
39 |
|
40 """ |
|
41 `if_log!(f :: Functdion, log :: Log{T}, i :: Int)` |
|
42 |
|
43 If based on the log settings, `i` is a verbose iteration, store the value of `f()` |
|
44 in the log at index `i`. Typically to be used with `do`-notation: |
|
45 |
|
46 ```julia |
|
47 if_log!(log, iteration) do |
|
48 # calculate value to be logged |
|
49 end |
|
50 ``` |
|
51 """ |
|
52 function if_log!(f :: Function, log :: Log{T}, i :: Int) where T |
|
53 if mod(i, log.verbose_iterations)==0 |
|
54 log!(f, log, i) |
|
55 #printstyled("$(i): $(val)\n", color=:light_black) |
|
56 end |
|
57 end |
|
58 |
|
59 """ |
|
60 `log!(f :: Function, log :: Log{T}, i :: Int) ` |
|
61 |
|
62 Store the value `f()` in the log at index `i`. |
|
63 """ |
|
64 function log!(f :: Function, log :: Log{T}, i :: Int) where T |
|
65 val = f() |
|
66 push!(log.log, i => val) |
|
67 end |
|
68 |
|
69 ############## |
|
70 # Data export |
|
71 ############## |
|
72 |
|
73 """ |
|
74 `write_log(filename :: AbstractString, log :: Log{T})` |
|
75 |
|
76 Write a `Log{T}` as a CSV file using `DelimitedFiles`. |
|
77 If T is a structural type, the field names are used as header fields. |
|
78 Otherwise a single `value` field is attempted to be written. |
|
79 """ |
|
80 function write_log(filename :: AbstractString, log :: Log{T}) where T |
|
81 k = fieldnames(T) |
|
82 @assert(:iter ∉ k) |
|
83 |
|
84 open(filename, "w") do io |
|
85 # Write header |
|
86 writedlm(io, isempty(k) ? [:iter :value] : ((:iter, k...),)) |
|
87 # Write values |
|
88 iters = sort(collect(keys(log.log))) |
|
89 for i ∈ iters |
|
90 v = log.log[i] |
|
91 writedlm(io, isempty(k) ? [i v] : ((i, (getfield(v, j) for j ∈ k)...),)) |
|
92 end |
|
93 end |
|
94 end |
|
95 |
|
96 end # module |