diff --git a/CHANGELOG.md b/CHANGELOG.md index e0a24244..92163728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### User-facing changes +|new| (non-NaN) Default values and data types for parameters appear in math documentation (if they appear in the model definition schema) (#677). + |changed| `data_sources` -> `data_tables` and `data_sources.source` -> `data_tables.data`. This change has occurred to avoid confusion between data "sources" and model energy "sources" (#673). diff --git a/src/calliope/backend/backend_model.py b/src/calliope/backend/backend_model.py index ab0cd78f..26fd70d5 100644 --- a/src/calliope/backend/backend_model.py +++ b/src/calliope/backend/backend_model.py @@ -66,6 +66,7 @@ class BackendModelGenerator(ABC): "description", "unit", "default", + "type", "title", "math_repr", "original_dtype", @@ -74,6 +75,7 @@ class BackendModelGenerator(ABC): _PARAM_TITLES = extract_from_schema(MODEL_SCHEMA, "title") _PARAM_DESCRIPTIONS = extract_from_schema(MODEL_SCHEMA, "description") _PARAM_UNITS = extract_from_schema(MODEL_SCHEMA, "x-unit") + _PARAM_TYPE = extract_from_schema(MODEL_SCHEMA, "x-type") def __init__(self, inputs: xr.Dataset, **kwargs): """Abstract base class to build a representation of the optimisation problem. diff --git a/src/calliope/backend/latex_backend_model.py b/src/calliope/backend/latex_backend_model.py index 0256af5a..c1c86243 100644 --- a/src/calliope/backend/latex_backend_model.py +++ b/src/calliope/backend/latex_backend_model.py @@ -11,6 +11,7 @@ import jinja2 import numpy as np +import pandas as pd import xarray as xr from calliope.backend import backend_model, parsing @@ -224,6 +225,10 @@ class LatexBackendModel(backend_model.BackendModelGenerator): **Default**: {{ equation.default }} {% endif %} + {% if equation.type is not none %} + + **Type**: {{ equation.type }} + {% endif %} {% if equation.expression != "" %} .. container:: scrolling-wrapper @@ -299,6 +304,10 @@ class LatexBackendModel(backend_model.BackendModelGenerator): \textbf{Default}: {{ equation.default }} {% endif %} + {% if equation.type is not none %} + + \textbf{Type}: {{ equation.type }} + {% endif %} {% if equation.expression != "" %} \begin{equation} @@ -371,6 +380,10 @@ class LatexBackendModel(backend_model.BackendModelGenerator): **Default**: {{ equation.default }} {% endif %} + {% if equation.type is not none %} + + **Type**: {{ equation.type }} + {% endif %} {% if equation.expression != "" %} {% if mkdocs_features and yaml_snippet is not none%} @@ -421,14 +434,17 @@ def add_parameter( # noqa: D102, override "title": self._PARAM_TITLES.get(parameter_name, None), "description": self._PARAM_DESCRIPTIONS.get(parameter_name, None), "unit": self._PARAM_UNITS.get(parameter_name, None), + "type": self._PARAM_TYPE.get(parameter_name, None), "math_repr": rf"\textit{{{parameter_name}}}" + self._dims_to_var_string(parameter_values), } + if pd.notna(default): + attrs["default"] = default self._add_to_dataset(parameter_name, parameter_values, "parameters", attrs) def add_constraint( # noqa: D102, override - self, name: str, constraint_dict: parsing.UnparsedConstraint | None = None + self, name: str, constraint_dict: parsing.UnparsedConstraint ) -> None: equation_strings: list = [] @@ -488,7 +504,7 @@ def _constraint_setter(where: xr.DataArray, references: set) -> xr.DataArray: ) def add_global_expression( # noqa: D102, override - self, name: str, expression_dict: parsing.UnparsedExpression | None = None + self, name: str, expression_dict: parsing.UnparsedExpression ) -> None: equation_strings: list = [] @@ -515,7 +531,7 @@ def _expression_setter( ) def add_variable( # noqa: D102, override - self, name: str, variable_dict: parsing.UnparsedVariable | None = None + self, name: str, variable_dict: parsing.UnparsedVariable ) -> None: domain_dict = {"real": r"\mathbb{R}\;", "integer": r"\mathbb{Z}\;"} bound_refs: set = set() @@ -544,7 +560,7 @@ def _variable_setter(where: xr.DataArray, references: set) -> xr.DataArray: ) def add_objective( # noqa: D102, override - self, name: str, objective_dict: parsing.UnparsedObjective | None = None + self, name: str, objective_dict: parsing.UnparsedObjective ) -> None: sense_dict = { "minimize": r"\min{}", @@ -623,6 +639,7 @@ def generate_math_doc( ), "uses": sorted(list(uses[name] - set([name]))), "default": da.attrs.get("default", None), + "type": da.attrs.get("type", None), "unit": da.attrs.get("unit", None), "yaml_snippet": da.attrs.get("yaml_snippet", None), } diff --git a/tests/conftest.py b/tests/conftest.py index 50c7b203..b01dc5ba 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -289,9 +289,13 @@ def dummy_model_data(config_defaults, model_defaults): "all_nan": np.nan, "with_inf": 100, "only_techs": 5, + "no_dims": 0, **model_defaults, } ) + # This value is set on the parameter directly to ensure it finds its way through to the LaTex math. + model_data.no_dims.attrs["default"] = 0 + model_data.attrs["math"] = AttrDict( {"constraints": {}, "variables": {}, "global_expressions": {}, "objectives": {}} ) diff --git a/tests/test_backend_latex_backend.py b/tests/test_backend_latex_backend.py index 0b6220a4..e309484e 100644 --- a/tests/test_backend_latex_backend.py +++ b/tests/test_backend_latex_backend.py @@ -383,6 +383,8 @@ def test_create_obj_list(self, dummy_latex_backend_model): \begin{itemize} \item expr \end{itemize} + + \textbf{Default}: 0 \end{document}""" ), ), @@ -421,6 +423,8 @@ def test_create_obj_list(self, dummy_latex_backend_model): **Used in**: * expr + + **Default**: 0 """ ), ), @@ -454,6 +458,8 @@ def test_create_obj_list(self, dummy_latex_backend_model): **Used in**: * [expr](#expr) + + **Default**: 0 """ ), ), @@ -589,6 +595,8 @@ def test_generate_math_doc_mkdocs_features_admonition(self, dummy_model_data): ??? info "Used in" * [expr](#expr) + + **Default**: 0 """ ) @@ -709,3 +717,53 @@ def test_get_variable_bounds_string(self, dummy_latex_backend_model): "expression": r"\textbf{multi_dim_var}_\text{node,tech} \leq 2\mathord{\times}10^{+06}" } assert refs == {"multi_dim_var"} + + def test_param_type(self, dummy_model_data): + backend_model = latex_backend_model.LatexBackendModel(dummy_model_data) + backend_model.add_global_expression( + "expr", + { + "equations": [{"expression": "1 + flow_cap_max"}], + "description": "foobar", + "default": 0, + }, + ) + doc = backend_model.generate_math_doc(format="md") + assert doc == textwrap.dedent( + r""" + + ## Where + + ### expr + + foobar + + **Uses**: + + * [flow_cap_max](#flow_cap_max) + + **Default**: 0 + + $$ + \begin{array}{l} + \quad 1 + \textit{flow\_cap\_max}\\ + \end{array} + $$ + + ## Parameters + + ### flow_cap_max + + Limits `flow_cap` to a maximum. + + **Used in**: + + * [expr](#expr) + + **Unit**: power. + + **Default**: inf + + **Type**: float + """ + )