Skip to content

Commit

Permalink
Add uses cross-ref to math docs & collapsed admonitions (#656)
Browse files Browse the repository at this point in the history
& alphabetically sort math docs
  • Loading branch information
brynpickering authored Sep 5, 2024
1 parent c3d6c4c commit 3cd8caf
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

### User-facing changes

|new| Math component cross-references in both directions ("uses" and "used in") in Markdown math documentation (#643).

|fixed| Duplicated links in math documentation (#651).

|changed| `node_groups` and `tech_groups` changed to a general top-level `templates` key,
accessed via the `template` key (replacing `inherit`) in `nodes` and `techs` (#600).

Expand Down
2 changes: 1 addition & 1 deletion docs/hooks/generate_math_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def write_file(

nav_reference["Pre-defined math"].append(output_file.as_posix())

math_doc = model.math_documentation.write(format="md", mkdocs_tabbed=True)
math_doc = model.math_documentation.write(format="md", mkdocs_features=True)
file_to_download = Path("..") / filename
output_full_filepath.write_text(
PREPEND_SNIPPET.format(
Expand Down
4 changes: 3 additions & 1 deletion docs/user_defined_math/customise.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,7 @@ We recommend you only use HTML as the equations can become too long for a PDF pa

!!! note

You can add the tabs to flip between rich-text math and the input YAML snippet in your math documentation by using the `mkdocs_tabbed` argument in `model.math_documentation.write`.
You can add interactive elements to your documentation, if you are planning to host them online using MKDocs.
This includes tabs to flip between rich-text math and the input YAML snippet, and dropdown lists for math component cross-references.
Just set the `mkdocs_features` argument to `True` in `model.math_documentation.write`.
We use this functionality in our [pre-defined math](../pre_defined_math/index.md).
92 changes: 70 additions & 22 deletions src/calliope/backend/latex_backend_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,18 @@ def inputs(self, val: xr.Dataset):
def write(
self,
filename: Literal[None] = None,
mkdocs_tabbed: bool = False,
mkdocs_features: bool = False,
format: _ALLOWED_MATH_FILE_FORMATS | None = None,
) -> str: ...

# Expecting None (and format arg is not needed) if giving filename.
@overload
def write(self, filename: str | Path, mkdocs_tabbed: bool = False) -> None: ...
def write(self, filename: str | Path, mkdocs_features: bool = False) -> None: ...

def write(
self,
filename: str | Path | None = None,
mkdocs_tabbed: bool = False,
mkdocs_features: bool = False,
format: _ALLOWED_MATH_FILE_FORMATS | None = None,
) -> str | None:
"""Write model documentation.
Expand All @@ -86,9 +86,10 @@ def write(
filename (str | Path | None, optional):
If given, will write the built mathematical formulation to a file with
the given extension as the file format. Defaults to None.
mkdocs_tabbed (bool, optional):
If True and Markdown docs are being generated, the equations will be on
a tab and the original YAML math definition will be on another tab.
mkdocs_features (bool, optional):
If True and Markdown docs are being generated, then:
- the equations will be on a tab and the original YAML math definition will be on another tab;
- the equation cross-references will be given in a drop-down list.
Defaults to False.
format (_ALLOWED_MATH_FILE_FORMATS | None, optional):
Not required if filename is given (as the format will be automatically inferred).
Expand Down Expand Up @@ -119,7 +120,7 @@ def write(
raise ValueError(
f"Math documentation format must be one of {allowed_formats}, received `{format}`"
)
populated_doc = self._instance.generate_math_doc(format, mkdocs_tabbed)
populated_doc = self._instance.generate_math_doc(format, mkdocs_features)

if filename is None:
return populated_doc
Expand Down Expand Up @@ -199,10 +200,18 @@ class LatexBackendModel(backend_model.BackendModelGenerator):
{{ equation.description }}
{% endif %}
{% if equation.references %}
{% if equation.used_in %}
**Used in**:
{% for ref in equation.references %}
{% for ref in equation.used_in %}
* {{ ref }}
{% endfor %}
{% endif %}
{% if equation.uses %}
**Uses**:
{% for ref in equation.uses %}
* {{ ref }}
{% endfor %}
Expand Down Expand Up @@ -264,10 +273,19 @@ class LatexBackendModel(backend_model.BackendModelGenerator):
{{ equation.description }}
{% endif %}
{% if equation.references %}
{% if equation.used_in %}
\textbf{Used in}:
{% for ref in equation.references %}
{% for ref in equation.used_in %}
\begin{itemize}
\item {{ ref }}
\end{itemize}
{% endfor %}
{% endif %}
{% if equation.uses %}
\textbf{Uses}:
{% for ref in equation.uses %}
\begin{itemize}
\item {{ ref }}
\end{itemize}
Expand Down Expand Up @@ -321,12 +339,28 @@ class LatexBackendModel(backend_model.BackendModelGenerator):
{{ equation.description }}
{% endif %}
{% if equation.references %}
{% if equation.used_in %}
{% if mkdocs_features %}
??? info "Used in"
{% else %}
**Used in**:
{% endif %}
{% for ref in equation.used_in %}
{{ " " if mkdocs_features else "" }}* [{{ ref }}](#{{ ref }})
{% endfor %}
{% endif %}
{% if equation.uses %}
{% for ref in equation.references %}
* [{{ ref }}](#{{ ref }})
{% if mkdocs_features %}
??? info "Uses"
{% else %}
**Uses**:
{% endif %}
{% for ref in equation.uses %}
{{ " " if mkdocs_features else "" }}* [{{ ref }}](#{{ ref }})
{% endfor %}
{% endif %}
{% if equation.unit is not none %}
Expand All @@ -338,7 +372,7 @@ class LatexBackendModel(backend_model.BackendModelGenerator):
**Default**: {{ equation.default }}
{% endif %}
{% if equation.expression != "" %}
{% if mkdocs_tabbed and yaml_snippet is not none%}
{% if mkdocs_features and yaml_snippet is not none%}
=== "Math"
Expand Down Expand Up @@ -549,36 +583,50 @@ def delete_component( # noqa: D102, override
del self._dataset[key]

def generate_math_doc(
self, format: _ALLOWED_MATH_FILE_FORMATS = "tex", mkdocs_tabbed: bool = False
self, format: _ALLOWED_MATH_FILE_FORMATS = "tex", mkdocs_features: bool = False
) -> str:
"""Generate the math documentation by embedding LaTeX math in a template.
Args:
format (Literal["tex", "rst", "md"]):
The built LaTeX math will be embedded in a document of the given format (tex=LaTeX, rst=reStructuredText, md=Markdown). Defaults to "tex".
mkdocs_tabbed (bool, optional): If True and format is `md`, the equations will be on a tab and the original YAML math definition will be on another tab.
mkdocs_features (bool, optional):
If True and format is `md`, then:
- the equations will be on a tab and the original YAML math definition will be on another tab;
- the equation cross-references will be given in a drop-down list.
Returns:
str: Generated math documentation.
"""
if mkdocs_tabbed and format != "md":
if mkdocs_features and format != "md":
raise ModelError(
"Cannot use MKDocs tabs when writing math to a non-Markdown file format."
"Cannot use MKDocs features when writing math to a non-Markdown file format."
)

doc_template = self.FORMAT_STRINGS[format]
uses = {
name: set(
other
for other, da_other in self._dataset.data_vars.items()
if name in da_other.attrs.get("references", set())
)
for name in self._dataset.data_vars
}
components = {
objtype: [
{
"expression": da.attrs.get("math_string", ""),
"name": name,
"description": da.attrs.get("description", None),
"references": list(da.attrs.get("references", set())),
"used_in": sorted(
list(da.attrs.get("references", set()) - set([name]))
),
"uses": sorted(list(uses[name] - set([name]))),
"default": da.attrs.get("default", None),
"unit": da.attrs.get("unit", None),
"yaml_snippet": da.attrs.get("yaml_snippet", None),
}
for name, da in getattr(self, objtype).data_vars.items()
for name, da in sorted(getattr(self, objtype).data_vars.items())
if "math_string" in da.attrs
or (objtype == "parameters" and da.attrs["references"])
]
Expand All @@ -595,7 +643,7 @@ def generate_math_doc(
if not components["parameters"]:
del components["parameters"]
return self._render(
doc_template, mkdocs_tabbed=mkdocs_tabbed, components=components
doc_template, mkdocs_features=mkdocs_features, components=components
)

def _add_latex_strings(
Expand Down
74 changes: 69 additions & 5 deletions tests/test_backend_latex_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,11 @@ def test_create_obj_list(self, dummy_latex_backend_model):
foobar
\textbf{Uses}:
\begin{itemize}
\item no_dims
\end{itemize}
\textbf{Default}: 0
\begin{equation}
Expand Down Expand Up @@ -393,6 +398,10 @@ def test_create_obj_list(self, dummy_latex_backend_model):
foobar
**Uses**:
* no_dims
**Default**: 0
.. container:: scrolling-wrapper
Expand Down Expand Up @@ -425,6 +434,10 @@ def test_create_obj_list(self, dummy_latex_backend_model):
foobar
**Uses**:
* [no_dims](#no_dims)
**Default**: 0
$$
Expand Down Expand Up @@ -488,7 +501,7 @@ def test_generate_math_doc_no_params(self, dummy_model_data):
"""
)

def test_generate_math_doc_mkdocs_tabbed(self, dummy_model_data):
def test_generate_math_doc_mkdocs_features_tabs(self, dummy_model_data):
backend_model = latex_backend_model.LatexBackendModel(dummy_model_data)
backend_model.add_global_expression(
"expr",
Expand All @@ -498,7 +511,7 @@ def test_generate_math_doc_mkdocs_tabbed(self, dummy_model_data):
"default": 0,
},
)
doc = backend_model.generate_math_doc(format="md", mkdocs_tabbed=True)
doc = backend_model.generate_math_doc(format="md", mkdocs_features=True)
assert doc == textwrap.dedent(
r"""
Expand Down Expand Up @@ -527,14 +540,65 @@ def test_generate_math_doc_mkdocs_tabbed(self, dummy_model_data):
"""
)

def test_generate_math_doc_mkdocs_tabbed_not_in_md(self, dummy_model_data):
def test_generate_math_doc_mkdocs_features_admonition(self, dummy_model_data):
backend_model = latex_backend_model.LatexBackendModel(dummy_model_data)
backend_model.add_global_expression(
"expr",
{
"equations": [{"expression": "no_dims + 1"}],
"description": "foobar",
"default": 0,
},
)
doc = backend_model.generate_math_doc(format="md", mkdocs_features=True)
assert doc == textwrap.dedent(
r"""
## Where
### expr
foobar
??? info "Uses"
* [no_dims](#no_dims)
**Default**: 0
=== "Math"
$$
\begin{array}{l}
\quad \textit{no\_dims} + 1\\
\end{array}
$$
=== "YAML"
```yaml
equations:
- expression: no_dims + 1
```
## Parameters
### no_dims
??? info "Used in"
* [expr](#expr)
"""
)

def test_generate_math_doc_mkdocs_features_not_in_md(self, dummy_model_data):
backend_model = latex_backend_model.LatexBackendModel(dummy_model_data)
with pytest.raises(exceptions.ModelError) as excinfo:
backend_model.generate_math_doc(format="rst", mkdocs_tabbed=True)
backend_model.generate_math_doc(format="rst", mkdocs_features=True)

assert check_error_or_warning(
excinfo,
"Cannot use MKDocs tabs when writing math to a non-Markdown file format.",
"Cannot use MKDocs features when writing math to a non-Markdown file format.",
)

@pytest.mark.parametrize(
Expand Down

0 comments on commit 3cd8caf

Please sign in to comment.