DrWatson.DrWatson
— ModuleThe perfect sidekick to your scientific inquiries
DrWatson.Derived
— TypeDerived(parameters::Vector{Union{String,Symbol}}, function::Function) Derived(parameter::Union{String,Symbol}, function::Function)
Wrap the name(s) of a parameter(s) and a function. After the possible parameter combinations are created, dict_list
will replace instances of Derived by the result of the function func, evaluated with the value of the parameter(s).
Examples
julia> p = Dict(:α => [1, 2],
:solver => ["SolverA","SolverB"],
:β => Derived(:α, x -> x^2),
)
Dict{Symbol, Any} with 3 entries:
:α => [1, 2]
:solver => ["SolverA", "SolverB"]
:β => Derived{Symbol}(:α, #51)
julia> dict_list(p)
4-element Vector{Dict{Symbol, Any}}:
Dict(:α => 1, :solver => "SolverA", :β => 1)
Dict(:α => 2, :solver => "SolverA", :β => 4)
Dict(:α => 1, :solver => "SolverB", :β => 1)
Dict(:α => 2, :solver => "SolverB", :β => 4)
A vector of parameter names can also be passed when the accompanying function uses multiple arguments:
julia> p2 = Dict(:α => [1, 2],
:β => [10,100],
:solver => ["SolverA","SolverB"],
:γ => Derived([:α,:β], (x,y) -> x^2 + 2y),
)
Dict{Symbol, Any} with 4 entries:
:α => [1, 2]
:γ => Derived{Symbol}([:α, :β], #7)
:solver => ["SolverA", "SolverB"]
:β => [10, 100]
julia> dict_list(p2)
8-element Vector{Dict{Symbol, Any}}:
Dict(:α => 1, :γ => 21, :solver => "SolverA", :β => 10)
Dict(:α => 2, :γ => 24, :solver => "SolverA", :β => 10)
Dict(:α => 1, :γ => 21, :solver => "SolverB", :β => 10)
Dict(:α => 2, :γ => 24, :solver => "SolverB", :β => 10)
Dict(:α => 1, :γ => 201, :solver => "SolverA", :β => 100)
Dict(:α => 2, :γ => 204, :solver => "SolverA", :β => 100)
Dict(:α => 1, :γ => 201, :solver => "SolverB", :β => 100)
Dict(:α => 2, :γ => 204, :solver => "SolverB", :β => 100)
DrWatson.access
— Methodaccess(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.allaccess
— Methodallaccess(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.allignore
— Methodallignore(c) = ()
Return all the keys c
that will be ignored in savename
.
DrWatson.checktagtype!
— Methodchecktagtype!(d::AbstractDict{K,T}) where {K<:Union{Symbol,String},T}
Check if the value type of d
allows String
and promote it to do so if not.
DrWatson.default_allowed
— Methoddefault_allowed(c) = (Real, String, Symbol, TimeType)
Return the (super-)Types that will be used as allowedtypes
in savename
.
DrWatson.default_expand
— Methoddefault_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
).
DrWatson.default_prefix
— Methoddefault_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.dict2ntuple
— Methoddict2ntuple(dict) -> ntuple
Convert a dictionary (with Symbol
or String
as key type) to a NamedTuple
.
DrWatson.dict_list
— Methoddict_list(c::AbstractDict)
Expand the dictionary c
into a vector of dictionaries. Each entry has a unique combination from the product of the Vector
values of the dictionary while the non-Vector
values are kept constant for all possibilities. The keys of the entries are the same.
Whether the values of c
are iterable or not is of no concern; the function considers as "iterable" only subtypes of Vector
.
To restrict some values in the dictionary so that they only appear in the resulting dictionaries, if a certain condition is met, the macro @onlyif
can be used on those values.
To compute some parameters on creation of dict_list
as a function of other specified parameters, use the type Derived
.
Use the function dict_list_count
to get the number of dictionaries that dict_list
will produce.
Examples
julia> c = Dict(:a => [1, 2], :b => 4);
julia> dict_list(c)
2-element Array{Dict{Symbol,Int64},1}:
Dict(:a=>1,:b=>4)
Dict(:a=>2,:b=>4)
julia> c[:model] = "linear"; c[:run] = ["bi", "tri"];
julia> dict_list(c)
4-element Array{Dict{Symbol,Any},1}:
Dict(:a=>1,:b=>4,:run=>"bi",:model=>"linear")
Dict(:a=>2,:b=>4,:run=>"bi",:model=>"linear")
Dict(:a=>1,:b=>4,:run=>"tri",:model=>"linear")
Dict(:a=>2,:b=>4,:run=>"tri",:model=>"linear")
julia> c[:e] = [[1, 2], [3, 5]];
julia> dict_list(c)
8-element Array{Dict{Symbol,Any},1}:
Dict(:a=>1,:b=>4,:run=>"bi",:e=>[1, 2],:model=>"linear")
Dict(:a=>2,:b=>4,:run=>"bi",:e=>[1, 2],:model=>"linear")
Dict(:a=>1,:b=>4,:run=>"tri",:e=>[1, 2],:model=>"linear")
Dict(:a=>2,:b=>4,:run=>"tri",:e=>[1, 2],:model=>"linear")
Dict(:a=>1,:b=>4,:run=>"bi",:e=>[3, 5],:model=>"linear")
Dict(:a=>2,:b=>4,:run=>"bi",:e=>[3, 5],:model=>"linear")
Dict(:a=>1,:b=>4,:run=>"tri",:e=>[3, 5],:model=>"linear")
Dict(:a=>2,:b=>4,:run=>"tri",:e=>[3, 5],:model=>"linear")
Example using Derived
julia> p = Dict(:α => [1, 2],
:solver => ["SolverA","SolverB"],
:β => Derived(:α, x -> x^2),
)
Dict{Symbol, Any} with 3 entries:
:α => [1, 2]
:solver => ["SolverA", "SolverB"]
:β => Derived{Symbol}(:α, #51)
julia> dict_list(p)
4-element Vector{Dict{Symbol, Any}}:
Dict(:α => 1, :solver => "SolverA", :β => 1)
Dict(:α => 2, :solver => "SolverA", :β => 4)
Dict(:α => 1, :solver => "SolverB", :β => 1)
Dict(:α => 2, :solver => "SolverB", :β => 4)
DrWatson.dict_list_count
— Methoddict_list_count(c) -> N
Return the number of dictionaries that will be created by calling dict_list(c)
.
DrWatson.esc_dict_expr_from_vars
— Methodesc_dict_expr_from_vars(vars)
Transform a Tuple
of Symbol
and assignments (a=b
) into a dictionary where each Symbol
in vars
defines a key-value pair. The value is obtained by evaluating the Symbol
in the macro calling environment.
This should only be called when producing an expression intended to be returned by a macro.
DrWatson.findproject
— Functionfindproject(dir = pwd()) -> project_path
Recursively search dir
and its parents for a valid Julia project file (anything in Base.project_names
). If it is found return its path, otherwise issue a warning and return nothing
.
The function stops searching if it hits either the home directory or the root directory.
DrWatson.get_rawtype
— Methodget_rawtype(D::DataType) = getproperty(parentmodule(D), nameof(D))
Return Concrete DataType from an AbstractDict
D
. Found online at: https://discourse.julialang.org/t/retrieve-the-type-of-abstractdict-without-parameters-from-a-concrete-dictionary-type/67567/3
DrWatson.gitdescribe
— Functiongitdescribe(gitpath = projectdir(); dirty_suffix = "-dirty") -> gitstr
Return a string gitstr
with the output of git describe
if an annotated git tag exists, otherwise the current active commit id of the Git repository present in gitpath
, which by default is the currently active project. If the repository is dirty when this function is called the string will end with dirty_suffix
.
Return nothing
if gitpath
is not a Git repository, i.e. a directory within a git repository.
The format of the git describe
output in general is
`"TAGNAME-[NUMBER_OF_COMMITS_AHEAD-]gLATEST_COMMIT_HASH[-dirty]"`
If the latest tag is v1.2.3
and there are 5 additional commits while the latest commit hash is 334a0f225d9fba86161ab4c8892d4f023688159c, the output will be v1.2.3-5-g334a0f
. Notice that git will shorten the hash if there are no ambiguous commits.
More information about the git describe
output can be found on (https://git-scm.com/docs/git-describe)
See also tag!
.
Examples
julia> gitdescribe() # a tag exists
"v1.2.3-g7364ab"
julia> gitdescribe() # a tag doesn't exist
"96df587e45b29e7a46348a3d780db1f85f41de04"
julia> gitdescribe(path_to_a_dirty_repo)
"3bf684c6a115e3dce484b7f200b66d3ced8b0832-dirty"
DrWatson.gitpatch
— Functiongitpatch(gitpath = projectdir())
Generates a patch describing the changes of a dirty repository compared to its last commit; i.e. what git diff HEAD
produces. The gitpath
needs to point to a directory within a git repository, otherwise nothing
is returned.
Be aware that gitpatch
needs a working installation of Git, that can be found in the current PATH.
DrWatson.initialize_project
— Functioninitialize_project(path [, name]; kwargs...)
Initialize a scientific project expected by DrWatson
in path
(directory representing an empty folder). If name
is not given, it is assumed to be the folder's name.
The new project remains activated for you to immediately add packages.
Keywords
readme = true
: adds a README.md file.authors = nothing
: if a string or container of strings, adds the authors in the Project.toml file and README.md.force = false
: If thepath
is not empty then throw an error. If howeverforce
istrue
then recursively delete everything in the path and create the project.git = true
: Make the project a Git repository.add_test = true
: Add some additional files for testing the project. This is done automatically during continuous integration (if hosted on GitHub), or manually by running the contents of thetest/runtests.jl
file.add_docs = false
: Add some additional files for generating documentation for the project, which can be generated locally by runningdocs/make.jl
but is also generated and hosted during continuous integration using Documenter.jl (if hosted on GitHub). If this option is enabled,Documenter
also becomes a dependency of the project.To host the docs online, set the keyword
github_name
with the name of the GitHub account you plan to upload at, and then manually enable thegh-pages
deployment by going to settings/pages of the GitHub repo, and choosing as "Source" thegh-pages
branch.Typically, a full documentation is not necessary for most projects, because README.md can serve as the documentation, hence this feature is
false
by default.template = DrWatson.DEFAULT_TEMPLATE
: A template containing the folder structure of the project. It should be a vector containing strings (folders) or pairs ofString => Vector{String}
, containg a folder and subfolders (this can be nested further). Example:DEFAULT_TEMPLATE = [ "_research", "src", "scripts", "data", "plots", "notebooks", "papers", "data" => ["sims", "exp_raw", "exp_pro"], ]
Obviously, the default derivative functions of
projectdir
, such asdatadir
, have been written with the default template in mind.placeholder = false
: Add "hidden" placeholder files in each default folder to ensure that project folder structure is maintained when the directory is cloned (because empty folders are not pushed to a remote). Only used whengit = true
.folders_to_gitignore = ["data", "videos","plots","notebooks","_research"]
: Folders to include in the created .gitignore
DrWatson.is_standard_julia_project
— Methodis_standard_julia_project()
Return true
if the standard Julia project is active.
DrWatson.isdirty
— Functionisdirty(gitpath = projectdir()) -> Bool
Return true
if gitpath
is the path to a dirty Git repository, false
otherwise.
Note that unlike tag!
, isdirty
can error (for example, if the path passed to it doesn't exist, or isn't a Git repository). The purpose of isdirty
is to be used as a check before running simulations, for users that do not wish to tag data while having a dirty git repo.
DrWatson.istaggable
— Methodistaggable(file::AbstractStrig) → bool
Return true
if the file save format (file ending) is "taggable", i.e. allows adding additional data fields as strings. Currently endings that can do this are:
("bson", "jld", "jld2")
DrWatson.istaggable
— Methodistaggable(x) = x isa AbstractDict
For non-string input the function just checks if input is dictionary.
DrWatson.keyname
— Methodkeyname(d::AbstractDict{K,T}, key) where {K<:Union{Symbol,String},T}
Check the key type of d
and convert key
to the appropriate type.
DrWatson.lookup_candidate
— Methodlookup_candidate(original_dict, d, name)
If name
is a key name from original_dict
either return it's value from d
or throw a KeyDeletedFromDictError
error if it's not in d
.
DrWatson.ntuple2dict
— Methodntuple2dict([type = Dict,] nt) -> dict
Convert a NamedTuple
to a dictionary.
DrWatson.parse_from_savename_value
— Methodparse_from_savename_value(types,str)
Try parsing str
with the types given in types
. The first working parse is returned. Fallback is String
ie. str
is returned.
DrWatson.parse_savename
— Methodparse_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
.
DrWatson.produce_derived_parameters
— Methodproducecomputedparameter(dicts)
Receive an array of parameter dictionaries, and for each one, evaluate the computed parameters after the possible combination of parameters has been created.
DrWatson.produce_or_load
— Functionproduce_or_load(f::Function, config, path = ""; kwargs...) -> data, file
The goal of produce_or_load
is to avoid running some data-producing code that has already been run with a given configuration container config
. If the output of some function f(config)
exists on disk, produce_or_load
will load it and return it, and if not, it will produce it, save it, and then return it.
Here is how it works:
- The output data are saved in a file named
name = filename(config)
. I.e., the output file's name is created from the configuration containerconfig
. By default, this isname =
savename
(config)
, but can be configured differently, using e.g.hash
, see keywordfilename
below. See alsoproduce_or_load
with hash codes for an example whereconfig
would be hard to put intoname
withsavename
, andhash
is used instead. - Now, let
file = joinpath(path, name)
. - If
file
exists, load it and return the containeddata
, along with the global path that it is saved at (file
). - If the file does not exist then call
data = f(config)
, withf
your function that produces your data from the configuration container. - Then save the
data
asfile
and then returndata, file
.
The function f
should return a string-keyed dictionary if the data are saved in the default format of JLD2.jl., the macro @strdict
can help with that.
You can use a do-block instead of defining a function to pass in. For example,
produce_or_load(config, path) do config
# code using `config` runs here
# and then returns a dictionary to be saved
end
Keywords
Name deciding
filename::Union{Function, String} = savename
: Configures thename
of the file to produce or load given the configuration container. It may be a one-argument function ofconfig
,savename
by default, so thatname = filename(config)
. Useful alternative tosavename
ishash
. The keywordfilename
could also be aString
directly, possibly extracted fromconfig
before callingproduce_or_load
, in which casename = filename
.suffix = "jld2", prefix = default_prefix(config)
: If not empty, added toname
asname = prefix*'_'*name*'.'*suffix
(i.e., like insavename
).
Saving
tag::Bool = DrWatson.readenv("DRWATSON_TAG", istaggable(suffix))
: Save the file usingtagsave
iftrue
(which is the default).gitpath, storepatch
: Given totagsave
iftag
istrue
.force = false
: Iftrue
then don't check iffile
exists and produce it and save it anyway.loadfile = true
: Iffalse
, this function does not actually load the file, but only checks if it exists. The return value in this case is alwaysnothing, file
, regardless of whether the file exists or not. If it doesn't exist it is still produced and saved.verbose = true
: print info about the process, if the file doesn't exist.wsave_kwargs = (;)
: Keywords to pass towsave
(e.g. to enable compression). Defaults to an empty named tuple.wload_kwargs = (;)
: Keywords to pass towload
(e.g. to pass a typemap). Defaults to an empty named tuple.
DrWatson.projectdir
— Methodprojectdir()
Return the directory of the currently active project.
projectdir(args...) = joinpath(projectdir(), args...)
Join the path of the currently active project with args
(typically other subfolders).
DrWatson.projectname
— Methodprojectname()
Return the name of the currently active project.
DrWatson.quickactivate
— Functionquickactivate(path [, name::String])
Activate the project found by recursively searching the path
and its parents for a valid Julia project file via the findproject
function. Optionally check if name
is the same as the activated project's name. If it is not, throw an error. See also @quickactivate
. Do nothing if the project found is already active, or if no project file is found.
Example:
using DrWatson
quickactivate("path/to/project", "Best project in the WOLRLD")
Notice that this function is first activating the project and then checking if it matches the name
.
Note that to access quickactivate
you need to be using DrWatson
. For this to be possible DrWatson
must be already added in the existing global environment. If you use quickactivate
and share your project, do note to your co-workers that they need to add DrWatson
globally (the default README.md created by initialize_project
says this automatically).
In addition, in your scripts write:
using DrWatson # YES
quickactivate(@__DIR__)
using Package1, Package2
# do stuff
instead of the erroneous:
using DrWatson, Package1, Package2 # NO!
quickactivate(@__DIR__)
# do stuff
This ensures that the packages you use will all have the versions dictated by your activated project (besides DrWatson
, since this is impossible to do using quickactivate
).
DrWatson.read_stdout_stderr
— Methodread_stdout_stderr(cmd::Cmd)
Run cmd
synchronously and capture stdout, stdin and a possible error exception. Return a NamedTuple
with the fields exception
, out
and err
.
DrWatson.readenv
— Methodreadenv(var, default::T)
Try to read the environment variable var
and parse it as type T
. If that fails, return default
.
DrWatson.safesave
— Methodsafesave(filename, data...; kwargs...)
Safely save data
in filename
by ensuring that no existing files are overwritten. Do this by renaming already existing data with a backup-number ending like #1, #2, ...
. For example if filename = test.jld2
, the first time you safesave
it, the file is saved normally. The second time the existing save is renamed to test_#1.jld2
and a new file test.jld2
is then saved.
If a backup file already exists then its backup-number is incremented (e.g. going from #2
to #3
). For example safesaving test.jld2
a third time will rename the old test_#1.jld2
to test_#2.jld2
, rename the old test.jld2
to test_#1.jld2
and then save a new test.jld2
with the latest data
.
Any additional keyword arguments are passed through to wsave (to e.g. enable compression).
See also tagsave
.
DrWatson.savename
— Methodsavename([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"
DrWatson.scripttag!
— Methodscripttag!(d::AbstractDict{K,T}, source::LineNumberNode; gitpath = projectdir(), force = false) where {K<:Union{Symbol,String},T}
Include a script
field in d
, containing the source file and line number in source
. Do nothing if the field is already present unless force = true
. Uses gitpath
to make the source file path relative.
DrWatson.struct2dict
— Methodstruct2dict([type = Dict,] s) -> d
Convert a Julia composite type s
to a dictionary d
with key type Symbol
that maps each field of s
to its value. Simply passing s
will return a regular dictionary. This can be useful in e.g. saving:
tagsave(savename(s), struct2dict(s))
DrWatson.struct2ntuple
— Methodstruct2ntuple(s) -> n
Convert a Julia composite type s
to a NamedTuple n
.
DrWatson.tag!
— Methodtag!(d::AbstractDict; kwargs...) -> d
Tag d
by adding an extra field gitcommit
which will have as value the gitdescribe
of the repository at gitpath
(by default the project's gitpath). Do nothing if a key gitcommit
already exists (unless force=true
then replace with the new value) or if the Git repository is not found. If the git repository is dirty, i.e. there are un-commited changes, and storepatch
is true, then the output of git diff HEAD
is stored in the field gitpatch
. Note that patches for binary files are not stored. You can use isdirty
to check if a repo is dirty. If the commit message
is set to true
, then the dictionary d
will include an additional field "gitmessage"
and will contain the git message associated with the commit.
Notice that the key-type of the dictionary must be String
or Symbol
. If String
is a subtype of the value type of the dictionary, this operation is in-place. Otherwise a new dictionary is created and returned.
To restore a repository to the state of a particular model-run do:
- checkout the relevant commit with
git checkout xyz
where xyz is the value stored - apply the patch
git apply patch
, where the string stored in thegitpatch
field needs to be written to the filepatch
.
Keywords
gitpath = projectdir()
force = false
storepatch = DrWatson.readenv("DRWATSON_STOREPATCH", false)
: Whether to collect and store the output ofgitpatch
as well.
Examples
julia> d = Dict(:x => 3, :y => 4)
Dict{Symbol,Int64} with 2 entries:
:y => 4
:x => 3
julia> tag!(d; commit_message=true)
Dict{Symbol,Any} with 3 entries:
:y => 4
:gitmessage => "File set up by DrWatson"
:gitcommit => "96df587e45b29e7a46348a3d780db1f85f41de04"
:x => 3
DrWatson.tagsave
— Methodtagsave(file::String, d::AbstractDict; kwargs...)
First tag!
dictionary d
and then save d
in file
.
"Tagging" means that when saving the dictionary, an extra field :gitcommit
is added to establish reproducibility of results using Git. If the Git repository is dirty and storepatch=true
, one more field :gitpatch
is added that stores the difference string. If a dictionary already contains a key :gitcommit
, it is not overwritten, unless force=true
. For more details, see tag!
.
Keywords gitpath, storepatch, force,
are propagated to tag!
. Any additional keyword arguments are propagated to wsave
, to e.g. enable compression.
The keyword safe = DrWatson.readenv("DRWATSON_SAFESAVE", false)
decides whether to save the file using safesave
.
DrWatson.tmpsave
— Functiontmpsave(dicts::Vector{Dict} [, tmp]; kwargs...) -> r
Save each entry in dicts
into a unique temporary file in the directory tmp
. Then return the list of file names (relative to tmp
) that were used for saving each dictionary. Each dictionary can then be loaded back by calling
wload(nth_tmpfilename, "params")
tmp
defaults to projectdir("_research", "tmp")
.
See also dict_list
.
Keywords
l = 8
: number of characters in the random string.prefix = ""
: prefix each temporary name will have.suffix = "jld2"
: ending of the temporary names (no need for the dot).kwargs...
: Any additional keywords are passed through to wsave (e.g. compression).
DrWatson.tostringdict
— Methodtostringdict(d)
Change a dictionary with key type Symbol
to have key type String
.
DrWatson.tosymboldict
— Methodtosymboldict(d)
Change a dictionary with key type String
to have key type Symbol
.
DrWatson.unexpand_restricted
— Methodunexpand_restricted(c)
Return a dict where nested @@onlyif
vectors are removed. This is necessary, because @onlyif
automatically broadcasts vector arguments. In a case like this:
:b => [@onlyif(:a==10,[10,11]), [12,13]]
Broadcasting is obviously not wanted as :b
should retain it's type of Vector{Int}
.
DrWatson.valtostring
— Methodvaltostring(val)
Convert val
to a string with the smallest possible representation of val
that allows recovering val
from valtostring(val)
.
DrWatson.wload
— MethodCurrently equivalent with FileIO.load
.
DrWatson.wsave
— Methodwsave(filename, data...; kwargs...)
Save data
at filename
by first creating the appropriate paths. Default fallback is FileIO.save
. Extend this for your types by extending DrWatson._wsave(filename, data::YourType, args...; kwargs...)
.
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.@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.@onlyif
— Macro@onlyif(ex, value)
Tag value
to only appear in a dictionary created with dict_list
if the Julia expression ex
(see below) is evaluated as true. If value
is a subtype of Vector
, @onlyif
is applied to each entry. Since @onlyif
is applied to a value and not to a dictionary key, it is possible to restrict only some of the values of a vector. This means that based on ex
the number of options for a particular key varies.
Within ex
it is possible to extract values of the dictionary passed to dict_list
by a shorthand notation where only the key must be provided. For example ex = :(:N == 1)
is tranformed in the call dict_list(d)
to an expression analogous to :(d[:N] == 1)
by using the function lookup_candidate
. This is supported for Symbol
and String
keys.
Examples
julia> d = Dict(:a => [1, 2], :b => 4, :c => @onlyif(:a == 1, [10, 11]));
julia> dict_list(d) # only in case `:a` is `1` the dictionary will get key `:c`
3-element Array{Dict{Symbol,Int64},1}:
Dict(:a => 1,:b => 4,:c => 10)
Dict(:a => 1,:b => 4,:c => 11)
Dict(:a => 2,:b => 4)
julia> d = Dict(:a => [1, 2], :b => 4, :c => [10, @onlyif(:a == 1, 11)]);
julia> dict_list(d) # only in case `:a` is `1` the dictionary will get extra value `11` for key `:c`
3-element Array{Dict{Symbol,Int64},1}:
Dict(:a => 1,:b => 4,:c => 10)
Dict(:a => 1,:b => 4,:c => 11)
Dict(:a => 2,:b => 4,:c => 10)
See the Defining parameter sets with restrictions section for more examples.
DrWatson.@produce_or_load
— Macro@produce_or_load(f, config, path; kwargs...)
Same as produce_or_load
but one more field :script
is added that records the local path of the script and line number that called @produce_or_load
, see @tag!
.
Notice that path
here is mandatory in contrast to produce_or_load
.
DrWatson.@quickactivate
— Macro@quickactivate
Equivalent with quickactivate(@__DIR__)
.
@quickactivate name::String
Equivalent with quickactivate(@__DIR__, name)
.
Notice that since @quickactivate
is a macro, standard caveats apply when using Distributed
computing. Specifically, you need to import DrWatson
and use @quickactivate
in different begin
blocks as follows:
using Distributed
addprocs(8)
@everywhere using DrWatson
@everywhere begin
@quickactivate "TestEnv"
using Distributions, ...
# remaining imports
end
Pluto.jl understands the @quickactivate
macro and will switch to using the standard Julia package manager once it encounters it (or quickactivate
). But, because @quickactivate
is a macro it needs to be executed in a new cell, after using DrWatson
. I.e., you need to split
begin
using DrWatson
@quickactivate "Whatever"
end
to two different cells:
using DrWatson
@quickcativate "Whatever"
DrWatson.@quickactivate
— Macro@quickactivate ProjectName::Symbol
If given a Symbol
then first quickactivate(@__DIR__, string(ProjectName))
, and then do using ProjectName
, as if the symbol was representing a module name.
This ties with Making your project a usable module functionality, see the docs for an example.
DrWatson.@savename
— MacroDrWatson.@strdict
— Macro@strdict vars...
Same as @dict
but the key type is String
.
DrWatson.@tag!
— Macro@tag!(d, gitpath = projectdir(), storepatch = true, force = false) -> d
Do the same as tag!
but also add another field script
that has the path of the script that called @tag!
, relative with respect to gitpath
. The saved string ends with #line_number
, which indicates the line number within the script that @tag!
was called at.
Examples
julia> d = Dict(:x => 3)Dict{Symbol,Int64} with 1 entry:
:x => 3
julia> @tag!(d) # running from a script or inline evaluation of Juno
Dict{Symbol,Any} with 3 entries:
:gitcommit => "618b72bc0936404ab6a4dd8d15385868b8299d68"
:script => "test\stools_tests.jl#10"
:x => 3
DrWatson.@tagsave
— Macro