DrWatson.jl

repository·main·Indexed 21 days ago

https://github.com/juliadynamics/drwatson.jl

A scientific project assistant for Julia designed to help researchers manage simulations, data, scripts, and parameters to ensure project reproducibility. It provides tools for universal project structure, deterministic simulation naming via `savename`, robust project activation with `@quickactivate`, and safe data saving with `@tagsave` including Git commit ID tagging.

Tokens
8.7K
Snippets
38
Records
45
Agent score
74%

What's inside DrWatson.jl

  1. Overview of DrWatson functionality

    main

    DrWatson is a scientific project assistant designed to increase consistency, reproducibility, and ease of navigation in scientific projects. Its core functionalities include:

    • Project Setup: Provides a universal project structure and functions for robust navigation regardless of project location.
    • Naming Simulations: Implements a robust and deterministic scheme for naming and handling simulation containers.
    • Saving Tools: Tools for safely saving and loading data, including automatic Git commit ID tagging and safety checks for dirty repositories.
    • Running & Listing Simulations: Tools for producing tables of existing simulations, adding new results to tables, and preparing batch parameter containers.

    DrWatson is modular; you can use individual components (islands) without adopting the entire workflow. It is not a data management system, a Julia package creator (like PkgTemplates.jl), or a package development tool.

  2. Overview of DrWatson.jl

    main

    DrWatson is a scientific project assistant designed to help manage scientific projects and ensure reproducibility. It provides tools to handle:

    • Simulation parameters and existing simulations
    • File storage locations
    • Experimental data
    • Scripts and project source code
    • Establishing project reproducibility
  3. How DrWatson project navigation works

    main

    DrWatson uses a relative-path philosophy to ensure projects are portable across different machines. Once a project is activated, functions like projectdir, datadir, and plotsdir return the absolute path to the corresponding directory within the active project, regardless of your current working directory or where the script is located.

    To make this seamless, add the @quickactivate "ProjectName" macro to the top of your scripts. This automatically activates the correct Julia project and enables all DrWatson path functions.

    @quickactivate "ProjectName"
  4. How DrWatson handles data provenance and parameter management

    main

    DrWatson provides a lightweight, non-invasive approach to data provenance and parameter tracking. Instead of requiring a separate server or complex system-call tracing, it focuses on two primary methods:

    1. Version Control Logging: It logs version control information (like commit IDs) directly into Julia dictionaries.
    2. Parameter-Result Coupling: It uses the savename function to store parameter configurations within the file paths themselves. This creates a file-format-independent method for keeping simulation parameters physically associated with their corresponding result files.

    This design is intended to be language-agnostic in practice because Julia's interop capabilities (via PyCall and RCall) allow DrWatson to work alongside Python and R scripts, making it suitable for heterogeneous pipelines.

  5. Organize code using `src` vs `scripts`

    main

    DrWatson suggests a distinction between the src and scripts directories based on the side effects of the files:

    • scripts: Use this for files that produce output. If running include("file.jl") results in data files, plots, or console output, it belongs in scripts.
    • src: Use this for reusable functionality. Files in src should define functions or types and should not produce any output. These are intended to be used across multiple files or pipelines.
  6. Automate input-to-output pipelines with savename and produce_or_load

    main

    You can automate your entire project pipeline and eliminate duplicate code by combining savename and produce_or_load. This pattern ensures that simulations or experiments are only run if their specific output does not already exist, preventing accidental re-runs.

    Workflow Steps:

    1. Define a Configuration Struct: Create a custom struct that represents all input parameters for an experiment.
    2. Extend savename: Implement a custom savename method for your configuration struct so that the filename uniquely identifies the specific experiment parameters.
    3. Define a Main Function: Write a function that accepts an instance of your configuration struct and returns the results as a Dict (where keys are String).
    4. Use produce_or_load: Call produce_or_load with your main function and the configuration instance. This will either load the existing result from disk or run the function and save the output automatically.

    Example Pattern:

    # 1. Define config
    config = BasinConfig(; system, p, basin_kwargs, grid)
    
    # 2. & 3. & 4. Run or load via produce_or_load
    # (Assuming produce_basins is defined to use produce_or_load internally)
    basins, attractors = produce_basins(config)
    config = BasinConfig(; system, p, basin_kwargs, grid)
    basins, attractors = produce_basins(config)
  7. Customize savename for custom structs

    main

    You can customize how savename generates filenames for your custom types by extending several DrWatson internal functions:

    1. DrWatson.default_prefix(obj): Define the starting part of the filename (e.g., using a date or a specific field).
    2. DrWatson.default_allowed(obj): Specify which types should be allowed to be extended for this object.
    3. DrWatson.allaccess(obj): Define which specific fields of the struct should be included in the filename.
    4. DrWatson.default_expand(obj): (For nested containers) Define which sub-fields should be expanded/traversed.

    Note: savename uses Base.string(obj) to convert values to strings for the filename; you can extend Base.string for your types to control this output.

    # Example customization
    DrWatson.default_prefix(e::Experiment) = "Experiment_"*string(e.date)
    DrWatson.default_allowed(::Experiment) = (Real, String, Species)
    DrWatson.allaccess(::Experiment) = (:n, :c, :x, :species)
    Base.string(::Mouse) = "mouse"
    
    savename(e1) # Generates filename based on these rules
  8. Use @quickactivate for robust local directory management

    main

    To ensure your scripts work regardless of where they are located within a project or if the entire project folder is moved, use @quickactivate at the start of every file. This command activates the project and sets up the environment.

    For projects that are also usable as modules, you can use the symbol syntax @quickactivate :ProjectName. This combines the activation of the project and the using ProjectName command into one step.

    Note: For @quickactivate :ProjectName to work, your project must have a src/ProjectName.jl file defining a module named ProjectName, and the Project.toml file must have the name = "ProjectName" field set.

    using DrWatson
    @quickactivate "ProjectName"
    using OtherPackages
    
    include(srcdir("script.jl"))
  9. Transition an existing project to DrWatson

    main

    You do not need to use initialize_project if you already have an existing project.

    If your project is already a Julia project (has Project.toml and Manifest.toml): Simply activate it and start using DrWatson functions like projectdir() to navigate your existing folders.

    If your project is NOT a Julia project:

    1. Navigate to your project folder: cd("path/to/project").
    2. Activate it in Julia: Pkg.activate(".").
    3. Add your required packages: Pkg.add("Package1", "Package2", ...). This will automatically generate the necessary Project.toml and Manifest.toml files.
    cd("path/to/project")
    using Pkg
    Pkg.activate(".")
    Pkg.add("Package1", "Package2")
  10. Prepare batch simulation parameter sets with `dict_list`

    main

    When running batch simulations where you want to test multiple parameter combinations, use dict_list to expand a single dictionary of parameters into a collection of individual parameter containers.

    The Expansion Rule: dict_list follows a simple rule: Anything that is a Vector is treated as many parameters, otherwise it is treated as one parameter. This allows you to pass custom iterable types as a single parameter without them being expanded incorrectly.

    This approach keeps your preparation code clean and consistent by defining the parameter space in one place before passing it to a main simulation function.

    # Example concept (actual implementation depends on dict_list behavior)
    # If params = Dict(:a => [1, 2], :b => 3)
    # dict_list(params) will produce a collection of Dicts:
    # [Dict(:a => 1, :b => 3), Dict(:a => 2, :b => 3)]
  11. Create custom directory functions

    main

    If you have custom subdirectories that you access frequently, you can create your own directory functions that behave exactly like DrWatson's built-in ones by wrapping projectdir.

    # Example: creating a function for a 'custom' folder
    customdir(args...) = projectdir("custom", args...)
    
    # Usage
    customdir("my_folder", "file.txt")