Naming Simulations
Here we overview functionality that helps you quickly produce containers of parameters and name them using a consistent and intuitive naming scheme.
Naming Schemes
A robust naming scheme allows you to create quick names for simulations, create lists of simulations, check existing simulations, etc. More importantly it allows you to easily create simulation-based names consistently and deterministically.
This is what the function savename
does. Of course, you don't have to use it only for using names to save files. You could use it for anything that fits you (like e.g. adding identifiers to tabular data). savename
is also surprisingly useful for creating titles of figures, e.g. savename(c; connector = ", ")
.
DrWatson.savename
— Functionsavename([prefix,], c [, suffix]; kwargs...)
Create a shorthand name, commonly used for saving a file or as a figure title, based on the parameters in the container c
(Dict
, NamedTuple
or any other Julia composite type). If provided use the prefix
and end the name with .suffix
(i.e. you don't have to include the .
in your suffix
).
The function chains keys and values into a string of the form:
key1=val1_key2=val2_key3=val3
while the keys are sorted alphabetically by default. If you provide the prefix/suffix the function will do:
prefix_key1=val1_key2=val2_key3=val3.suffix
assuming you chose the default connector
, see below. Notice that prefix
should not contain path separators to avoid compatibility issues on different operating systems. For constructing paths, use the *dir()
methods or joinpath
with savename()
as the last parameter. See default_prefix
for more.
savename
can be conveniently combined with @dict
or @ntuple
. See also parse_savename
and @savename
.
Standard keywords
sort = true
: Indicate whether the pairs are sorted alphabetically by keys. If not, they are sorted by the order ofaccesses
. WARNING: the defaultaccesses
is not deterministic forDict
inputs.digits = nothing, sigdigits = 3
: Floating point values are rounded using theround
function with these keywords.connector = "_"
: string used to connect the various entries.
Customization keywords
allowedtypes = default_allowed(c)
: Only values of type subtyping anything inallowedtypes
are used in the name. By default this is(Real, String, Symbol, TimeType)
.accesses = allaccess(c)
: specify which specific keys you want to use with the keywordaccesses
. By default this is all possible keysc
can be accessed with, seeallaccess
.ignores = allignore(c)
: You can also specify keys that you want to ignore with the keywordignores
. By default this is an empty tuple, seeallignore
. (keys inignore
are ignored even if they are inaccesses
)val_to_string = nothing
: If notnothing
, this is a function that converts any given value to a string representation, and allows for custom formatting. If given,digits, sigidigits
are ignored.expand::Vector{String} = default_expand(c)
: keys that will be expanded to thesavename
of their contents, to allow for nested containers. By default is empty. Notice that the type of the container must also be allowed inallowedtypes
forexpand
to take effect! Thesavename
of the nested arguments is always called with its default arguments (so customization here is possible only by rolling your own container type). Containers leading to emptysavename
are skipped.equals = "="
: Connector between name and value. Can be useful to modify for adding space" = "
.
Examples
d = (a = 0.153456453, b = 5, mode = "double")
savename(d; digits = 4) == "a=0.1535_b=5_mode=double"
savename("n", d) == "n_a=0.153_b=5_mode=double"
savename(d, "n") == "a=0.153_b=5_mode=double.n"
savename("n", d, "n"; connector = "-") == "n-a=0.153-b=5-mode=double.n"
savename(d, allowedtypes = (String,)) == "mode=double"
savename(d, connector=" | ", equals=" = ") == "a = 0.153 | b = 5 | mode = double"
rick = (never = "gonna", give = "you", up = "!");
savename(rick) == "give=you_never=gonna_up=!" # keys are sorted!
savename(rick; ignores = ["up"]) == "give=you_never=gonna"
Notice that this naming scheme integrates perfectly with Parameters.jl.
Convenience functions
Convenience functions are provided to shorten common function calls and easily create named tuples, dictionaries as well as switch between them:
DrWatson.@dict
— Macro@dict vars...
Create a dictionary out of the given variables that has as keys the variable names and as values their values.
Notice: @dict a b
is the correct way to call the macro. @dict a, b
is incorrect. If you want to use commas you have to do @dict(a, b)
.
Examples
julia> ω = 5; χ = "test"; ζ = π/3;
julia> @dict ω y=χ ζ
Dict{Symbol,Any} with 3 entries:
:ω => 5
:y => "test"
:ζ => 1.0472
DrWatson.@strdict
— Macro@strdict vars...
Same as @dict
but the key type is String
.
DrWatson.@ntuple
— Macro@ntuple vars...
Create a NamedTuple
out of the given variables that has as keys the variable names and as values their values.
Examples
julia> ω = 5; χ = "test"; ζ = 3.14;
julia> @ntuple ω χ π=ζ
(ω = 5, χ = "test", π = 3.14)
DrWatson.@savename
— Macro@savename vars...
Convenient combination of chaining a call to @dict
on vars
and savename
.
Examples
julia> a = 0.153456453; b = 5.0; mode = "double"
julia> @savename a b mode
"a=0.153_b=5_mode=double"
DrWatson.ntuple2dict
— Functionntuple2dict([type = Dict,] nt) -> dict
Convert a NamedTuple
to a dictionary.
DrWatson.dict2ntuple
— Functiondict2ntuple(dict) -> ntuple
Convert a dictionary (with Symbol
or String
as key type) to a NamedTuple
.
DrWatson.tostringdict
— Functiontostringdict(d)
Change a dictionary with key type Symbol
to have key type String
.
DrWatson.tosymboldict
— Functiontosymboldict(d)
Change a dictionary with key type String
to have key type Symbol
.
DrWatson also re-exports @pack!, @unpack
tools from UnPack.jl, because they play very well with @dict
and similar functions. Be aware of the syntactic ,
difference: d = @dict a b c
versus @unpack a, b, c = d
.
Customizing savename
You can customize savename
for your own Types. For example you could make it so that it only uses some specific keys instead of all of them, only specific types, or you could make it access data in a different way (maybe even loading files!). You can even make it have a custom prefix
!
To do that you may extend any of the following functions:
DrWatson.allaccess
— Functionallaccess(c)
Return all the keys c
can be accessed using access
. For dictionaries/named tuples this is keys(c)
, for everything else it is fieldnames(typeof(c))
.
DrWatson.access
— Functionaccess(c, key)
Access c
with given key. For AbstractDict
this is getindex
, for anything else it is getproperty
.
access(c, keys...)
When given multiple keys, access
is called recursively, i.e. access(c, key1, key2) = access(access(c, key1), key2)
and so on. For example, if c, c.k1
are NamedTuple
s then access(c, k1, k2) == c.k1.k2
.
Please only extend the single key method when customizing access
for your own Types.
DrWatson.allignore
— Functionallignore(c) = ()
Return all the keys c
that will be ignored in savename
.
DrWatson.default_allowed
— Functiondefault_allowed(c) = (Real, String, Symbol, TimeType)
Return the (super-)Types that will be used as allowedtypes
in savename
.
DrWatson.default_prefix
— Functiondefault_prefix(c) = ""
Return the prefix
that will be used by default in savename
.
Notice that if default_prefix
is defined for c
but a prefix is also given to savename
then the two values are merged via joinpath
for convenience (if they are not the same of course).
E.g. defining default_prefix(c::MyType) = "lala"
and calling
savename(datadir(), mytype)
will in fact return a string that looks like
"path/to/data/lala_p1=..."
This allows savename
to work well with produce_or_load
.
DrWatson.default_expand
— Functiondefault_expand(c) = String[]
Keys that should be expanded in their savename
within savename
. Must be Vector{String}
(as all keys are first translated into strings inside savename
).
See Real World Examples for an example of customizing savename
. Specifically, have a look at savename
and nested containers for a way to
Reverse-engineering savename
DrWatson.parse_savename
— Functionparse_savename(filename::AbstractString; kwargs...)
Try to convert a shorthand name produced with savename
into a dictionary containing the parameters and their values, a prefix and suffix string. Return prefix, parameters, suffix
.
Parsing the key-value parts of filename
is performed under the assumption that the value is delimited by =
and the closest connector
. This allows the user to have connector
(eg. _
) in a key name (variable name) but not in the value part.
Keywords
connector = "_"
: string used to connect the various entries.parsetypes = (Int, Float64)
: tuple used to define the types which should be tried when parsing the values given infilename
. Fallback isString
.