From 472f545e78ac26d67528e7505b2c403a027a98f4 Mon Sep 17 00:00:00 2001 From: Fergus Baker Date: Wed, 2 Oct 2024 13:29:59 +0100 Subject: [PATCH] feat: add describe function for FittingProblem This is to begin addressing some of the parameter binding issues that were being encountered. The `describe` function differs from just displaying the FittingProblem in that it will resolve and pretty print the parameter bindings. Bound parameters will no longer appear as `FREE` or `FROZEN` but rather list the model and that model's parameter symbol that they are bound to. --- src/composite-models.jl | 25 ++++++++++++-- src/fitting/problem.jl | 70 +++++++++++++++++++++++++++++++++++++--- test/io/test-printing.jl | 18 +++++------ 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/composite-models.jl b/src/composite-models.jl index 283b02ba..f8d16a4f 100644 --- a/src/composite-models.jl +++ b/src/composite-models.jl @@ -157,7 +157,7 @@ function Base.show(io::IO, @nospecialize(model::CompositeModel)) ) end -function _print_param(io, free, name, val, q0, q1, q2, q3, q4) +function _print_param(io, free, name, val, q0, q1, q2, q3, q4; binding = nothing) print(io, lpad("$name", q0), " ->") if val isa FitParam info = get_info_tuple(val) @@ -165,6 +165,17 @@ function _print_param(io, free, name, val, q0, q1, q2, q3, q4) if free print(io, " ± ", rpad(info[2], q2)) print(io, " ∈ [", lpad(info[3], q3), ", ", rpad(info[4], q4), "]") + end + + if !isnothing(binding) + print( + io, + " ", + Crayons.Crayon(foreground = :magenta), + lpad(binding, 7), + Crayons.Crayon(reset = true), + ) + elseif free print( io, Crayons.Crayon(foreground = :green), @@ -174,6 +185,7 @@ function _print_param(io, free, name, val, q0, q1, q2, q3, q4) else print( io, + " ", Crayons.Crayon(foreground = :cyan), lpad("FROZEN", 15 + q1 + q2 + q3 + q4), Crayons.Crayon(reset = true), @@ -183,7 +195,7 @@ function _print_param(io, free, name, val, q0, q1, q2, q3, q4) println(io) end -function _printinfo(io::IO, @nospecialize(model::CompositeModel)) +function _printinfo(io::IO, @nospecialize(model::CompositeModel); bindings = nothing) expr_buffer = 5 sym_buffer = 5 @@ -208,6 +220,7 @@ function _printinfo(io::IO, @nospecialize(model::CompositeModel)) ) println(io, "Model key and parameters:") + param_index = 1 for (sym, m) in destructed.model_map param_syms = destructed.parameter_symbols[sym] basename = Base.typename(typeof(m)).name @@ -224,7 +237,13 @@ function _printinfo(io::IO, @nospecialize(model::CompositeModel)) for ps in param_syms param = destructed.parameter_map[ps] free = param isa FitParam ? !isfrozen(param) : true - _print_param(io, free, ps, param, param_offset, q1, q2, q3, q4) + val, binding = if !isnothing(bindings) && !isempty(bindings) + get(bindings, param_index, param => nothing) + else + param, nothing + end + _print_param(io, free, ps, val, param_offset, q1, q2, q3, q4; binding) + param_index += 1 end end end diff --git a/src/fitting/problem.jl b/src/fitting/problem.jl index 85bd8602..e91e2f7f 100644 --- a/src/fitting/problem.jl +++ b/src/fitting/problem.jl @@ -12,12 +12,45 @@ struct FittableMultiModel{M} FittableMultiModel(model::Vararg{<:AbstractSpectralModel}) = new{typeof(model)}(model) end +function translate_bindings( + model_index::Int, + m::FittableMultiModel, + bindings::Vector{Vector{Pair{Int,Int}}}, +) + # map the parameter index to a string to display + translation = Dict{Int,Pair{paramtype(m.m[1]),String}}() + for b in bindings + # we skip the first item in the list since that is the root of the binding + root = b[1] + + params = parameter_named_tuple(m.m[first(root)]) + symbol = propertynames(params)[last(root)] + value = params[last(root)] + + for pair in @views b[2:end] + # check if this binding applies to the current model + if first(pair) == model_index + translation[last(pair)] = value => "~Model $(first(root)) $(symbol)" + end + end + end + + translation +end + function Base.show(io::IO, ::MIME"text/plain", @nospecialize(model::FittableMultiModel)) buff = IOBuffer() println(buff, "Models:") - for m in model.m + for (i, m) in enumerate(model.m) buf = IOBuffer() - print(buf, "- ") + print( + buf, + "\n", + Crayons.Crayon(foreground = :yellow), + "Model $i", + Crayons.Crayon(reset = true), + ": ", + ) _printinfo(buf, m) print(buff, indent(String(take!(buf)), 2)) end @@ -135,11 +168,38 @@ function Base.show(io::IO, ::MIME"text/plain", @nospecialize(prob::FittingProble buff, " . ", Crayons.Crayon(foreground = :green), - "Free (DOF)", + "Free", Crayons.Crayon(reset = true), - " : $(free)", + " : $(free)", ) print(io, encapsulate(String(take!(buff)))) end -export FittingProblem, FittableMultiModel, FittableMultiDataset + +""" + details(prob::FittingProblem) + +Show details about the fitting problem, including the specific model parameters that are bound together. +""" +function details(prob::FittingProblem) + buff = IOBuffer() + println(buff, "Models:") + for (i, m) in enumerate(prob.model.m) + buf = IOBuffer() + print( + buf, + "\n", + Crayons.Crayon(foreground = :yellow), + "Model $i", + Crayons.Crayon(reset = true), + ": ", + ) + + _printinfo(buf, m; bindings = translate_bindings(i, prob.model, prob.bindings)) + print(buff, indent(String(take!(buf)), 2)) + end + + print(encapsulate(String(take!(buff)))) +end + +export FittingProblem, FittableMultiModel, FittableMultiDataset, details diff --git a/test/io/test-printing.jl b/test/io/test-printing.jl index 1e1b1770..7eb9cf80 100644 --- a/test/io/test-printing.jl +++ b/test/io/test-printing.jl @@ -17,7 +17,7 @@ expected = """ ┌ DummyAdditive │ K -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m │ a -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b -> 5\e[36m FROZEN\e[0m +│ b -> 5 \e[36m FROZEN\e[0m └ """ @test string == expected @@ -32,14 +32,14 @@ expected = """┌ CompositeModel with 3 model components: │ \e[36m a1\e[0m => \e[36mDummyAdditive\e[0m │ K_1 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m │ a_1 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_1 -> 5\e[36m FROZEN\e[0m +│ b_1 -> 5 \e[36m FROZEN\e[0m │ \e[36m a2\e[0m => \e[36mDummyAdditive\e[0m │ K_2 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m │ a_2 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_2 -> 5\e[36m FROZEN\e[0m +│ b_2 -> 5 \e[36m FROZEN\e[0m │ \e[36m m1\e[0m => \e[36mDummyMultiplicative\e[0m │ a_3 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_3 -> 5\e[36m FROZEN\e[0m +│ b_3 -> 5 \e[36m FROZEN\e[0m └ """ @test string == expected @@ -55,20 +55,20 @@ expected = """┌ CompositeModel with 5 model components: │ \e[36m a1\e[0m => \e[36mDummyAdditive\e[0m │ K_1 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m │ a_1 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_1 -> 5\e[36m FROZEN\e[0m +│ b_1 -> 5 \e[36m FROZEN\e[0m │ \e[36m m1\e[0m => \e[36mDummyMultiplicative\e[0m │ a_2 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_2 -> 5\e[36m FROZEN\e[0m +│ b_2 -> 5 \e[36m FROZEN\e[0m │ \e[36m a2\e[0m => \e[36mDummyAdditive\e[0m │ K_2 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m │ a_3 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_3 -> 5\e[36m FROZEN\e[0m +│ b_3 -> 5 \e[36m FROZEN\e[0m │ \e[36m m2\e[0m => \e[36mDummyMultiplicative\e[0m │ a_4 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_4 -> 5\e[36m FROZEN\e[0m +│ b_4 -> 5 \e[36m FROZEN\e[0m │ \e[36m m3\e[0m => \e[36mDummyMultiplicative\e[0m │ a_5 -> 1 ± 0.1 ∈ [ 0, Inf ]\e[32m FREE\e[0m -│ b_5 -> 5\e[36m FROZEN\e[0m +│ b_5 -> 5 \e[36m FROZEN\e[0m └ """ @test string == expected