Documentation

The documentation is build with Documenter.jl, by running the docs/make.jl script. Note that docs/make.jl calls some stuff from docs/docs_util.jl.

Build the documentation locally

The documentation is bundled in with the source code, so it is possible to build the documentation locally.

First, navigate into the SpineOpt main folder and activate the docs environment from the julia package manager:

(SpineOpt) pkg> activate docs
(docs) pkg>

Next, in order to make sure that the docs environment uses the same SpineOpt version it is contained within, install the package locally into the docs environment:

(docs) pkg> develop .
Resolving package versions...
<lots of packages being checked>
(docs) pkg>

Now, you should be able to build the documentation by exiting the package manager and typing:

julia> include("docs/make.jl")

This should build the documentation on your computer, and you can access it in the docs/build/ folder.

Note that in order for the above to work, the docs environment needs to be active in your current session, which it is if you just the environment up according to the above instructions. However, if you need to build the documentation repeatedly (e.g. for testing), uncommenting lines 2-3 from docs/make.jl results in the script activating docs on its own.

# Tasku: Uncomment these and run this script to build Docs locally.
#using Pkg
#Pkg.activate(@__DIR__)

Note that one likely needs to keep them omitted for the GitHub deployment to work, though.

Automatically generated content

SpineOpt documentation is partially autogenerated to reduce manual maintenance needs, as well as to try and ensure the consistency of its contents. The first step is the Concept reference generation, followed by Generating mathematical formulation using docstrings, and finally Populating empty chapters.

Concept reference generation

The SpineOpt Template sections in the documentation are generated automatically using tempates/spineopt_template.json. Entity Classes, Parameters, and Parameter Value Lists are collected, cross-referenced, and expanded using manual description files stored under docs/src/concept_reference/. The names of the manual description files need to match the entity/parameter/list they describe. Due to being autogenerated, there is no point in making manual changes to the docs/src/concept_reference/Entity Classes.md, .../Parameters.md, or .../Parameter Value Lists.md files. Instead, edit/add manual descriptions.

Generating mathematical formulation using docstrings

The mathematical formulation of the constraints is also automatically generated: docs/src/mathematical_formulation/constraints.txt contains tags to automatically pull a function's docstring to the file .../constraints_automatically_generated.md.

Note

.../constraints.txt is written in markdown, but must not be .md! Otherwise, Documenter.jl will attempt to cross-reference its sections and get confused by the duplicate @ids in .../constraints_automatically_generated.md

An example of a tag:

@@add_constraint_nodal_balance!

An example for how the docstring looks:

@doc raw"""
    add_constraint_nodal_balance!(m::Model)

Balance equation for nodes.

In **SpineOpt**, [node](@ref) is the place where an energy balance is enforced. As universal aggregators,
they are the glue that brings all components of the energy system together. An energy balance is created for each [node](@ref) for all `node_stochastic_time_indices`, unless the [balance\_type](@ref) parameter of the node takes the value [none](@ref balance_type_list) or if the node in question is a member of a node group, for which the [balance\_type](@ref) is [group\_balance](@ref balance_type_list). The parameter [balance\_sense](@ref) defaults to equality, but can be changed to allow overproduction ([balance\_sense](@ref) [`>=`](@ref constraint_sense_list)) or underproduction ([balance\_sense](@ref) [`<=`](@ref constraint_sense_list)).
The energy balance is enforced by the following constraint:

#```math # NOTE! This line doesn't really have the `#` in the beginning, but it messes up the example otherwise.
\begin{aligned}
& v_{node\_injection}(n,s,t) \\
& + \sum_{\substack{(conn,n',d_{in},s,t) \in connection\_flow\_indices: \\ d_{out} == :to\_node}}
v_{connection\_flow}(conn,n',d_{in},s,t)\\
& - \sum_{\substack{(conn,n',d_{out},s,t) \in connection\_flow\_indices: \\ d_{out} == :from\_node}}
v_{connection\_flow}(conn,n',d_{out},s,t)\\
& + v_{node\_slack\_pos}(n,s,t) \\
& - v_{node\_slack\_neg}(n,s,t) \\
& \{>=,==,<=\} \\
& 0 \\
& \forall (n,s,t) \in node\_stochastic\_time\_indices: \\
& p_{balance\_type}(n) != none \\
& \nexists ng \in groups(n) : group\_balance \\
\end{aligned}
#``` # NOTE! This `#` is not really here either.
"""

The reason for using the docstring is such that it is easier to update the documentation in the docstring when developing a certain constraint.

The feature is completely optional. To activate the functionality for another file (e.g. .../objective_function.md) add tags to that file and then add code similar to this to docs/make.jl:

mathpath = joinpath(path, "src", "mathematical_formulation")
docstrings = all_docstrings(SpineOpt)

objective_function_lines = readlines(joinpath(mathpath, "objective_function.md"))
expand_tags!(objective_function_lines, docstrings)
open(joinpath(mathpath, "objective_function_automatically_generated.md"), "w") do file
    write(file, join(objective_function_lines, "\n"))
end

To deactivate the functionality, just remove the code and replace the tags in your .md file.

It is also possible to introduce this feature over time. Anytime you want to add the documentation of a constraint to the docstring you need to follow a few steps:

  1. For the docstring
    1. add @doc raw before the docstring (that allows to write latex in the docstring)
  2. For the .md file
    1. cut the description and mathematical formulation and paste them in the corresponding function's docstring
    2. add the tag to pull the above from the docstring

An example of both the docstring and the instruction file have already been shown above.

Populating empty chapters

There is also a drag-and-drop feature for select chapters (e.g. the how to section). For those chapters you can simply add your markdown file to the folder of the chapter, and it will be automatically added to the documentation. To allow both manually composed chapters and automatically generated chapter, the functionality is only activated for empty chapters (of the structure "chapter name" => []).

The drag-and-drop function assumes a specific structure for the documentation files.

  • All chapters and corresponding markdown files are in the docs/src folder.
  • Folder names need to be lowercase with underscores because the automated folder names are derived from the page names in docs/make.jl. A new chapter (e.g. implementation details) needs to follow this structure.
  • Markdown file names can have uppercases and can have underscores but don't need to because the page names in docs/make.jl are derived from the actual file names. In other words, your filename will become the page name in the documentation so make this descriptive.