Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uses cross-ref to math docs & collapsed admonitions #656

Merged
merged 5 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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