diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..1800f55 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 0f197074e684046035fce96fbb25e89b +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 0000000..fa06466 Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 0000000..2fc28f2 Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/notebooks/custom_results_1.doctree b/.doctrees/notebooks/custom_results_1.doctree new file mode 100644 index 0000000..1531f25 Binary files /dev/null and b/.doctrees/notebooks/custom_results_1.doctree differ diff --git a/.doctrees/notebooks/custom_results_2.doctree b/.doctrees/notebooks/custom_results_2.doctree new file mode 100644 index 0000000..a68ba35 Binary files /dev/null and b/.doctrees/notebooks/custom_results_2.doctree differ diff --git a/.doctrees/notebooks/first_model.doctree b/.doctrees/notebooks/first_model.doctree new file mode 100644 index 0000000..89a7c7e Binary files /dev/null and b/.doctrees/notebooks/first_model.doctree differ diff --git a/.doctrees/pages/dev/core.doctree b/.doctrees/pages/dev/core.doctree new file mode 100644 index 0000000..ce33cf5 Binary files /dev/null and b/.doctrees/pages/dev/core.doctree differ diff --git a/.doctrees/pages/dev/general.doctree b/.doctrees/pages/dev/general.doctree new file mode 100644 index 0000000..ca15cfb Binary files /dev/null and b/.doctrees/pages/dev/general.doctree differ diff --git a/.doctrees/pages/dev/updating.doctree b/.doctrees/pages/dev/updating.doctree new file mode 100644 index 0000000..ccfdabb Binary files /dev/null and b/.doctrees/pages/dev/updating.doctree differ diff --git a/.doctrees/pages/manual/julia/assets.doctree b/.doctrees/pages/manual/julia/assets.doctree new file mode 100644 index 0000000..115824d Binary files /dev/null and b/.doctrees/pages/manual/julia/assets.doctree differ diff --git a/.doctrees/pages/manual/julia/index.doctree b/.doctrees/pages/manual/julia/index.doctree new file mode 100644 index 0000000..d5b6a6e Binary files /dev/null and b/.doctrees/pages/manual/julia/index.doctree differ diff --git a/.doctrees/pages/manual/julia/resultsduckdb.doctree b/.doctrees/pages/manual/julia/resultsduckdb.doctree new file mode 100644 index 0000000..5920600 Binary files /dev/null and b/.doctrees/pages/manual/julia/resultsduckdb.doctree differ diff --git a/.doctrees/pages/manual/julia/resultsjld2.doctree b/.doctrees/pages/manual/julia/resultsjld2.doctree new file mode 100644 index 0000000..8f4d022 Binary files /dev/null and b/.doctrees/pages/manual/julia/resultsjld2.doctree differ diff --git a/.doctrees/pages/manual/julia/utilities.doctree b/.doctrees/pages/manual/julia/utilities.doctree new file mode 100644 index 0000000..f60dbbb Binary files /dev/null and b/.doctrees/pages/manual/julia/utilities.doctree differ diff --git a/.doctrees/pages/manual/python/configuration.doctree b/.doctrees/pages/manual/python/configuration.doctree new file mode 100644 index 0000000..7f60ce9 Binary files /dev/null and b/.doctrees/pages/manual/python/configuration.doctree differ diff --git a/.doctrees/pages/manual/python/index.doctree b/.doctrees/pages/manual/python/index.doctree new file mode 100644 index 0000000..f016d38 Binary files /dev/null and b/.doctrees/pages/manual/python/index.doctree differ diff --git a/.doctrees/pages/manual/python/jump.doctree b/.doctrees/pages/manual/python/jump.doctree new file mode 100644 index 0000000..cce50b7 Binary files /dev/null and b/.doctrees/pages/manual/python/jump.doctree differ diff --git a/.doctrees/pages/manual/python/model.doctree b/.doctrees/pages/manual/python/model.doctree new file mode 100644 index 0000000..3466532 Binary files /dev/null and b/.doctrees/pages/manual/python/model.doctree differ diff --git a/.doctrees/pages/manual/python/results.doctree b/.doctrees/pages/manual/python/results.doctree new file mode 100644 index 0000000..43c47c6 Binary files /dev/null and b/.doctrees/pages/manual/python/results.doctree differ diff --git a/.doctrees/pages/manual/python/util.doctree b/.doctrees/pages/manual/python/util.doctree new file mode 100644 index 0000000..8862abf Binary files /dev/null and b/.doctrees/pages/manual/python/util.doctree differ diff --git a/.doctrees/pages/manual/yaml/core/connection.doctree b/.doctrees/pages/manual/yaml/core/connection.doctree new file mode 100644 index 0000000..54722ce Binary files /dev/null and b/.doctrees/pages/manual/yaml/core/connection.doctree differ diff --git a/.doctrees/pages/manual/yaml/core/decision.doctree b/.doctrees/pages/manual/yaml/core/decision.doctree new file mode 100644 index 0000000..8c4f5e8 Binary files /dev/null and b/.doctrees/pages/manual/yaml/core/decision.doctree differ diff --git a/.doctrees/pages/manual/yaml/core/node.doctree b/.doctrees/pages/manual/yaml/core/node.doctree new file mode 100644 index 0000000..c843739 Binary files /dev/null and b/.doctrees/pages/manual/yaml/core/node.doctree differ diff --git a/.doctrees/pages/manual/yaml/core/profile.doctree b/.doctrees/pages/manual/yaml/core/profile.doctree new file mode 100644 index 0000000..d821033 Binary files /dev/null and b/.doctrees/pages/manual/yaml/core/profile.doctree differ diff --git a/.doctrees/pages/manual/yaml/core/unit.doctree b/.doctrees/pages/manual/yaml/core/unit.doctree new file mode 100644 index 0000000..6e64f38 Binary files /dev/null and b/.doctrees/pages/manual/yaml/core/unit.doctree differ diff --git a/.doctrees/pages/manual/yaml/core_components.doctree b/.doctrees/pages/manual/yaml/core_components.doctree new file mode 100644 index 0000000..608c692 Binary files /dev/null and b/.doctrees/pages/manual/yaml/core_components.doctree differ diff --git a/.doctrees/pages/manual/yaml/index.doctree b/.doctrees/pages/manual/yaml/index.doctree new file mode 100644 index 0000000..6d469cc Binary files /dev/null and b/.doctrees/pages/manual/yaml/index.doctree differ diff --git a/.doctrees/pages/manual/yaml/top_level.doctree b/.doctrees/pages/manual/yaml/top_level.doctree new file mode 100644 index 0000000..dd8091f Binary files /dev/null and b/.doctrees/pages/manual/yaml/top_level.doctree differ diff --git a/.doctrees/pages/references/projects.doctree b/.doctrees/pages/references/projects.doctree new file mode 100644 index 0000000..bf93393 Binary files /dev/null and b/.doctrees/pages/references/projects.doctree differ diff --git a/.doctrees/pages/references/publications.doctree b/.doctrees/pages/references/publications.doctree new file mode 100644 index 0000000..52cfb3d Binary files /dev/null and b/.doctrees/pages/references/publications.doctree differ diff --git a/.doctrees/pages/tutorials/addons.doctree b/.doctrees/pages/tutorials/addons.doctree new file mode 100644 index 0000000..bb41425 Binary files /dev/null and b/.doctrees/pages/tutorials/addons.doctree differ diff --git a/.doctrees/pages/tutorials/templates_1.doctree b/.doctrees/pages/tutorials/templates_1.doctree new file mode 100644 index 0000000..08970de Binary files /dev/null and b/.doctrees/pages/tutorials/templates_1.doctree differ diff --git a/.doctrees/pages/tutorials/templates_2.doctree b/.doctrees/pages/tutorials/templates_2.doctree new file mode 100644 index 0000000..235d368 Binary files /dev/null and b/.doctrees/pages/tutorials/templates_2.doctree differ diff --git a/.doctrees/pages/user_guides/general/best_practice.doctree b/.doctrees/pages/user_guides/general/best_practice.doctree new file mode 100644 index 0000000..4c4f4fe Binary files /dev/null and b/.doctrees/pages/user_guides/general/best_practice.doctree differ diff --git a/.doctrees/pages/user_guides/general/common_errors.doctree b/.doctrees/pages/user_guides/general/common_errors.doctree new file mode 100644 index 0000000..674da29 Binary files /dev/null and b/.doctrees/pages/user_guides/general/common_errors.doctree differ diff --git a/.doctrees/pages/user_guides/general/linking_components.doctree b/.doctrees/pages/user_guides/general/linking_components.doctree new file mode 100644 index 0000000..740bb80 Binary files /dev/null and b/.doctrees/pages/user_guides/general/linking_components.doctree differ diff --git a/.doctrees/pages/user_guides/general/solvers.doctree b/.doctrees/pages/user_guides/general/solvers.doctree new file mode 100644 index 0000000..94eb848 Binary files /dev/null and b/.doctrees/pages/user_guides/general/solvers.doctree differ diff --git a/.doctrees/pages/user_guides/index.doctree b/.doctrees/pages/user_guides/index.doctree new file mode 100644 index 0000000..de46bd6 Binary files /dev/null and b/.doctrees/pages/user_guides/index.doctree differ diff --git a/.doctrees/pages/user_guides/qna/dynamic_marginal_costs.doctree b/.doctrees/pages/user_guides/qna/dynamic_marginal_costs.doctree new file mode 100644 index 0000000..3ba7ef8 Binary files /dev/null and b/.doctrees/pages/user_guides/qna/dynamic_marginal_costs.doctree differ diff --git a/.doctrees/pages/user_guides/qna/passive_charging.doctree b/.doctrees/pages/user_guides/qna/passive_charging.doctree new file mode 100644 index 0000000..092e684 Binary files /dev/null and b/.doctrees/pages/user_guides/qna/passive_charging.doctree differ diff --git a/.doctrees/pages/user_guides/qna/profiles_sign.doctree b/.doctrees/pages/user_guides/qna/profiles_sign.doctree new file mode 100644 index 0000000..d1081c0 Binary files /dev/null and b/.doctrees/pages/user_guides/qna/profiles_sign.doctree differ diff --git a/.doctrees/pages/user_guides/qna/snapshot_duration.doctree b/.doctrees/pages/user_guides/qna/snapshot_duration.doctree new file mode 100644 index 0000000..42a96eb Binary files /dev/null and b/.doctrees/pages/user_guides/qna/snapshot_duration.doctree differ diff --git a/.doctrees/pages/user_guides/toc_general.doctree b/.doctrees/pages/user_guides/toc_general.doctree new file mode 100644 index 0000000..edf04a5 Binary files /dev/null and b/.doctrees/pages/user_guides/toc_general.doctree differ diff --git a/.doctrees/pages/user_guides/toc_qna.doctree b/.doctrees/pages/user_guides/toc_qna.doctree new file mode 100644 index 0000000..240ae43 Binary files /dev/null and b/.doctrees/pages/user_guides/toc_qna.doctree differ diff --git a/.doctrees/tocs/tutorials_extracting_results.doctree b/.doctrees/tocs/tutorials_extracting_results.doctree new file mode 100644 index 0000000..4783c0b Binary files /dev/null and b/.doctrees/tocs/tutorials_extracting_results.doctree differ diff --git a/.doctrees/tocs/tutorials_templates.doctree b/.doctrees/tocs/tutorials_templates.doctree new file mode 100644 index 0000000..e9f17f3 Binary files /dev/null and b/.doctrees/tocs/tutorials_templates.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_modules/iesopt/iesopt.html b/_modules/iesopt/iesopt.html new file mode 100644 index 0000000..abc2a47 --- /dev/null +++ b/_modules/iesopt/iesopt.html @@ -0,0 +1,391 @@ + + + + + + + + + iesopt.iesopt | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+ +
+
+
+ + +
+

Source code for iesopt.iesopt

+from pathlib import Path
+
+from .util import logger, get_iesopt_module_attr
+from .model import Model, ModelStatus
+
+
+
+[docs] +def run(filename: str | Path, **kwargs) -> Model: + r""" + Generate and optimize an IESopt model. + + Results can be accessed (after a successful optimization) using `model.results`. + + Arguments: + filename : str + Path to the IESopt model file to load. + + Keyword Arguments: + **kwargs: Additional keyword arguments to pass to the `Model` constructor. + + Returns: + The generated and optimized IESopt model. + + Example: + .. code-block:: python + :caption: Run an IESopt model + + import iesopt + iesopt.run("opt/config.iesopt.yaml") + """ + model = Model(filename, **kwargs) + model.generate() + + if model.status == ModelStatus.GENERATED: + model.optimize() + else: + logger.error("Model could not be generated; skipping optimization.") + + return model
+ +
+ +
+[docs] +def examples() -> list[str]: + """ + Return a list of all available examples. + + Returns: + List of available examples. This contains the names of the examples, not the full filenames (so, e.g., + `"some_example"` instead of `"some_example.iesopt.yaml"`). + """ + import os + + julia = get_iesopt_module_attr("julia") + folder = Path(str(julia.IESopt.Assets.get_path("examples"))) + return sorted([fn.split(".iesopt.yaml")[0] for fn in os.listdir(folder) if fn.endswith(".iesopt.yaml")])
+ +
+ +
+[docs] +def make_example(example: str, dst_dir: str | Path = "./", dst_name: str | None = None) -> Path: + """ + Generate a local copy of a specific example. + + A list of examples, and their exact names, can be obtained using :py:func:`iesopt.examples()`. + + Arguments: + example : str + Name of the example to generate. + dst_dir : Optional[str] + Directory to generate the example in, defaults to `"./"`. + dst_name: Optional[str] + Name of the generated example file (without the ".iesopt.yaml" extension), e.g., + `"config"`, will create `dst_dir/config.iesopt.yaml`. Will default to the original name of the example. + + Returns: + Path to the generated example file. + """ + import shutil + import os + import stat + + julia = get_iesopt_module_attr("julia") + folder = Path(str(julia.IESopt.Assets.get_path("examples"))) + + filename = folder / f"{example}.iesopt.yaml" + datafolder = folder / "files" + target_filename = Path(dst_dir) / ((example if dst_name is None else dst_name) + ".iesopt.yaml") + target_datafolder = Path(dst_dir) / "files" + + # Check if data folder already exists. + if not os.path.exists(target_datafolder): + logger.info("Data folder for examples does not exist; creating it, and copying contents") + shutil.copytree(datafolder, target_datafolder) + else: + logger.info("Data folder for examples already exists; NOT copying ANY contents") + + # Copy the config file. + logger.info("Creating example ('%s') at: '%s'" % (example, target_filename)) + shutil.copy(filename, target_filename) + + # Make everything editable. + logger.info( + "Set write permissions for example ('%s'), and data folder ('%s')" % (target_filename, target_datafolder) + ) + os.chmod(target_filename, os.stat(target_filename).st_mode & ~stat.S_IWRITE | stat.S_IWUSR) + for dirpath, _, filenames in os.walk(target_datafolder): + os.chmod(dirpath, os.stat(dirpath).st_mode & ~stat.S_IWRITE | stat.S_IWUSR) + for filename in filenames: + filepath = os.path.join(dirpath, filename) + os.chmod(filepath, os.stat(filepath).st_mode & ~stat.S_IWRITE | stat.S_IWUSR) + + return target_filename
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/_modules/iesopt/model.html b/_modules/iesopt/model.html new file mode 100644 index 0000000..3a1db01 --- /dev/null +++ b/_modules/iesopt/model.html @@ -0,0 +1,622 @@ + + + + + + + + + iesopt.model | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+ +
+
+
+ + +
+

Source code for iesopt.model

+from enum import Enum
+from pathlib import Path
+from warnings import warn
+
+from .util import logger, get_iesopt_module_attr
+from .julia.util import jl_symbol, recursive_convert_py2jl, jl_safe_seval
+from .results import Results
+
+
+
+[docs] +class ModelStatus(Enum): + """Status of an :py:class:`iesopt.Model`.""" + + EMPTY = "empty" + GENERATED = "generated" + FAILED_GENERATE = "failed_generate" + FAILED_OPTIMIZE = "failed_optimize" + OPTIMAL = "optimal" + OPTIMAL_LOCALLY = "local_optimum" + INFEASIBLE = "infeasible" + INFEASIBLE_OR_UNBOUNDED = "infeasible_unbounded" + OTHER = "other"
+ +
+ +
+[docs] +class Model: + """An IESopt model, based on an :jl:module:`IESopt.jl <IESopt.IESopt>` core model.""" + + def __init__(self, filename: str | Path, **kwargs) -> None: + self._filename = filename + self._kwargs = recursive_convert_py2jl(kwargs) + + self._model = None + self._verbosity = kwargs.get("verbosity", True) + + self._status = ModelStatus.EMPTY + self._status_details = None + + self._results = None + + self._IESopt = get_iesopt_module_attr("IESopt") + self._JuMP = get_iesopt_module_attr("JuMP") + self._jump_value = get_iesopt_module_attr("jump_value") + self._jump_dual = get_iesopt_module_attr("jump_dual") + + def __repr__(self) -> str: + if self._model is None: + return "An IESopt model (not yet generated)" + + n_var = self._JuMP.num_variables(self.core) + n_con = self._JuMP.num_constraints(self.core, count_variable_in_set_constraints=False) + solver = self._JuMP.solver_name(self.core) + termination_status = self._JuMP.termination_status(self.core) + + return ( + f"An IESopt model:" + f"\n\tname: {self.data.input.config['general']['name']['model']}" + f"\n\tsolver: {solver}" + f"\n\t" + f"\n\t{n_var} variables, {n_con} constraints" + f"\n\tstatus: {termination_status}" + ) + + @property + def core(self): + """Access the core `JuMP` model that is used internally.""" + if self._model is None: + raise Exception("Model was not properly set up; call `generate` first") + return self._model + + @property + def data(self): + """Access the IESopt data object of the model. + + This is deprecated; use `model.internal` instead (similar to the Julia usage `IESopt.internal(model)`). + """ + warn( + "Using `model.data` is deprecated; use `model.internal` instead (similar to the Julia usage `IESopt.internal(model)`)", + DeprecationWarning, + stacklevel=2, + ) + return self.internal + + @property + def internal(self): + """Access the IESopt data object of the model.""" + return self._IESopt.internal(self.core) + + @property + def status(self) -> ModelStatus: + """Get the current status of this model. See `ModelStatus` for possible values.""" + return self._status + + @property + def objective_value(self) -> float: + """Get the objective value of the model. Only available if the model was solved beforehand.""" + if self._status == ModelStatus.OPTIMAL_LOCALLY: + logger.warning("Model is only locally optimal; objective value may not be accurate") + elif self._status != ModelStatus.OPTIMAL: + raise Exception("Model is not optimal; no objective value available") + return self._JuMP.objective_value(self.core) + +
+[docs] + def generate(self) -> None: + """Generate a IESopt model from the attached top-level YAML config.""" + try: + self._model = self._IESopt.generate_b(str(self._filename), **self._kwargs) + self._status = ModelStatus.GENERATED + except Exception as e: + self._status = ModelStatus.FAILED_GENERATE + logger.error(f"Exception during `generate`: {e}") + try: + logger.error(f"Current debugging info: {self.data.debug}") + except Exception as e: + logger.error("Failed to extract debugging info")
+ +
+
+[docs] + def write_to_file(self, filename=None, *, format: str = "automatic") -> str: + """Write the model to a file. + + Consult the Julia version of this function, [IESopt.write_to_file](https://ait-energy.github.io/iesopt/pages/manual/julia/index.html#write-to-file) + for more information. This will automatically invoke `generate` if the model has not been generated yet. + + Arguments: + filename (Optional[str]): The filename to write to. If `None`, the path and name are automatically + determined by IESopt. + + Keyword Arguments: + format (str): The format to write the file in. If `automatic`, the format is determined based on the + extension of the filename. Otherwise, it used as input to [`JuMP.write_to_file`](https://jump.dev/JuMP.jl/stable/api/JuMP/#write_to_file), + by converting to uppercase and prefixing it with `MOI.FileFormats.FORMAT_`. Writing to, e.g., + an LP file can be done by setting `format="lp"` or `format="LP"`. + + Returns: + str: The filename (including path) of the written file. + + Examples: + .. code-block:: python + :caption: Writing a model to a problem file. + + import iesopt + + cfg = iesopt.make_example("01_basic_single_node", dst_dir="opt") + + # Model will be automatically generated when calling `write_to_file`: + model = iesopt.Model(cfg) + model.write_to_file() + + # It also works with already optimized models: + model = iesopt.run(cfg) + model.write_to_file("opt/out/my_problem.LP") + + # And supports different formats: + target = model.write_to_file("opt/out/my_problem.foo", format="mof") + print(target) + """ + if self._status == ModelStatus.EMPTY: + self.generate() + + try: + if filename is None: + return self._IESopt.write_to_file(self.core) + else: + format = jl_safe_seval(f"JuMP.MOI.FileFormats.FORMAT_{format.upper()}") + return self._IESopt.write_to_file(self.core, str(filename), format=format) + except Exception as e: + logger.error(f"Error while writing model to file: {e}") + return ""
+ +
+
+[docs] + def optimize(self) -> None: + """Optimize the model.""" + try: + self._IESopt.optimize_b(self.core) + + if self._JuMP.is_solved_and_feasible(self.core, allow_local=False): + self._status = ModelStatus.OPTIMAL + self._results = Results(model=self) + elif self._JuMP.is_solved_and_feasible(self.core, allow_local=True): + self._status = ModelStatus.OPTIMAL_LOCALLY + self._results = Results(model=self) + else: + _term_status = self._JuMP.termination_status(self.core) + if str(_term_status) == "INFEASIBLE": + self._status = ModelStatus.INFEASIBLE + logger.error( + "The model seems to be infeasible; refer to `model.compute_iis()` and its documentation if you want to know more about the source of the infeasibility." + ) + elif str(_term_status) == "INFEASIBLE_OR_UNBOUNDED": + self._status = ModelStatus.INFEASIBLE_OR_UNBOUNDED + else: + self._status = ModelStatus.OTHER + self._status_details = _term_status + except Exception as e: + self._status = ModelStatus.FAILED_OPTIMIZE + logger.error(f"Exception during `optimize`: {e}") + try: + logger.error(f"Current debugging info: {self.data.debug}") + except Exception as e: + logger.error("Failed to extract debugging info")
+ +
+
+[docs] + def compute_iis(self, filename=None) -> None: + """Compute and print the Irreducible Infeasible Set (IIS) of the model, or optionally write it to a file. + + Note that this requires a solver that supports IIS computation. + + Arguments: + filename (Optional[str | Path]): The filename to write the IIS to. If `None`, the IIS is only printed to the + console. + + Examples: + .. code-block:: python + :caption: Computing the IIS of a model. + + import iesopt + + model = iesopt.run("infeasible_model.iesopt.yaml") + model.compute_iis() + + # or (arbitrary filename/extension): + model.compute_iis(filename="my_problem.txt") + """ + try: + if filename is None: + self._IESopt.compute_IIS(self.core) + else: + self._IESopt.compute_IIS(self.core, filename=str(filename)) + except Exception as e: + logger.error("Error while computing IIS: `%s`" % str(e.args[0]))
+ +
+ @property + def results(self) -> Results: + """Get the results of the model.""" + if self._results is None: + raise Exception("No results available; have you successfully called `optimize`?") + return self._results + +
+[docs] + def extract_result(self, component: str, field: str, mode: str = "value"): + """Manually extract a specific result from the model.""" + try: + c = self._IESopt.get_component(self.core, component) + except Exception: + raise Exception(f"Exception during `extract_result({component}, {field}, mode={mode})`") + + f = None + for fieldtype in ["var", "exp", "con", "obj"]: + try: + t = getattr(c, fieldtype) + f = getattr(t, field) + break + except Exception: + pass + + if f is None: + raise Exception(f"Field `{field}` not found in component `{component}`") + + try: + if mode == "value": + return self._jump_values(f) + elif mode == "dual": + return self._jump_duals(f) + else: + raise Exception(f"Mode `{mode}` not supported, use `value` or `dual`") + except Exception: + raise Exception(f"Error during extraction of result `{field}` from component `{component}`")
+ +
+
+[docs] + def get_component(self, component: str): + """Get a core component based on its full name.""" + try: + return self._IESopt.get_component(self.core, component) + except Exception: + raise Exception(f"Error while retrieving component `{component}` from model")
+ +
+
+[docs] + def get_components(self, tagged=None): + """ + Get all components of the model, possibly filtered by (a) tag(s). + + Arguments: + tagged (str or list of str): The tag(s) to filter the components by, can be `None` (default) to get all components. + """ + if tagged is None: + return self._IESopt.get_components(self.core) + + return self._IESopt.get_components(self.core, recursive_convert_py2jl(tagged))
+ +
+
+[docs] + def get_variable(self, component: str, variable: str): + """Get a specific variable from a core component.""" + raise DeprecationWarning( + f"`get_variable(...)` is deprecated; you can instead access the variable directly using " + f"`model.get_component('{component}').var.{variable}`. Conversion to Python lists, if not done " + f"automatically, can be done using `list(...)`." + )
+ +
+
+[docs] + def get_constraint(self, component: str, constraint: str): + """Get a specific constraint from a core component.""" + raise DeprecationWarning( + f"`get_constraint(...)` is deprecated; you can instead access the constraint directly using " + f"`model.get_component('{component}').con.{constraint}`. Conversion to Python lists, if not done " + f"automatically, can be done using `list(...)`." + )
+ +
+
+[docs] + def nvar(self, var: str): + """Extract a named variable, from `model`. + + If your variable is called `:myvar`, and you would access it in Julia using `model[:myvar]`, you can call + `model.nvar("myvar")`. + """ + try: + return self.core[jl_symbol(var)] + except Exception: + raise Exception(f"Error while retrieving variable `{var}` from model")
+ +
+ @staticmethod + def _to_pylist(obj): + # if isinstance(obj, jl.VectorValue): + # return jl.PythonCall.pylist(obj) + return obj
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/_modules/iesopt/results.html b/_modules/iesopt/results.html new file mode 100644 index 0000000..96b96c1 --- /dev/null +++ b/_modules/iesopt/results.html @@ -0,0 +1,747 @@ + + + + + + + + + iesopt.results | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+ +
+
+
+ + +
+

Source code for iesopt.results

+import warnings
+import re
+import pandas as pd
+import numpy as np
+from pydantic import validate_call
+
+from .util import get_iesopt_module_attr
+from .julia import jl_isa
+
+
+class ddict(dict):
+    """Wrapper to enable dot.notation access to dictionary attributes."""
+
+    __getattr__ = dict.get
+    __setattr__ = dict.__setitem__
+    __delattr__ = dict.__delitem__
+
+
+class JuliaCallPyConvertReadWrapper:
+    def __init__(self, obj):
+        import juliacall
+
+        self._juliacall = juliacall
+        self._obj = obj
+
+    def keys(self):
+        if isinstance(self._obj, dict | self._juliacall.DictValue):
+            return dict(self._obj).keys()
+        raise TypeError(f"Object of type '{type(self._obj)}' inside JuliaCallPyConvertReadWrapper has no keys")
+
+    def __len__(self):
+        if isinstance(self._obj, dict | list | tuple | self._juliacall.DictValue | self._juliacall.VectorValue):
+            return len(self._obj)
+        raise TypeError(f"Object of type '{type(self._obj)}' inside JuliaCallPyConvertReadWrapper has no length")
+
+    def _rewrap_return(self, value):
+        if isinstance(value, self._juliacall.ArrayValue):
+            return value.to_numpy(copy=False)
+        if isinstance(value, float | int | str | bool | type(None)):
+            return value
+
+        if isinstance(value, self._juliacall.DictValue):
+            return JuliaCallPyConvertReadWrapper(value)
+
+        # Some other/unknown type, rewrap it.
+        return JuliaCallPyConvertReadWrapper(value)
+
+    def __getattr__(self, name: str):
+        return self._rewrap_return(getattr(self._obj, name))
+
+    def __getitem__(self, key):
+        return self._rewrap_return(self._obj.get(key))
+
+
+
+[docs] +class Results: + _valid_attrs = ["attributes", "model", "custom", "input", "info", "snapshots", "components"] + + @validate_call + def __init__(self, *, file: str = None, model=None): + """ + Create a new `Results` object, either from a file or from an IESopt model. Make sure to pass either a file or a + model explicitly using a keyword argument. + + Keyword Arguments + file : Optional[str] + Path to the results file to load, by default None + model : Optional[IESopt.Model] + IESopt model to extract results from, by default None + """ + self._attributes = None + self._model = None + self._snapshots = None + self._components = None + self._custom = None + self._input = None + self._info = None + self._source = None + + self._cache = None + + self._IESopt = get_iesopt_module_attr("IESopt") + self._julia = get_iesopt_module_attr("julia") + + if (int(file is None) + int(model is None)) != 1: + raise Exception("Either file or model must be set, not both and not none of them.") + elif file is not None: + if not isinstance(file, str): + raise Exception("File must be a `str`, did you try passing a model without `model=your_model`?") + self._from_file(file) + elif model is not None: + self._from_model(model) + + # Allow dot access to attributes of "model" results, to align with access to the models structure. + self._model = ddict(self._model) + + @property + def components(self): + return JuliaCallPyConvertReadWrapper(self._model["components"]) + + @property + def objectives(self): + return JuliaCallPyConvertReadWrapper(self._model["objectives"]) + + @property + def customs(self): + return JuliaCallPyConvertReadWrapper(self._model["customs"]) + + @validate_call + def get( + self, + result: str, + component: str, + fieldtype: str, + field: str, + *, + mode: str = "primal", + build_cache: bool = False, + ): + if result not in ["component", "objective", "custom"]: + raise ValueError(f"`result` must be 'component', 'objective', or 'custom', got '{result}'") + + if result in ["objective", "custom"]: + # TODO: Implement and remove this + raise NotImplementedError("Accessing objectives and custom results is not yet supported using `get(...)`") + + if mode == "dual": + field = field + "__dual" + elif mode != "primal": + raise ValueError(f"`mode` must be 'primal' or 'dual', got '{mode}'") + + if build_cache: + self._build_cache() + + if self._has_cache(): + t = (component, fieldtype, field) + if t in self._cache: + return self._cache[t] + raise ValueError(f"Failed to access result '{fieldtype}.{field}' in component '{component}'") + + return self._get_safe(component, fieldtype, field, mode) + +
+[docs] + @validate_call + def query_available_results(self, component: str, mode: str = "both"): + """ + Query the available results for a specific component, optionally filtered by `mode`. + + Arguments: + component : str + Component to query results for + mode : Optional[str] + Mode to query results for, either "both", "primal", or "dual", by default "both" + """ + regex = re.compile(component) + + if not self._has_cache(): + self._build_cache() + + if mode == "both": + return [ + (it[1], (x := it[2].split("__"))[0], "primal" if len(x) == 1 else "dual") + for it in self._cache + if re.search(regex, it[0]) + ] + elif mode == "primal": + return [(it[1], it[2]) for it in self._cache if re.search(regex, it[0]) and "__dual" not in it[2]] + elif mode == "dual": + return [ + (it[1], it[2].split("__")[0]) for it in self._cache if re.search(regex, it[0]) and "__dual" in it[2] + ] + else: + raise ValueError("Invalid mode, must be either 'both', 'primal', or 'dual'")
+ +
+
+[docs] + @validate_call + def entries(self, field: str = None): + """ + Get all available entries for a specific field, or all fields if `field` is `None`. + + Arguments: + field : Optional[str] + Field to get entries for, by default None which returns all fields + """ + if field is None: + return [it for it in Results._valid_attrs if getattr(self, f"_{it}") is not None] + return sorted(getattr(self, field).keys())
+ +
+
+[docs] + @validate_call + def to_dict(self, filter=None, field_types=None, build_cache: bool = True) -> dict: + """ + Extract results from the `Results` object and return them as a dictionary. + + Arguments: + filter : Optional[Callable] + Filter function to apply to the results, by default None, must take three arguments: (c, t, f) where `c` is + the component's name, `t` is the field type ("var", "exp", "con", or "obj"), and `f` is the field name. + field_types : Optional[list[str]] + Field types to extract, by default None, which extracts all field types. + build_cache : Optional[bool] + Whether to build a cache of the results, by default True. + + Returns: + Dictionary of results, with keys as tuples of (component, fieldtype, field) and values as the result. + """ + if build_cache: + self._build_cache() + + if field_types is None: + field_types = ["var", "exp", "con", "obj", "res"] + + entries = {} + if self._cache is None: + for c in self._model["components"].keys(): + for t in field_types: + container = getattr(self._model["components"][c], t) + for f in [str(it) for it in self._julia.Main.keys(container)]: + if (filter is None) or filter(c, t, f): + entries[(c, t, f)] = Results._safe_convert(getattr(container, f)) + else: + for c, t, f in self._cache.keys(): + if (t in field_types) and ((filter is None) or filter(c, t, f)): + entries[(c, t, f)] = self._cache[(c, t, f)] + + return entries
+ +
+
+[docs] + @validate_call + def to_pandas(self, filter=None, field_types=None, orientation: str = "long", build_cache: bool = True): + """ + Extract results from the `Results` object and return them as :py:class:`pandas.DataFrame` or :py:class:`pandas.Series` (depending on the + shape of the included results). + + Arguments: + filter : Optional[Callable] + Filter function to apply to the results, by default None, must take three arguments: (c, t, f) where `c` is + the component's name, `t` is the field type ("var", "exp", "con", or "obj"), and `f` is the field name + field_types : Optional[list[str]] + Field types to extract, by default None, which extracts all field types + orientation : Optional[str] + Orientation of the resulting DataFrame, either "wide" or "long", by default "long" + build_cache : Optional[bool] + Whether to build a cache of the results, by default True. + + Returns: + DataFrame or Series of results, depending on the orientation and shape of the results. + """ + _dict = self.to_dict(filter, field_types, build_cache) + + # Check if we can cast to `pd.Series` (no matter the orientation). + _result_types = set(type(it) for it in _dict.values()) + if len(_result_types) == 1: + _rt = _result_types.pop() + if _rt in [int, float]: + return pd.Series(_dict) + + if _rt == np.ndarray: + if len(_dict) == 1: + k, v = next(iter(_dict.items())) + + if isinstance(v, int | float): + return pd.Series(v, index=k) + + value_shape = v.shape + if len(value_shape) == 1: + if value_shape[0] == len(self._snapshots): + return pd.Series(v, index=self._snapshots, name=k) + + if orientation == "wide": + return pd.DataFrame(_dict, index=self._snapshots) + + if orientation == "wide": + warnings.warn( + "Results must be of the same shape (= length) to create a DataFrame, with `orientation='wide'`. " + "Falling back to `orientation='long'`. This warning can be prevented by calling `results.to_pandas(" + "orientation='long') instead, or by applying filters (using `filter` and/or `field_types`) that only " + "select results of the same shape (example: objectives are always scalars, while most variables - " + "except Decisions - are vector valued, indexed over all snapshots)." + ) + warnings.warn(f"Got the following result types: {_result_types}") + orientation = "long" + + if orientation == "long": + n_snapshots = len(self._snapshots) + _data = {c: [] for c in ["snapshot", "component", "fieldtype", "field", "value", "mode"]} + + for (c, t, f), v in _dict.items(): + m = "primal" + if f.endswith("__dual"): + f = f[:-6] + m = "dual" + + if isinstance(v, (int, float)): + _data["snapshot"].append(None) + _data["component"].append(c) + _data["fieldtype"].append(t) + _data["field"].append(f) + _data["value"].append(v) + _data["mode"].append(m) + else: + _data["snapshot"].extend(self._snapshots) + _data["component"].extend([c] * n_snapshots) + _data["fieldtype"].extend([t] * n_snapshots) + _data["field"].extend([f] * n_snapshots) + _data["value"].extend(v) + _data["mode"].extend([m] * n_snapshots) + + try: + return pd.DataFrame(_data) + except Exception: + warnings.warn( + "Failed to create DataFrame. This is mostlikely due to non aligned result shapes. A " + "common cause are custom results, registered inside an addon, that have a different " + "temporal resolution than the main model results. Consider filtering out such results." + ) + return None + + raise ValueError(f"`orientation` can be 'wide' or 'long', got '{orientation}'.")
+ +
+ @validate_call + def overview(self, component: str, *, temporal: bool, mode: str = "both"): + regex = re.compile(component) + + if not self._has_cache(): + self._build_cache() + + if mode == "both": + ret = { + (k[0], k[1], (x := k[2].split("__"))[0], "primal" if len(x) == 1 else "dual"): v + for (k, v) in self._cache.items() + if re.search(regex, k[0]) and isinstance(v, int | float) != temporal + } + elif mode == "primal": + ret = { + (k[0], k[1], k[2], "primal"): v + for (k, v) in self._cache.items() + if re.search(regex, k[0]) and "__dual" not in k[2] and isinstance(v, int | float) != temporal + } + elif mode == "dual": + ret = { + (k[0], k[1], k[2].split("__")[0], "dual"): v + for (k, v) in self._cache.items() + if re.search(regex, k[0]) and "__dual" in k[2] and isinstance(v, int | float) != temporal + } + else: + raise ValueError("Invalid mode, must be either 'both', 'primal', or 'dual'") + + if len(ret) == 0: + return None + + if temporal: + return pd.DataFrame(ret, index=self._snapshots).sort_index(axis=1) + else: + return pd.Series(ret) + + # def __getattr__(self, attr: str): + # if attr not in Results._valid_attrs: + # raise Exception(f"Attribute '{attr}' is not accessible, pick one of {Results._valid_attrs}") + # if not hasattr(self, f"_{attr}"): + # raise Exception(f"`Results` object has no attribute '{attr}'") + # if getattr(self, f"_{attr}") is None: + # raise Exception(f"Attribute '{attr}' not properly loaded, consider checking `results.entries()` first") + # return getattr(self, f"_{attr}") + + def _build_cache(self): + if self._cache is None: + self._cache = self.to_dict(build_cache=False) + + def _has_cache(self): + return self._cache is not None + + def _get_safe(self, component: str, fieldtype: str, field: str, mode: str = "primal"): + if mode not in ["primal", "dual"]: + raise ValueError(f"`mode` must be 'primal' or 'dual', got '{mode}'.") + + try: + components = self._model["components"] + except Exception: + raise ValueError("Failed to access `components` in model results") + try: + all_results = components[component] + except Exception: + raise ValueError(f"Failed to access component '{component}' in model results") + try: + container = getattr(all_results, fieldtype) + except Exception: + raise ValueError(f"Failed to access results for `fieldtype` '{fieldtype}' in component '{component}'") + try: + result = getattr(container, field) + except Exception: + raise ValueError(f"Failed to access result '{field}' in component '{component}'") + + return Results._safe_convert(result) + + def _from_file(self, file: str): + results = self._IESopt.load_results(file) + + result_entries = ddict({}) + for key in results.keys(): + _current = result_entries + _keys = key.split("/") + for i in range(len(_keys)): + if i == len(_keys) - 1: + _current[_keys[i]] = Results._safe_convert(results[key]) + else: + _current[_keys[i]] = _current.get(_keys[i], ddict({})) + _current = _current[_keys[i]] + + for key in result_entries.keys(): + if hasattr(self, f"_{key}"): + if getattr(self, f"_{key}") is None: + setattr(self, f"_{key}", result_entries[key]) + else: + raise Exception(f"Duplicate result entry: {key}") + else: + raise Exception(f"Unknown result entry: {key}") + + snapshots = results["model/snapshots"] + self._snapshots = [snapshots[t].name for t in sorted(snapshots.keys())] + self._components = sorted(results["model/components"].keys()) + self._source = file + + def _from_model(self, model): + self._source = "an IESopt model" + self._model = { + "components": model.internal.results.components, + "objectives": model.internal.results.objectives, + "custom": model.internal.results.customs, + } + self._snapshots = [model.internal.model.snapshots[t].name for t in model.internal.model.T] + self._components = sorted(model.internal.results.components.keys()) + + def __repr__(self) -> str: + _sep = "', '" + return ( + "An IESopt result object:" + + f"\n\tsource: {self._source}" + + "\n\tattributes: " + + ( + f"'{self.entries('attributes')[0]}', ..., '{self.entries('attributes')[-1]}'" + if self._attributes is not None + else "none" + ) + + f"\n\tmodel results: {'yes' if self._model is not None else 'no'}" + + "\n\tcustom results: " + + (f"{len(self._custom)}" if self._custom is not None and len(self._custom) > 0 else "no") + + "\n\tinput fields: " + + (f"'{_sep.join(self._input)}'" if self._input is not None else "none") + + "\n\tinfo fields: " + + (f"'{_sep.join(self._info)}'" if self._info is not None else "none") + ) + + @classmethod + def _safe_convert(cls, value): + # TODO: ensure recursive conversion of all multi-element types + if jl_isa(value, "AbstractVector"): + return value.to_numpy(copy=False) + if jl_isa(value, "AbstractDict"): + return dict(value) + if jl_isa(value, "AbstractSet"): + return set(value) + return value
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..5e36151 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,274 @@ + + + + + + + + + Overview: module code | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/_sources/index.md.txt b/_sources/index.md.txt new file mode 100644 index 0000000..bce41cf --- /dev/null +++ b/_sources/index.md.txt @@ -0,0 +1,249 @@ +# Integrated Energy System Optimization + + + +:::{toctree} +:hidden: +:caption: IESopt +:maxdepth: 2 + +Getting started +::: + +:::{toctree} +:hidden: +:caption: Tutorials +:maxdepth: 2 + +notebooks/first_model.ipynb +tocs/tutorials_extracting_results.md +tocs/tutorials_templates.md +pages/tutorials/addons.md +::: + + + +:::{toctree} +:hidden: +:caption: User guides + +pages/user_guides/index.md +pages/user_guides/toc_general.md +pages/user_guides/toc_qna.md +::: + +:::{toctree} +:hidden: +:caption: Manual + +pages/manual/yaml/index.md +pages/manual/python/index.md +pages/manual/julia/index.md +::: + +:::{toctree} +:hidden: +:caption: References + +pages/references/publications.md +pages/references/projects.md +::: + +:::{toctree} +:hidden: +:caption: Developer documentation + +pages/dev/general.md +pages/dev/core.md +pages/dev/updating.md +::: + + + +:::{admonition} _IESopt -- an Integrated Energy System Optimization framework._ +:class: tip + +IESopt is developed and maintained at the Center for Energy at AIT Austrian Institute of Technology GmbH. The framework isdesigned to support the optimization of energy systems that are characterized by a high degree of integration between different energy carriers and sectors. It focuses on offering a modular and adaptable tool for modelers, that does not compromise on performance, while still being user-friendly. This is enabled by reducing energy system assets to abstract building blocks, that are supported by specialized implementation, and can be combined into complex systems without the need of a detailed understanding of mathematical modeling or proficiency in any coding-language. +::: + +:::{caution} +The documentation is currently being put together based on cleaned parts of the internal docs. Until this is finished, this documentation may contains some placeholders. +::: + +## Overview + +This overview of `iesopt`'s documentation [^diataxis] will help you know where to find what you are looking for. + +### Getting started + +1. The {ref}`Installation` section explains how to quickly install and set up `iesopt`. +2. If you are new, you can then work through {ref}`A first model`, which will guide you through all the basics you need +to now. + +### Using this documentation + +For anything beyond {Getting started}`Getting started`, the following provides a high-level overview of the remaining +documentation that can be helpful when creating your own models: + +1. **Tutorials** will help you learn how to apply `iesopt`'s various main functionalities, to solve energy +system optimization models. Start here if you are new and have completed the {ref}`A first model` initial tutorial. +2. **User guides** provide various concise how-to guides, that help you accomplish a certain task, correctly +and safely. Consult these to remind yourself _how to do X_. +3. **Reference** contains technical reference for `IESopt.jl` core components, the YAML syntax, APIs, and more +internal details. It assumes that you already have a basic understanding of the core concepts of `iesopt`. +4. **Developer documentation** can be consulted for tips on how to improve `iesopt`, its +documentation, or other useful information related to developing `iesopt`. If you are only _using_ `iesopt` to develop +your own tools / projects, this will not be necessary to check at all. + +If you are up- or downgrading `iesopt`, head over to the [Releases Page](https://github.com/ait-energy/iesopt/releases/) +that provides you with information on what changed between versions. + +### Different projects + +The following projects / repositories are part of _"IESopt"_: + +- [`IESopt.jl`](https://github.com/ait-energy/IESopt.jl), the Julia-based core model powering all of IESopt's capabilities. +- [`iesopt`](https://github.com/ait-energy/iesopt), the Python interface (which you are currently viewing), which +enables a fast and simple application of `IESopt.jl`, without the need to know any Julia, or how to set it up. It further +provides different quality-of-life features, and embeds the model into a more conventional object-oriented style, that +you may be more used to - compared to the way Julia works. +- [`IESoptLib.jl`](https://github.com/ait-energy/IESoptLib.jl), the library of various assets related to IESopt. You can +find examples, as well as pre-defined templates and addons here. The library is automatically loaded for you. + +## Installation + +### Setting up an environment + +:::{note} +Skip this step if you want to install `iesopt` into an existing environment and directly continue directly continue with +[Installing `iesopt`](#installing-iesopt). +::: + +This assumes that you have a working `conda` executable installed on your system, e.g., after installing [Miniconda](https://docs.anaconda.com/miniconda/). +If you added the binary paths to your `PATH` environment variable, you should be able to execute the following steps in +every terminal (e.g., within [VSCode](https://code.visualstudio.com/)), otherwise make sure to use a proper shell - most +likely you _Anaconda Prompt_. + +First we create a new environment using (make sure to replace `yourenvname` by a fitting name) + +```bash +conda create -n yourenvname python=3.12 -y +conda activate yourenvname +``` + +Your terminal should now print the name of your environment in each new line, similar to + +```bash +(yourenvname) user@PCNAME:~/your/current/path$ +``` + +Next, we install [Poetry](https://python-poetry.org/) by executing + +```bash +pip install poetry +``` + +and use it to create a new basic environment by executing + +```bash +poetry init -n +``` + +Now you should see a new `pyproject.toml` file inside your folder, and are ready to [install `iesopt`](Installing `iesopt`). + +:::{admonition} Learning more about managing dependencies with Poetry +:class: tip + +Checkout the great tutorial ["Dependency Management With Python Poetry"](https://realpython.com/dependency-management-python-poetry/) +to learn more about all of this, or consult the [Basic usage](https://python-poetry.org/docs/basic-usage/) section of +the Poetry documentation. +::: + +### Installing `iesopt` + +This assumes that you have a working environment, that has Poetry installed. It should however work similarly using +`conda install` or `pip install` instead. + +You can install `iesopt` by executing + +```bash +poetry add iesopt +``` + +And that's it... you are done! + +#### Precompiling + +Julia, compared to Python as you are probably used to it, _compiles_ code [^compiling] just before it executes it. This, +coupled with the fact that we - until now - did not fully initialize our Julia environment, may lead to your first time +using `iesopt` taking a long (!) time. + +To "prevent" this, we can do a lot of the heavy lifting right here and now, by starting Python. You can do this by just +executing `python` in the terminal that you used to set up everything, like so + +```bash +(yourenvname) user@PCNAME:~/your/current/path$ python +``` + +which should result in an info message similar to this one: + +```text +Python 3.11.9 (main, Apr 19 2024, 16:48:06) [GCC 11.2.0] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> +``` + +Then just run + +```python +import iesopt +``` + +You will see some messages like `INFO:iesopt:Setting up Julia ...`, and most likely a lot of other output related to the +instantiation of a Julia environment. This may take a few minutes, but should end with lines that print + +```text +INFO:iesopt:Julia setup successful +INFO:iesopt:Importing Julia module `IESoptLib` +INFO:iesopt:Importing Julia module `IESopt` +INFO:iesopt:Importing Julia module `JuMP` +``` + +and are followed by a welcome message that documents the current version of IESopt that you are using. After that, you +are ready to start using `iesopt`. + +:::{admonition} Reducing overhead +:class: hint + +The next time that you launch `iesopt` by using `import iesopt` inside your current environment will be considerably +faster. Nonetheless, every new launch comes with certain compilation-related overheads. The best way to prevent this, is +making use of an interactive / REPL-based style of development. +::: + + + +## Citing IESopt + +If you find IESopt useful in your work, and are intend to publish or document your modeling, we kindly request that you +include the following citation: + +- **Style: APA7** + > Strömer, S., Schwabeneder, D., & contributors. (2021-2024). _IESopt: Integrated Energy System Optimization_ [Software]. AIT Austrian Institute of Technology GmbH. [https://github.com/ait-energy/IESopt](https://github.com/ait-energy/iesopt) +- **Style: IEEE** + > [1] S. Strömer, D. Schwabeneder, and contributors, _"IESopt: Integrated Energy System Optimization,"_ AIT Austrian Institute of Technology GmbH, 2021-2024. [Online]. Available: [https://github.com/ait-energy/IESopt](https://github.com/ait-energy/iesopt) +- **BibTeX:** + ```bibtex + @misc{iesopt, + author = {Strömer, Stefan and Schwabeneder, Daniel and contributors}, + title = {{IES}opt: Integrated Energy System Optimization}, + organization = {AIT Austrian Institute of Technology GmbH}, + url = {https://github.com/ait-energy/iesopt}, + type = {Software}, + year = {2021-2024}, + } + ``` + +[^diataxis]: The structure of the documentation follows the [Diátaxis Framework](https://diataxis.fr), especially +related to [The difference between a tutorial and how-to guide](https://diataxis.fr/tutorials-how-to). +[^compiling]: If you are unsure what "compiling" actually means, you possibly could benefit from checking [differences between Julia and other languages](https://docs.julialang.org/en/v1/manual/noteworthy-differences/) (if you already know another programming language), or looking at [this discourse post](https://discourse.julialang.org/t/so-does-julia-compile-or-interpret/56073/2), +or even read more about [compilers](https://en.wikipedia.org/wiki/Compiler). diff --git a/_sources/notebooks/custom_results_1.ipynb.txt b/_sources/notebooks/custom_results_1.ipynb.txt new file mode 100644 index 0000000..642743e --- /dev/null +++ b/_sources/notebooks/custom_results_1.ipynb.txt @@ -0,0 +1,1093 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Extracting results: Part I\n", + "\n", + "This tutorial showcases how results can be extracted, including how user defined templates are able to create new result\n", + "calculations." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [ + "remove-stdout", + "remove-stderr" + ] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:iesopt:Setting up Julia ...\n", + "[ Info: Now using Revise\n", + "INFO:iesopt:Julia setup successful\n", + "INFO:iesopt:Importing Julia module `IESoptLib`\n", + "INFO:iesopt:Importing Julia module `IESopt`\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Detected IPython. Loading juliacall extension. See https://juliapy.github.io/PythonCall.jl/stable/compat/#IPython\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:iesopt:Importing Julia module `JuMP`\n", + "INFO:iesopt:╔════════════════════════════════════════════════════════════════════════╗\n", + "INFO:iesopt:║ IESopt «Integrated Energy System Optimization» ║\n", + "INFO:iesopt:╟────────────────────────────────────────────────────────────────────────╢\n", + "INFO:iesopt:║ ╭────────────────────────────────────────────────────────────────╮ ║\n", + "INFO:iesopt:║ ├ authors: Stefan Strömer, Daniel Schwabeneder, and contributors │ ║\n", + "INFO:iesopt:║ ├ © 2021: AIT Austrian Institute of Technology GmbH │ ║\n", + "INFO:iesopt:║ ├ docs: https://ait-energy.github.io/iesopt │ ║\n", + "INFO:iesopt:║ ├ version: ┐ │ ║\n", + "INFO:iesopt:║ │ ├─{ py :: 1.0.0a3 } │ ║\n", + "INFO:iesopt:║ │ ├─{ jl :: 1.0.3 } │ ║\n", + "INFO:iesopt:║ │ └─{ lib :: 0.2.0 } │ ║\n", + "INFO:iesopt:║ ╰────────────────────────────────────────────────────────────────╯ ║\n", + "INFO:iesopt:╚════════════════════════════════════════════════════════════════════════╝\n" + ] + } + ], + "source": [ + "import iesopt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:iesopt:Data folder for examples already exists; NOT copying ANY contents\n", + "INFO:iesopt:Creating example ('48_custom_results') at: 'ex_custom_results/config.iesopt.yaml'\n", + "INFO:iesopt:Set write permissions for example ('ex_custom_results/config.iesopt.yaml'), and data folder ('ex_custom_results/files')\n" + ] + } + ], + "source": [ + "config_file = iesopt.make_example(\n", + " \"48_custom_results\", dst_dir=\"ex_custom_results\", dst_name=\"config\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "model = iesopt.run(config_file, verbosity=False)\n", + "\n", + "assert model.status == iesopt.ModelStatus.OPTIMAL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Accessing model results: Objectives\n", + "\n", + "The objective of your model - after a successful solve - can be extracted using:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "981.1745152354571" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.objective_value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It may however be the case, that you have registered multiple objective functions (which can be used for multi-objective\n", + "algorithms, or even be useful just for analysis purposes). You can get their value by their name. The default objective\n", + "is always called `total_cost` and is the only one guaranteed to always exist. We can check which objectives are registered:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['total_cost'])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.objectives.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And get the value of `total_cost` (which should match the one obtained from `model.objective_value` for this example):" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "981.1745152354571" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.objectives[\"total_cost\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Accessing model results: Variables\n", + "\n", + "Three different ways to access the results of our custom storage component `storage`:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Direct access" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3.6565097 , 3.47368421, -0.99722992, 0.94736842, 0. ,\n", + " 0. , 0. , -5.6 , 0. , -0.1 ,\n", + " -1.50221607, 3.47368421, 2.63157895, 0.31578947, 0.42105263,\n", + " -3.46149584, 0. , 0.84210526, -2.4 , -4. ,\n", + " 3.57894737, 3.47368421, 0.94736842, 0.52631579])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.components[\"storage\"].res.setpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Accessing dual information:\n", + "> ```python\n", + "> model.results.components[\"grid\"].con.nodalbalance__dual\n", + "> ```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Access using `get(...)`" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3.6565097 , 3.47368421, -0.99722992, 0.94736842, 0. ,\n", + " 0. , 0. , -5.6 , 0. , -0.1 ,\n", + " -1.50221607, 3.47368421, 2.63157895, 0.31578947, 0.42105263,\n", + " -3.46149584, 0. , 0.84210526, -2.4 , -4. ,\n", + " 3.57894737, 3.47368421, 0.94736842, 0.52631579])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.get(\"component\", \"storage\", \"res\", \"setpoint\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Accessing dual information:\n", + "> ```python\n", + "> model.results.get(\"component\", \"grid\", \"con\", \"nodalbalance\", mode=\"dual\")\n", + "> ```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Collective results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Using `to_dict(...)`" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3.6565097 , 3.47368421, -0.99722992, 0.94736842, 0. ,\n", + " 0. , 0. , -5.6 , 0. , -0.1 ,\n", + " -1.50221607, 3.47368421, 2.63157895, 0.31578947, 0.42105263,\n", + " -3.46149584, 0. , 0.84210526, -2.4 , -4. ,\n", + " 3.57894737, 3.47368421, 0.94736842, 0.52631579])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results = model.results.to_dict()\n", + "\n", + "results[(\"storage\", \"res\", \"setpoint\")]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Accessing dual information:\n", + "> ```python\n", + "> results[(\"grid\", \"con\", \"nodalbalance__dual\")]\n", + "> ```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Note: You can filter the results returned by `to_dict(...)` in exactly the same way as when using `to_pandas(...)` (\n", + "> see below). However, this is uncommon to be useful, since you most likely want to work with tabular data anyways when\n", + "> using the filter function, which is why we skip it here." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Using `to_pandas(...)`" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3.6565097 , 3.47368421, -0.99722992, 0.94736842, 0. ,\n", + " 0. , 0. , -5.6 , 0. , -0.1 ,\n", + " -1.50221607, 3.47368421, 2.63157895, 0.31578947, 0.42105263,\n", + " -3.46149584, 0. , 0.84210526, -2.4 , -4. ,\n", + " 3.57894737, 3.47368421, 0.94736842, 0.52631579])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = model.results.to_pandas()\n", + "\n", + "df.loc[\n", + " (\n", + " (df[\"component\"] == \"storage\")\n", + " & (df[\"fieldtype\"] == \"res\")\n", + " & (df[\"field\"] == \"setpoint\")\n", + " & (df[\"mode\"] == \"primal\")\n", + " ),\n", + " \"value\",\n", + "].values" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "t1 -3.656510\n", + "t2 3.473684\n", + "t3 -0.997230\n", + "t4 0.947368\n", + "t5 0.000000\n", + "Name: (storage, res, setpoint), dtype: float64" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "series = model.results.to_pandas(\n", + " lambda c, t, f: c == \"storage\" and t == \"res\" and f == \"setpoint\"\n", + ")\n", + "series.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We could actually only filter for the component (`c == \"storage\"`), since this is the only result that it creates." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "t1 -3.656510\n", + "t2 3.473684\n", + "t3 -0.997230\n", + "t4 0.947368\n", + "t5 0.000000\n", + "Name: (storage, res, setpoint), dtype: float64" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "series = model.results.to_pandas(lambda c, t, f: c == \"storage\")\n", + "series.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This may however be dangerous, since a similar call\n", + "\n", + "```python\n", + "model.results.to_pandas(lambda c,t,f: c == \"grid\")\n", + "```\n", + "\n", + "would then suddenly return a `pd.DataFrame`, since it contains two different results (try it out!) linked to the\n", + "component `grid`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Accessing dual information (part I):\n", + ">\n", + "> ```python\n", + "> model.results.to_pandas(lambda c,t,f: c == \"grid\" and t == \"con\")\n", + "> ```\n", + ">\n", + "> This works, since the model only contains a single result linked to constraints of the component `grid`. However, this\n", + "> may again be dangerous, which is why you could instead make use of something like\n", + ">\n", + "> ```python\n", + "> df = model.results.to_pandas()\n", + "> df[df[\"mode\"] == \"dual\"]\n", + "> ```\n", + ">\n", + "> _Note that this extracts ALL dual results, not only those for the component used above, but again `to_pandas(...)` is\n", + "> mostly there to extract more than one result at the same time (we cover \"Which way should I use below?\")._" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "You may now wonder - since it all looks the same - what `to_pandas(...)` could be useful for. It's main usage is\n", + "extracting more than one result in one call:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
storage.storagedemandgridgenerator
expexpexpexp
injectionvalueinjectionout_electricity
t13.4736844.04.440892e-167.65651
t2-3.4736844.0-1.110223e-160.70000
t30.9473684.02.220446e-164.99723
t4-0.9473684.00.000000e+003.10000
t50.0000004.00.000000e+004.00000
\n", + "
" + ], + "text/plain": [ + " storage.storage demand grid generator\n", + " exp exp exp exp\n", + " injection value injection out_electricity\n", + "t1 3.473684 4.0 4.440892e-16 7.65651\n", + "t2 -3.473684 4.0 -1.110223e-16 0.70000\n", + "t3 0.947368 4.0 2.220446e-16 4.99723\n", + "t4 -0.947368 4.0 0.000000e+00 3.10000\n", + "t5 0.000000 4.0 0.000000e+00 4.00000" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = model.results.to_pandas(field_types=\"exp\", orientation=\"wide\")\n", + "df.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Which result extraction should I use?\n", + "\n", + "As a basic guide you can use the following logic to decide how to extract results. Are you:\n", + "\n", + "1. Looking for a single result of a component? Extract it similar to `model.results.components[\"storage\"].res.setpoint`.\n", + "2. Looking for multiple results of a component (e.g., all objective terms created by a single `Unit`), or similar results of multiple components (e.g., electricity generation of all generators)? Make use of `to_pandas(...)`, applying a specific filter, and either using `orientation = \"long\"` (the default), or `orientation = \"wide\"`.\n", + "\n", + "---\n", + "\n", + "**Advanced usage:** _Looking for a single result of a component at a time, but doing it repeatedly for a single run (= extracting a single result from component `A`, then one from component `B`, and so on)?_\n", + "\n", + "Then use the `to_dict(...)` function and then extract your results similar to `model.to_dict()[(\"storage\", \"res\", \"setpoint\")]`. Compared to (1.) this has the advantage of caching results during the first call to `to_dict(...)`, and being able to only extract specific results if correctly filtered. **Pay attention to why, when, and how you use this, since improper usage may be way slower than directly accessing your results as explained above." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading results from file\n", + "\n", + "The example that we have used until now, does not write any results to a file. To load some, we therefore need to enable\n", + "this and then re-solve the model.\n", + "\n", + "For that, edit the top-level config file, and change\n", + "\n", + "```yaml\n", + "config:\n", + " # ...\n", + " results:\n", + " enabled: true\n", + " memory_only: false\n", + "```\n", + "\n", + "to\n", + "\n", + "```yaml\n", + "config:\n", + " # ...\n", + " results:\n", + " enabled: true\n", + " memory_only: true # <-- change here!\n", + "```\n", + "\n", + "Now run the model once more to create the result output file" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [ + "remove-output" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "An IESopt model:\n", + "\tname: CustomResults\n", + "\tsolver: HiGHS\n", + "\t\n", + "\t96 variables, 170 constraints\n", + "\tstatus: OPTIMAL" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model = iesopt.run(\"ex_custom_results/config.iesopt.yaml\", verbosity=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will create an IESopt result file `SomeScenario.iesopt.result.jld2` inside the\n", + "`ex_custom_results/out/CustomResults/` folder, which contains all results. This can be used to analyse results at a\n", + "later time. To prevent losing information it tries to extract _**all**_ results - which may be time intensive, but\n", + "ensures that you do not forget to extract something, to only realise later that you miss it.\n", + "\n", + "We can now load this file using" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "results = iesopt.Results(\n", + " file=\"ex_custom_results/out/CustomResults/SomeScenario.iesopt.result.jld2\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, you can use exactly the same code that we have already walked through, c.f. {ref}`Accessing model results: Variables`, just by\n", + "replacing every access to `model.results` by `results`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### File results: Direct access" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3.6565097 , 3.47368421, -0.99722992, 0.94736842, 0. ,\n", + " 0. , 0. , -5.6 , 0. , -0.1 ,\n", + " -1.50221607, 3.47368421, 2.63157895, 0.31578947, 0.42105263,\n", + " -3.46149584, 0. , 0.84210526, -2.4 , -4. ,\n", + " 3.57894737, 3.47368421, 0.94736842, 0.52631579])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# instead of\n", + "# `model.results.components[\"storage\"].res.setpoint`\n", + "# we now use:\n", + "\n", + "results.components[\"storage\"].res.setpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### File results: Access using `get(...)`" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3.6565097 , 3.47368421, -0.99722992, 0.94736842, 0. ,\n", + " 0. , 0. , -5.6 , 0. , -0.1 ,\n", + " -1.50221607, 3.47368421, 2.63157895, 0.31578947, 0.42105263,\n", + " -3.46149584, 0. , 0.84210526, -2.4 , -4. ,\n", + " 3.57894737, 3.47368421, 0.94736842, 0.52631579])" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results.get(\"component\", \"storage\", \"res\", \"setpoint\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### File results: Collective results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### File results: Using `to_dict(...)`" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3.6565097 , 3.47368421, -0.99722992, 0.94736842, 0. ,\n", + " 0. , 0. , -5.6 , 0. , -0.1 ,\n", + " -1.50221607, 3.47368421, 2.63157895, 0.31578947, 0.42105263,\n", + " -3.46149584, 0. , 0.84210526, -2.4 , -4. ,\n", + " 3.57894737, 3.47368421, 0.94736842, 0.52631579])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_dict = results.to_dict()\n", + "result_dict[(\"storage\", \"res\", \"setpoint\")]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### File results: Using `to_pandas(...)`" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
storage.storagedemandgridgenerator
expexpexpexp
injectionvalueinjectionout_electricity
t13.4736844.04.440892e-167.65651
t2-3.4736844.0-1.110223e-160.70000
t30.9473684.02.220446e-164.99723
t4-0.9473684.00.000000e+003.10000
t50.0000004.00.000000e+004.00000
\n", + "
" + ], + "text/plain": [ + " storage.storage demand grid generator\n", + " exp exp exp exp\n", + " injection value injection out_electricity\n", + "t1 3.473684 4.0 4.440892e-16 7.65651\n", + "t2 -3.473684 4.0 -1.110223e-16 0.70000\n", + "t3 0.947368 4.0 2.220446e-16 4.99723\n", + "t4 -0.947368 4.0 0.000000e+00 3.10000\n", + "t5 0.000000 4.0 0.000000e+00 4.00000" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = results.to_pandas(field_types=\"exp\", orientation=\"wide\")\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calling into Julia\n", + "\n", + "You should probably never need the following, but you can also manually access the `results` data `Struct` inside the\n", + "Julia model to extract some results. For an optimized model (not for results loaded from a file!), this could be done\n", + "using" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "my_result = (\n", + " model.core.ext[iesopt.Symbol(\"iesopt\")].results.components[\"storage\"].res.setpoint\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Observe that" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "juliacall.VectorValue" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(my_result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "which shows that the other modes of result extraction take care of a proper Julia-to-Python conversion for you already.\n", + "Further, you can then" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5-element view(::Vector{Float64}, 1:1:5) with eltype Float64:\n", + " -3.656509695290859\n", + " 3.473684210526316\n", + " -0.997229916897507\n", + " 0.9473684210526315\n", + " 0.0" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "my_result[:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "which, as you see, returns an actual `view` into the `Vector{Float64}`, indexed using the Julia range `1:1:5` (given by\n", + "the Python range `:5`). But" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5-element view(::Vector{Float64}, 1:1:5) with eltype Float64:\n", + " -3.656509695290859\n", + " 3.473684210526316\n", + " -0.997229916897507\n", + " 0.9473684210526315\n", + " 0.0" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "my_result[0:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "makes it clear, that the wrapper we use automatically translates between 0-based (Python) and 1-based (Julia) indexing,\n", + "which may become confusing and error-prone when thinking about a Julia `Vector` but accessing the first entry using" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-3.656509695290859" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "my_result[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Accessing dual information (part I):\n", + ">\n", + "> ```python\n", + "> model.core.ext[iesopt.Symbol(\"iesopt\")].results.components[\"grid\"].con.nodalbalance__dual\n", + "> ```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "iesopt", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/_sources/notebooks/custom_results_2.ipynb.txt b/_sources/notebooks/custom_results_2.ipynb.txt new file mode 100644 index 0000000..118516c --- /dev/null +++ b/_sources/notebooks/custom_results_2.ipynb.txt @@ -0,0 +1,960 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Extracting results: Part II\n", + "\n", + "This tutorial showcases more ways to handle and analyse results. Make sure that you've read the first part!\n", + "\n", + "We make use of the same example, and first just load and run it." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [ + "remove-stdout", + "remove-stderr" + ] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:iesopt:Setting up Julia ...\n", + "[ Info: Revise and Infiltrator loaded\n", + "INFO:iesopt:Julia setup successful\n", + "INFO:iesopt:Importing Julia module `IESoptLib`\n", + "INFO:iesopt:Importing Julia module `IESopt`\n", + "INFO:iesopt:Importing Julia module `JuMP`\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Detected IPython. Loading juliacall extension. See https://juliapy.github.io/PythonCall.jl/stable/compat/#IPython\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:iesopt:╔════════════════════════════════════════════════════════════════════════╗\n", + "INFO:iesopt:║ IESopt «Integrated Energy System Optimization» ║\n", + "INFO:iesopt:╟────────────────────────────────────────────────────────────────────────╢\n", + "INFO:iesopt:║ ╭────────────────────────────────────────────────────────────────╮ ║\n", + "INFO:iesopt:║ ├ authors: Stefan Strömer, Daniel Schwabeneder, and contributors │ ║\n", + "INFO:iesopt:║ ├ © 2021: AIT Austrian Institute of Technology GmbH │ ║\n", + "INFO:iesopt:║ ├ docs: https://ait-energy.github.io/iesopt │ ║\n", + "INFO:iesopt:║ ├ version: ┐ │ ║\n", + "INFO:iesopt:║ │ ├─{ py :: 1.0.1 } │ ║\n", + "INFO:iesopt:║ │ ├─{ jl :: 1.0.3 } │ ║\n", + "INFO:iesopt:║ │ └─{ lib :: 0.2.0 } │ ║\n", + "INFO:iesopt:║ ╰────────────────────────────────────────────────────────────────╯ ║\n", + "INFO:iesopt:╚════════════════════════════════════════════════════════════════════════╝\n", + "INFO:iesopt:Data folder for examples already exists; NOT copying ANY contents\n", + "INFO:iesopt:Creating example ('48_custom_results') at: 'ex_custom_results/config.iesopt.yaml'\n", + "INFO:iesopt:Set write permissions for example ('ex_custom_results/config.iesopt.yaml'), and data folder ('ex_custom_results/files')\n" + ] + } + ], + "source": [ + "import iesopt\n", + "\n", + "config_file = iesopt.make_example(\n", + " \"48_custom_results\", dst_dir=\"ex_custom_results\", dst_name=\"config\"\n", + ")\n", + "model = iesopt.run(config_file, verbosity=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Remember that the most versatile way (for most tasks) is one that you already know: `df = model.results.to_pandas()`. This will give you **all** results as a single `pandas.DataFrame` that you can then filter, resample, analyse, etc. in any way you wish, with all functions that you are used to from `pandas`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What should I look at?\n", + "\n", + "If you are unsure which results are even available, you can make use of `query_available_results(...)` to find out:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('var', 'state'), ('exp', 'injection')]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.query_available_results(\"storage.storage\", mode=\"primal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This shows you that two results exist for the component `storage.storage`: `var.state` (the level of the state of this Node) and `exp.injection` (the expression holding the injection into the Node).\n", + "\n", + "> More results are available when using `mode=\"dual\"`, or `mode=\"both\"` - try it out!\n", + "\n", + "If you'd be interested in seeing results for the first, then you could (check part I of this tutorial for different ways to access this) do:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0. , 4.42105263, 0.94736842, 0.94736842, -0. ,\n", + " -0. , -0. , -0. , 3.19552632, 6.80552632,\n", + " 6.90052632, 10.13052632, 6.65684211, 4.02526316, 3.70947368,\n", + " 3.28842105, 3.28842105, 3.28842105, 2.44631579, 4.72631579,\n", + " 8.52631579, 4.94736842, 1.47368421, 0.52631579])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.get(\"component\", \"storage.storage\", \"var\", \"state\", mode=\"primal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, one hint: `query_available_results(...)` treats its first parameter as regular expression, so you can use any regex you want to look up more than one component at the same time! [regex.101](https://regex101.com/) is a good place to test your [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) that you wrote using any LLM (they are quite okay at that!)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Looking at (a) specific component(s)\n", + "\n", + "If you are now interested in seeing all results for a single component, the DataFrame returned by `to_pandas(...)` can get overwhelming quickly. That's what `overview(...)` can be used for." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Temporal results\n", + "\n", + "Observe the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
storage.chargingstorage.discharging
convarconvar
flow_lbflowflow_lbflow
dualdualprimaldualdualprimal
t1-0.0000000.04.65374-1.0263160.0-0.000000
t2-1.0803320.0-0.00000-0.0000000.03.473684
t30.0000000.0-0.00000-1.0263160.0-0.000000
t4-1.0803320.0-0.00000-0.0000000.00.947368
t50.0000000.0-0.00000-1.0263160.0-0.000000
\n", + "
" + ], + "text/plain": [ + " storage.charging storage.discharging \n", + " con var con var \n", + " flow_lb flow flow_lb flow \n", + " dual dual primal dual dual primal\n", + "t1 -0.000000 0.0 4.65374 -1.026316 0.0 -0.000000\n", + "t2 -1.080332 0.0 -0.00000 -0.000000 0.0 3.473684\n", + "t3 0.000000 0.0 -0.00000 -1.026316 0.0 -0.000000\n", + "t4 -1.080332 0.0 -0.00000 -0.000000 0.0 0.947368\n", + "t5 0.000000 0.0 -0.00000 -1.026316 0.0 -0.000000" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.overview(\"storage.*ing\", temporal=True, mode=\"both\").head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see:\n", + "\n", + "- The results are automatically given in wide format.\n", + "- The regular expression `\"storage.*ing\"` matched all components that (1) _start with \"storage\"_, but also (2) _end with \"ing\"_. Try changing that to `\"storage.\"` and see what other components get matched too.\n", + "- Since we passed `mode=\"both\"`, it returns both primal and dual results. Try passing `\"primal\"` or `\"dual\"` instead.\n", + "\n", + "Since we set `temporal=True`, we got results that are available for every Snapshot." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Non-temporal results\n", + "\n", + "Let's see what happens if we instead do:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "storage.storage con last_state_lb dual 0.000000\n", + " last_state_ub dual -0.000000\n", + "generator obj marginal_cost primal 981.174515\n", + "dtype: float64" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.results.overview(\".*\", temporal=False, mode=\"both\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since all results are now non-temporal, we get back a `pandas.Series` instead.\n", + "\n", + "> Can you explain why this now contains the dual results of constraints constructed by `storage.storage`? The documentation of the core component Node may help... But - in any case, feel free to ask stuff like this (we are very happy to answer this).\n", + "\n", + "But, lets look at an example that contains more interesting results for something like this. First we pull a different example and solve it" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:iesopt:Data folder for examples already exists; NOT copying ANY contents\n", + "INFO:iesopt:Creating example ('08_basic_investment') at: 'ex_custom_results/config.iesopt.yaml'\n", + "INFO:iesopt:Set write permissions for example ('ex_custom_results/config.iesopt.yaml'), and data folder ('ex_custom_results/files')\n" + ] + } + ], + "source": [ + "other_config = iesopt.make_example(\n", + " \"08_basic_investment\", dst_dir=\"ex_custom_results\", dst_name=\"config\"\n", + ")\n", + "other_model = iesopt.run(other_config, verbosity=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and then we take a look at the primal, non-temporal results:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "build_pipeline var value primal 0.750000\n", + " obj value primal 750.000000\n", + "build_gas var value primal 0.607143\n", + " obj value primal 303.571429\n", + "plant_gas obj marginal_cost primal 917.000000\n", + "build_storage var value primal 0.450714\n", + " obj value primal 45.071429\n", + "dtype: float64" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "other_model.results.overview(\".*\", temporal=False, mode=\"primal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This shows us the resulting values of all investment decisions in the model (e.g., `(build_pipeline, var, value)`), there associated costs (e.g., `(build_pipeline, obj, value)`), as well as the objective contribution of the marginal costs induced by operating `plant_gas`.\n", + "\n", + "To only see investment decisions, you could either take this series and further filter it, for example by doing\n", + "\n", + "```python\n", + "sr = other_model.results.overview(\".*\", temporal=False, mode=\"primal\")\n", + "\n", + "sr[sr.index.get_level_values(1) == \"var\"]\n", + "```\n", + "\n", + "which (un-)fortunately also hides the `obj` entries of the investment decisions.\n", + "\n", + "Or you could stick to an \"intelligent\" naming convention of your components (for example like we did in the example, naming all investment decisions `build_***`) and make use of the regular expression support of `overview(...)` by instead doing:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "build_pipeline var value primal 0.750000\n", + " obj value primal 750.000000\n", + "build_gas var value primal 0.607143\n", + " obj value primal 303.571429\n", + "build_storage var value primal 0.450714\n", + " obj value primal 45.071429\n", + "dtype: float64" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "other_model.results.overview(\"^build_.*$\", temporal=False, mode=\"primal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Check out [regex101.com/r/GzgzG2/1](https://regex101.com/r/GzgzG2/1), and read through the \"Explanation\" section, to understand what `^build_.*$` actually achieves. Note: Using the intuitive way, `other_model.results.overview(\"build_\", ...)`, would have worked (here) as well. The difference is minimal and subtle, but ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering components\n", + "\n", + "If you are not familiar with regular expressions, don't worry. The most commonly used \"filters\" work as expected. Let's switch back to temporal results and stick with the \"new\" example that we have just used." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Selecting a specific component" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
pipeline
var
flow
primal
t1-0.0
t2-0.0
t3-0.0
t4-0.0
t5-0.0
\n", + "
" + ], + "text/plain": [ + " pipeline\n", + " var\n", + " flow\n", + " primal\n", + "t1 -0.0\n", + "t2 -0.0\n", + "t3 -0.0\n", + "t4 -0.0\n", + "t5 -0.0" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "other_model.results.overview(\"pipeline\", temporal=True, mode=\"primal\").head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Selecting all components containing \"plant\"" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
plant_gasplant_solar
expvarexpvar
out_electricityconversionout_electricityconversion
primalprimalprimalprimal
t10.0000000.0000000.00-0.00
t20.0000000.0000000.00-0.00
t30.0342860.0342860.020.02
t40.0000000.0000000.070.07
t50.0000000.0000000.160.16
\n", + "
" + ], + "text/plain": [ + " plant_gas plant_solar \n", + " exp var exp var\n", + " out_electricity conversion out_electricity conversion\n", + " primal primal primal primal\n", + "t1 0.000000 0.000000 0.00 -0.00\n", + "t2 0.000000 0.000000 0.00 -0.00\n", + "t3 0.034286 0.034286 0.02 0.02\n", + "t4 0.000000 0.000000 0.07 0.07\n", + "t5 0.000000 0.000000 0.16 0.16" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "other_model.results.overview(\"plant\", temporal=True, mode=\"primal\").head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Selecting specific components" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using `|`, you can select multiple specific components.\n", + "\n", + "> Note: You can use that multiple times. Try out passing `\"h2_south|h2_north|demand\"`! Can you explain why `h2_south` and `h2_north` return different types of results? If not - go ahead, ask us!" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
elecelectrolysish2_south
expexpvarexp
injectionin_electricityout_h2conversioninjection
primalprimalprimalprimalprimal
t10.00.0000000.000000-0.0000000.0
t20.00.0000000.000000-0.0000000.0
t30.00.0542860.0271430.0271430.0
t40.00.0700000.0350000.0350000.0
t50.00.1600000.0800000.0800000.0
\n", + "
" + ], + "text/plain": [ + " elec electrolysis h2_south\n", + " exp exp var exp\n", + " injection in_electricity out_h2 conversion injection\n", + " primal primal primal primal primal\n", + "t1 0.0 0.000000 0.000000 -0.000000 0.0\n", + "t2 0.0 0.000000 0.000000 -0.000000 0.0\n", + "t3 0.0 0.054286 0.027143 0.027143 0.0\n", + "t4 0.0 0.070000 0.035000 0.035000 0.0\n", + "t5 0.0 0.160000 0.080000 0.080000 0.0" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "other_model.results.overview(\"h2_south|elec\", temporal=True, mode=\"primal\").head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But wait ... we did not want to get results for `electrolysis`. That's the disadvantage of being able to search for components containing `plant`, as shown before: Since `electrolysis` contains `elec` it matches this too.\n", + "\n", + "Let's fix this. Remember that we used `^build_.*$` before, without it being clear what this achieves? Let's see ..." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
elech2_south
expexp
injectioninjection
primalprimal
t10.00.0
t20.00.0
t30.00.0
t40.00.0
t50.00.0
\n", + "
" + ], + "text/plain": [ + " elec h2_south\n", + " exp exp\n", + " injection injection\n", + " primal primal\n", + "t1 0.0 0.0\n", + "t2 0.0 0.0\n", + "t3 0.0 0.0\n", + "t4 0.0 0.0\n", + "t5 0.0 0.0" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "other_model.results.overview(\"h2_south|^elec$\", temporal=True, mode=\"primal\").head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It works!\n", + "\n", + "_But why ... ?_\n", + "\n", + "As [regex101.com/r/GzgzG2/1](https://regex101.com/r/GzgzG2/1) explains:\n", + "\n", + "- `^` asserts position at start of a line\n", + "- `$` asserts position at the end of a line\n", + "\n", + "That means, instead of looking for any component that contains `elec`, we are looking for one that does not contain ANY characters before `elec` and also NONE after `elec`. In other words: It matches exactly `elec`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "iesopt", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/_sources/notebooks/first_model.ipynb.txt b/_sources/notebooks/first_model.ipynb.txt new file mode 100644 index 0000000..666eb96 --- /dev/null +++ b/_sources/notebooks/first_model.ipynb.txt @@ -0,0 +1,288 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# A first model\n", + "\n", + "After you have successfully installed IESopt, you can start to build your first model. In this tutorial, we will show you how to create a simple model, solve it, and extract some basic results.\n", + "\n", + "Let's start by creating a Python file, e.g. `main.py`, to hold the necessary code, and add the following lines:\n", + "\n", + "```python\n", + "import iesopt\n", + "\n", + "# Load and solve a model.\n", + "model = iesopt.run(\"my_first_model.iesopt.yaml\")\n", + "\n", + "print(\"Objective value:\", model.objective_value)\n", + "```\n", + "\n", + "Next we'll describe a simple model that we would like to solve and set various parameters. Create an empty file `my_first_model.iesopt.yaml` (later on multiple files can be combined to describe more complex models), that we can now use to actually describe the model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model configuration\n", + "\n", + "The first part of each `*.iesopt.yaml` file describes general configuration parameters. Add the following lines:\n", + "```yaml\n", + "config:\n", + " optimization:\n", + " problem_type: LP\n", + " snapshots:\n", + " count: 24\n", + "```\n", + "This tells IESopt to start building a model, while expecting all formulations to be representable as LP (so, adding binary\n", + "variables will cause an error). Further we specify how many time steps (called `Snapshot`) we'd like to use, here telling IESopt that we are looking to optimize a full day (by the default a `Snapshot`'s duration is one hour)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Energy carriers\n", + "\n", + "Since IESopt is a general purpose energy system model, it does not restrict you to a set of predefined types of energy, but\n", + "rather expects you to first define those. For our first model, we'll only care about electricity and gas, so we add\n", + "the following lines:\n", + "\n", + "```yaml\n", + "carriers:\n", + " electricity: {}\n", + " gas: {}\n", + "```\n", + "The `{}` represents an empty dictionary. Additional parameters related to the carrier could later be specified there." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model components\n", + "\n", + "Now that all general settings of the model are in place, we can actually start describing the model's structure:\n", + "\n", + "- A `photovoltaic` (a `Profile`) system is feeding in electricity (based on some external availability factor) into a local electricity grid (a `Node` called `elec_grid`)\n", + "- A simple `storage` is connected (via a `Connection`) to this grid, able to shift energy between time steps\n", + "- An endogenous `demand` (electricity) must be met at every time step\n", + "- Any uncovered demand (by PV or storage) can be satisfied using a `gasturbine` (a `Unit`), that draws gas from a `gas_grid`, that\n", + " needs to buy all used gas from a `gas_market`\n", + "\n", + "We now describe these seven components, starting with the electricity grid `Node`. Add the following lines to\n", + "`my_first_model.iesopt.yaml` - everything after a `#` is considered a comment by IESopt:\n", + "```yaml\n", + "components:\n", + " elec_grid: # the unique name of this component\n", + " type: Node # the type of this component\n", + " carrier: electricity # this (Node-specific) parameter fixes the carrier to be electricity\n", + "```\n", + "\n", + "> Head over to [this section of the docs](https://ait-energy.github.io/iesopt/pages/reference/yaml/) to read up on the different component types that are available.\n", + "\n", + "We add the other two `Node`s, making sure, that the `storage` is stateful (models a \"state of charge\"), and that the\n", + "`gas_grid` has the proper carrier. Make sure that you do get the correct indent, since all of the following lines still\n", + "belong to the overall `components:` definition:\n", + "\n", + "```yaml\n", + " gas_grid: # the unique name of this component\n", + " type: Node # the type of this component\n", + " carrier: gas # this (Node-specific) parameter fixes the carrier to be electricity\n", + "\n", + " storage:\n", + " type: Node\n", + " carrier: electricity\n", + " has_state: true # this allows this Node to have an \"internal state\"\n", + " state_lb: 0 # the state can not drop below 0\n", + " state_ub: 50 # a maximum of 50 electricity can be stored\n", + "```\n", + "\n", + "Two important things can be seen with the `storage`:\n", + "1. Values in IESopt do not carry an explicit unit (at the moment, this will be possible in the future). This means, that if\n", + " we consider electricity to be in kW/kWh in this model, we need to make sure that all settings are adjusted to match that.\n", + "2. We are implicitly using a default setting of a parameter that we did not specify: `state_cyclic` (check it out in the docs!) is set to `eq` per default, forcing the model to always end the optimization with as much \"charge\" in the storage as it started with in the first time step (however, how much that is, is left to the optimizer to decide).\n", + "\n", + "Now that we have all `Node`s in place, we can insert the only `Connection` by adding:\n", + "```yaml\n", + " conn:\n", + " type: Connection\n", + " node_from: elec_grid # energy flows from HERE\n", + " node_to: storage # to THERE\n", + " capacity: 15 # with a maximum capacity of +-15 units of electricity\n", + "```\n", + "\n", + "Notice that while we did specify the `Node`s that are connected by this `Connection`, no energy\n", + "carrier was explicitly set. This is due to the fact that `Connection`s infer the energy carrier and will automatically fail if they\n", + "connect two `Node`s with a different type of energy. The specified `capacity` therefore refers to 15 units of electricity\n", + "and constructs symmetric bounds on the flow (again, read up in the docs for other options and asymmetric bounds).\n", + "\n", + "Next, it's time to add all three `Profile`s to the model. A `Profile` allows for the \"creation\" or \"destruction\" of energy:\n", + "Normally, all energy needs to move through the model (possibly being transformed), but can not enter/leave the model. This\n", + "is where `Profile`s help, representing for example the cost of buying gas (`gas_market`) or a fixed demand that needs to\n", + "be covered (`demand`).\n", + "\n", + "We now add:\n", + "```yaml\n", + " demand:\n", + " type: Profile # the type is now \"Profile\"\n", + " carrier: electricity\n", + " value: 5 # this models a fixed demand of 5 units of electricity during every Snapshot\n", + " node_from: elec_grid # this tells MFC that this Profile draws energy from \"elec_grid\"\n", + "\n", + " # We can also set the \"value\" of a Profile to a time series, as can be seen:\n", + " photovoltaic:\n", + " type: Profile\n", + " carrier: electricity\n", + " value: [0,0,0,0,0,1,2,3,4,5,8,12,12,12,8,5,4,3,2,1,0,0,0,0]\n", + " node_to: elec_grid # now feeding INTO \"elec_grid\"\n", + "\n", + " gas_market:\n", + " type: Profile\n", + " carrier: gas\n", + " mode: create # this changes the mode from the default (\"fixed\") to \"create\"\n", + " cost: 100 # this specifies the \"cost of gas\"\n", + " node_to: gas_grid\n", + "```\n", + "\n", + "> Note on setting `Profile` values: While the value of a `Profile` can be set directly in the `*.iesopt.yaml` file, most of the time this will just result in a convoluted file. It's therefore possible to load external data files (in CSV format) and directly link to them using a simple `column@filename` syntax, that can be seen in other examples.\n", + "\n", + "While the first two `Profile`s should be mostly self-explanatory, the `gas_market` introduces a new concept: While\n", + "standard `Profile`s always consider a fixed value (a time series), some time series may not be exogenous, e.g. how\n", + "much gas is bought from the gas market. That's where the `mode: create` setting helps by defining a `Profile` that can\n", + "freely choose (as long as the value is >= 0) how much gas is being bought, but associates every unit of gas with a cost\n", + "that has to be \"paid\" (this is therefore automatically inserted into the objective function).\n", + "\n", + "The only thing missing from the model description is the `Unit` (`gas_turbine`). It takes gas as its only input and\n", + "transforms that into electricity. For this we first add the following component:\n", + "\n", + "```yaml\n", + " gasturbine:\n", + " type: Unit\n", + " inputs: {gas: gas_grid}\n", + " outputs: {electricity: elec_grid}\n", + " conversion: 1 gas -> 0.40 electricity\n", + " capacity: 100 out:electricity\n", + "```\n", + "\n", + "Let's look at the `Unit`-specific settings in detail:\n", + "\n", + "- `inputs: {gas: gas_grid}`: This tells IESopt that an input accepting `gas` (the carrier) is connected to `gas_grid` (= it is\n", + " consuming gas from there).\n", + "- Similarly, `outputs: {electricity: elec_grid}` tells IESopt where the only output (with carrier `electricity`) feeds energy to.\n", + "- The most important part is kept in the so-called \"conversion expression\" `conversion: 1 gas -> 0.40 electricity`: This\n", + " tells IESopt that the `Unit` will use 1 unit of gas (e.g. kWh, MJ, ...) and convert it into 0.4 units of electricity\n", + " (e.g. kWh) at a fixed rate.\n", + "- Finally, `capacity: 100 out:electricity` specifies the \"capacity limitations\" of this `Unit`: 100 units of electricity can\n", + " be produced. This implicitly limits the maximum amount of gas that can be used during a time step to 250 units of gas.\n", + "\n", + "`Unit`s come with a lot of additional (and very specific) parameters (e.g. `marginal_cost`, `availability`, ...) that are\n", + "explained in detail in the specific section of the docs." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Final config file\n", + "\n", + "Following the above steps you should now have your `my_first_model.iesopt.yaml` config file setup like this:\n", + "\n", + "```yaml\n", + "config:\n", + " optimization:\n", + " problem_type: LP\n", + " snapshots:\n", + " count: 24\n", + "\n", + "carriers:\n", + " electricity: {}\n", + " gas: {}\n", + "\n", + "components:\n", + " elec_grid: # the unique name of this component\n", + " type: Node # the type of this component\n", + " carrier: electricity # this (Node-specific) parameter fixes the carrier to be electricity\n", + "\n", + " gas_grid: # the unique name of this component\n", + " type: Node # the type of this component\n", + " carrier: gas # this (Node-specific) parameter fixes the carrier to be electricity\n", + "\n", + " storage:\n", + " type: Node\n", + " carrier: electricity\n", + " has_state: true # this allows this Node to have an \"internal state\"\n", + " state_lb: 0 # the state can not drop below 0\n", + " state_ub: 50 # a maximum of 50 electricity can be stored \n", + " \n", + " conn:\n", + " type: Connection\n", + " node_from: elec_grid # energy flows from HERE\n", + " node_to: storage # to THERE\n", + " capacity: 15 # with a maximum capacity of +-15 units of electricity\n", + "\n", + " demand:\n", + " type: Profile # the type is now \"Profile\"\n", + " carrier: electricity\n", + " value: 5 # this models a fixed demand of 5 units of electricity during every Snapshot\n", + " node_from: elec_grid # this tells MFC that this Profile draws energy from \"elec_grid\"\n", + "\n", + " # We can also set the \"value\" of a Profile to a time series, as can be seen:\n", + " photovoltaic:\n", + " type: Profile\n", + " carrier: electricity\n", + " value: [0,0,0,0,0,1,2,3,4,5,8,12,12,12,8,5,4,3,2,1,0,0,0,0]\n", + " node_to: elec_grid # now feeding INTO \"elec_grid\"\n", + "\n", + " gas_market:\n", + " type: Profile\n", + " carrier: gas\n", + " mode: create # this changes the mode from the default (\"fixed\") to \"create\"\n", + " cost: 100 # this specifies the \"cost of gas\"\n", + " node_to: gas_grid\n", + "\n", + " gasturbine:\n", + " type: Unit\n", + " inputs: {gas: gas_grid}\n", + " outputs: {electricity: elec_grid}\n", + " conversion: 1 gas -> 0.40 electricity\n", + " capacity: 100 out:electricity\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Running the optimization\n", + "\n", + "Assuming that both `my_first_model.iesopt.yaml` and `main.py` are located in the same folder, you can now execute the following\n", + "command there (make sure you are in the correct Python environment):\n", + "\n", + "```shell\n", + "python ./main.py\n", + "```\n", + "\n", + "The output should show a total objective value of `9500`, resulting from `38` units of electricity missing after\n", + "accounting for PV production, amounting to a total need of `95` units of gas, at a price of `100`.\n", + "\n", + "> Note on startup time: If you are running this for the first time, you might notice a considerable delay before the\n", + " output is shown. This is due to the fact that IESopt is automatically connecting to the internal \"core\" (which is written in Julia) and updating it. This can\n", + " be avoided by running the `import iesopt` command once and then just iterating on the generate/optimize part, in a \"REPL-style\" approach. Remember: Interactively executing a line or block of code in VSCode is usually bound to `Shift + Enter`.\n", + "\n", + "## Extracting model results\n", + "\n", + "Now, head over to the [extracting results](https://ait-energy.github.io/iesopt/notebooks/custom_results_1.html) tutorial, to get started with extracting actually results from your model." + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/_sources/pages/dev/core.md.txt b/_sources/pages/dev/core.md.txt new file mode 100644 index 0000000..9e7a4ab --- /dev/null +++ b/_sources/pages/dev/core.md.txt @@ -0,0 +1,3 @@ +# IESopt.jl + +To be added: notes on contributing to the Julia core. diff --git a/_sources/pages/dev/general.md.txt b/_sources/pages/dev/general.md.txt new file mode 100644 index 0000000..e413dfc --- /dev/null +++ b/_sources/pages/dev/general.md.txt @@ -0,0 +1,153 @@ +# Contributing + +Thank you for your interest in contributing to `iesopt`! This guide provides instructions on setting up your development environment and contributing code. + +## Setting up the development environment + +1. **Fork the Repository:** First, fork the main repository on GitHub to your account. +2. **Clone the Repository:** Clone the forked repository to your local machine, by running + + ```bash + git clone https://github.com/your-username/iesopt.git + cd iesopt + ``` +3. **Install uv:** + Please follow the official [uv install instructions](https://docs.astral.sh/uv/getting-started/installation/) + + +4. **Create the virtual environment:** To setup the development venv run + + ```bash + uv sync + ``` +The virtual environment is now located in the ```.venv``` folder (if you ever need to start over, you can just delete this folder) + +5. **Pre-commit Hooks:** To maintain code quality, please install the pre-commit hooks: + + ```bash + uv run pre-commit install + ``` + +## Code formatting and linting + +After installing pre-commit hooks before, all checks should be automatically run and applied when committing changes. + +:::{admonition} Manually running checks +:class: dropdown + +You can either execute + +```bash +uv run pre-commit +``` + +This stashes all changes that you have not committed. If you want to check all files, as they are, you can do so by running: + +```bash +uv run pre-commit run --all +``` + +::: + +`ruff` (our linter & formatter) should fix most mistakes automatically. `codespell` (our spelling checker) however, will report on mistakes in the terminal, linking to their occurrence. Inspect them, and fix them accordingly. Consult their [README](https://github.com/codespell-project/codespell) if you (highly unlikely) encounter a word where it wrongfully triggers. + +## Running tests + +To run the test suite in your dev environment do + +```bash +uv run pytest +``` + +This will print a coverage report, which looks like + +```text +Name Stmts Miss Cover +-------------------------------------------------- +src/iesopt/__init__.py 33 4 88% +src/iesopt/general.py 0 0 100% +... +src/iesopt/util/logging.py 7 0 100% +src/iesopt/util/util.py 5 0 100% +-------------------------------------------------- +TOTAL 417 172 59% +``` + +and tells you roughly how good each file is covered by automated tests. Further it creates a `coverage.xml` file in the project root folder, that you can use. In `VSCode`, the extension [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) allows inspecting test coverage directly in your code editor. If you have it installed and loaded, simply hit `Ctrl + Shift + 8` (if you are using default keybinds) to enable watching the coverage. + +### Running tox tests +To run the test suite against all compatible Python versions do + +```bash +uv run tox +``` + +## Making changes + +1. **Create a New Branch**: Always create a new branch for your work: + + ```bash + git checkout -b new-fix-or-feature + ``` + +2. **Make Your Changes**: Edit files and make your desired changes. + +3. **Test Your Changes**: Run tests frequently to ensure nothing is broken. + +4. **Commit Your Changes**: Follow conventional commit messages for clarity: + + ```bash + git commit -m "feat: added new feature X" + ``` + +5. **Push Your Changes**: Push the changes to your fork: + + ```bash + git push origin new-fix-or-feature + ``` + +## Improving the documentation + +### Building locally + +```bash +uv run sphinx-build --nitpicky --fail-on-warning --fresh-env --keep-going --builder html docs/ docs/_build/html +``` + +You can omit the `--fresh-env` option to not + +```text +--fresh-env, -E don't use a saved environment, always read all files +``` + +and alternatively use + +```bash +uv run make -C docs clean +``` + +to clean the `docs` before rebuilding them. + +:::{admonition} Note on `--keep-going` +:class: caution +A final build of updates to the documentation, that is built using the `--fail-on-warning` flag, should be done without +`--keep-going`, to prevent skipping warnings/errors: Each warning (or subsequent error) should be properly resolved! +::: + +## Submitting a pull request (PR) + +Once your changes are pushed to your fork, you can submit a pull request to the main repository: + +1. **Open a Pull Request**: Go to the [main repository](https://github.com/ait-energy/iesopt) and click "New Pull Request." +2. **Follow the Template**: Provide a clear title and description of your changes. +3. **Respond to Feedback**: Engage with reviewers and make requested changes promptly. + +## Code Style and Guidelines + +- **Follow PEP 8**: Ensure your code adheres to [PEP 8](https://www.python.org/dev/peps/pep-0008/). +- **Document Your Code**: Provide docstrings for all functions, classes, and modules. +- **Write Tests**: Include tests for new features and bug fixes. + +## Reporting issues + +If you find any bugs or have feature requests, please report them via the [issue tracker](https://github.com/ait-energy/iesopt/issues). diff --git a/_sources/pages/dev/updating.md.txt b/_sources/pages/dev/updating.md.txt new file mode 100644 index 0000000..fa5365b --- /dev/null +++ b/_sources/pages/dev/updating.md.txt @@ -0,0 +1,126 @@ +# Updating + +This page collects information about updating the project between specific versions. + +## 1.x.y to 2.0.0 + +The `2.0.0` release follows the breaking change of `IESopt.jl` going to `v2.0.0`. This was mainly triggered by a proper rework of how addons work, including a new internal handling for more complex expressions (allowing more flexibility in configuring `conversion`, or `cost` settings), as well as better support around templates (e.g. the introduction of `Virtual`s). A few breaking changes that were planned for quite some time are part of this. + +### Changes to the top-level config + +The structure of the `config` section in the top-level configuration file changed. Overall it's highly similar, but: + +- A `general` section was added that properly groups "general" settings, that were previous keyword arguments. +- Small adjustments were made to account for that. +- The `results` section changed slightly. + +See the respective section(s) in the [YAML/Top-level config](../manual/yaml/top_level.md) documentation. The most important changes relate to the following (sub-)sections: + +- `name` +- `version` +- `high_performance` +- `constraint_safety` is now called `soft_constraints` and part of the `optimization` section. + +### Changes to keyword arguments + +Previously we allowed passing arbitrary keyword arguments to public functions, like {py:func}`iesopt.Model.generate` or {py:func}`iesopt.run`. This can lead to problems with latency on previously unseen types of arguments, especially now that we support arbitrary dictionaries as model parameters. This now works differently: + +- There are no arbitrary keyword arguments anymore. +- Model parameters have to be passed as dictionary, using `parameters = {foo: 1.0, bar: "austria"}`. +- There is a new and "powerful" `config` keyword argument, explained below. + +#### `config` + +Besides model parameters, specific keyword arguments existed, e.g., `verbosity`. These are gone and now first-class options in the model's top-level config (see [YAML/Top-level config](../manual/yaml/top_level.md)). With that a way to change them programmatically was in need, which is what the `config` argument does. Take the following example: + +```{code-block} python +:caption: Example usage of `config` keyword argument. + +import iesopt + +iesopt.run("my_config.iesopt.yaml", config = { + "general.verbosity.core": "error", + "optimization.snapshots.count": 168, + "files.data_in": "scenario17/data.csv", +}) +``` + +The passed dictionary modifies the top-level configuration during the parsing step. That means you are able to overwrite / set each configuration option programmatically. The access pattern follows the structure of the `config` section in the top-level configuration file. For example, the verbosity in the YAML looks like: + +```{code-block} yaml +:caption: Verbosity example in the top-level YAML. + +config: + general: + verbosity: + core: info +``` + +To modify / set this, `general.verbosity.core` is used. As you can see that opens the possibility to modify, e.g., files that are loaded based on (for example) which scenario we are in by modifying the `files` section, using `files.data_in`. Here `data_in` is the "name" used in the model later on (using `some_col@data_in`). + +Further, this means you no longer need to use "model parameters" for stuff that is actually part of the `config` section, e.g., the number of Snapshots in your model. Instead of coming up with a parameter, e.g., `my_snapshot_count`, using it as `count: `, and then passing it as `iesopt.run("...", my_snapshot_count = 168)`, you can now just modify that as shown above by setting it using the accessor `optimization.snapshots.count`. + +#### `parameters` + +Any dictionary passed to this will be used in the same way as keyword arguments were used before: Parameters given there are replaced in the `parameters` section (which might be based on an external `*.iesopt.param.yaml` file). + +### Changes to result extraction + +We saw increasing interest in actively "filtering" results of various models. This triggered the implementation of a new result backend (initially only JLD2) supported by DuckDB. This is (as of 2.0.0) not enabled as default, since it does not fully support the functionality that the Python wrapper `iesopt` provides. However, we plan on switching to this backend in the future. + +### Changes to addons + +To be written: Link to addon page/docs as soon as they are done. + +### Changes to examples, and more + +Everything that was previously part of `IESoptLib.jl` is now integrated into `IESopt.jl`. Functionality around this can be accessed using `IESopt.Assets`. + +### Other changes + +#### Tags + +Component "tags" are back (or accessible again, they were never gone completely). That means you can tag components to later simplify result handling or working with your model. A tag can be added when creating a component: + +```{code-block} yaml +:caption: Adding a tag. + +components: + my_unit: + type: Unit + tags: CustomUnit +``` + +The attribute `tags` supports single tags (e.g., `CustomUnit`) or lists of tags (e.g., `[CustomUnit]` or `[CustomUnit, FindMe]`). + +:::{tip} +Each core component automatically tags its own type, so each `Unit` will already be tagged with the tag `"Unit"`. + +Most importantly, that means you are also able to extract all `Virtual`s (non-existing components related to templates that you use), since each of these also tags its "actual type". +::: + +Using this is possible with {py:func}`iesopt.Model.get_components`: + +```{code-block} python +:caption: Extracting tagged components. + +import iesopt + +model = iesopt.run("tagged_model.iesopt.config") + +my_units = model.get_components("CustomUnit") +``` + +When passing a `list` of tags, only components having ALL of these tags are returned. + +:::{tip} +Most importantly, that (and the fact that components "auto-tag" their type) means you are also able to extract all `Virtual`s (non-existing components related to templates that you use), since each of these also tags its "actual type". Consider a template `Battery` that you use to initialize a battery + +```yaml +components: + my_bat: + type: Battery +``` + +All batteries can then be found using `model.get_components("Battery")`. This can also be really helpful in addons, where (using Julia) you can find, e.g., all CHPs in your model by doing `chps = IESopt.get_components(model; tagged="CHP")`. +::: diff --git a/_sources/pages/manual/julia/assets.md.txt b/_sources/pages/manual/julia/assets.md.txt new file mode 100644 index 0000000..5993b39 --- /dev/null +++ b/_sources/pages/manual/julia/assets.md.txt @@ -0,0 +1,47 @@ +# Assets + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +Assets module for IESopt.jl, containing path handling (relocatable) for assets: addons, examples, templates. + + + +## API Reference + +### Types + +### Macros + +### Functions + +#### `get_path` + +```julia +get_path(asset_type::String) + + +``` + +Get the path to the asset type folder. Currently supports: "addons", "examples", "templates". + +_**Arguments**_ +- `asset_type::String`: The asset type. + +_**Returns**_ +- `RelocatableFolders.Path`: The path to the asset folder, already using `normpath`. `Path` implements an automatic conversion to `String`. + +_**Example**_ +```julia +Assets.get_path("templates") +``` + + + +--- + diff --git a/_sources/pages/manual/julia/index.md.txt b/_sources/pages/manual/julia/index.md.txt new file mode 100644 index 0000000..8de83d0 --- /dev/null +++ b/_sources/pages/manual/julia/index.md.txt @@ -0,0 +1,918 @@ +# Julia + +If you are interested in more details on how to use [IESopt.jl](https://github.com/ait-energy/IESopt.jl) directly, please refer to it's source code. + +This page contains the docstrings of all things that are part of the public API of IESopt, except: + +- All core components. Their full documentation is available in the [components section](../yaml/core_components.md). +- The docstrings related to sub-modules of IESopt, e.g., `IESopt.Assets`. These can be found in the sub-pages of the current section. + +## Notes + +- You can use `IESopt.IESU` as abbreviation for `IESopt.Utilities`, which allows using, e.g., `IESU.annuity(...)` after doing `using IESopt` - which may be better to understand when reading the code instead of `IESopt.Utilities.annuity(...)` (when only importing), or `Utilities.annuity(...)` (which does not show any relation to IESopt). + +:::{tip} +The public API of the Python wrapper was, as far as possible, designed to be almost identical to the one of the Julia package, so things should look similar. + +For example, the following Python code: + +```python +import iesopt + +model = iesopt.generate("config.iesopt.yaml") + +my_result = model.get_component("turbine").exp.out_water +``` + +can be translated to Julia like this: + +```julia +import IESopt + +model = IESopt.generate!("config.iesopt.yaml") + +my_result = IESopt.get_component(model, "turbine").exp.out_water +``` + +::: + +:::{toctree} +:hidden: + +assets.md +utilities.md +resultsduckdb.md +resultsjld2.md +::: + +## API Reference +### Types + +#### `Carrier` + +```julia +struct Carrier + name::String + unit::Union{String, Nothing} +end + + +``` + +Represents a single (energy) carrier with a given `name`. + +This is mostly used to represent various commodities that (easily) represent some form of energy (e.g. gas, water, ...), +but also enables modelling commodities that are not (treated as) representing some type of energy (e.g. CO2). Specify +`unit` to bind that carrier to an (arbitrary) unit that allows easier plotting and result analysis. + + + +--- + +#### `CoreTemplate` + +```julia +CoreTemplate + + +``` + +A struct to represent an IESopt.jl "Core Template". + + + +--- + +#### `Expression` + +```julia +Expression + + +``` + +A mutable struct representing a general expression in the optimization model. + +_**Fields**_ +- `model::JuMP.Model`: The IESopt model associated with the expression. +- `dirty::Bool`: A flag indicating if the expression is dirty (modified but not updated). Defaults to `false`. +- `temporal::Bool`: A flag indicating if the expression is temporal. Defaults to `false`. +- `empty::Bool`: A flag indicating if the expression is empty. Defaults to `false`. +- `value::Union{Nothing, JuMP.VariableRef, JuMP.AffExpr, Vector{JuMP.AffExpr}, Float64, Vector{Float64}}`: The value of the expression, which can be a JuMP variable reference, affine expression, vector of affine expressions, float, or vector of floats. Defaults to `nothing`. +- `internal::Union{Nothing, NamedTuple}`: Internal data associated with the expression. Defaults to `nothing`. + +_**Usage examples**_ +```julia +if !my_exp.empty + # ... do something with `my_exp`, since it contains values ... +end +``` + +```julia +# Get the Expression's value at Snapshot `t`. +access(my_exp, t) + +# Get the Expression's value - could be vector-valued. +access(my_exp) +``` + +Both ways to access the value can be used with a type assertion to get the value in a specific type: + +```julia +# Get the Expression's value at Snapshot `t` as a Float64. +access(my_exp, t, Float64) + +# Get the Expression's value as a Float64. +access(my_exp, Float64) +``` + +If the value of `my_exp` is a vector of `Float64`, the first call will succeed, while the second will throw a type assertion error. + + + +--- + +#### `InternalData` + +```julia +InternalData + + +``` + +The internal data structure used by IESopt.jl to store all relevant information about the model, its components, and +results. + + + +--- + +#### `NonEmptyExpressionValue` + +This constant defines a union type `NonEmptyExpressionValue` which describes any value type that an Expression can hold, +guaranteeing that the Expression can not be empty. + + +--- + +#### `NonEmptyNumericalExpressionValue` + +This constant defines a union type `NonEmptyNumericalExpressionValue` which describes any numerical value type that an +Expression can hold, guaranteeing that the Expression can not be empty. `JuMP` objects are not included, and it can be +either scalar or vector-valued. + + +--- + +#### `NonEmptyScalarExpressionValue` + +This constant defines a union type `NonEmptyScalarExpressionValue` which describes any scalar-valued type that an +Expression can hold, guaranteeing that the Expression can not be empty. + + +--- + +#### `OptionalScalarExpressionValue` + +This constant defines a union type `OptionalScalarExpressionValue` which describes any scalar-valued value type that an +Expression can hold. Due to `Optional` it also includes `nothing`. + + +--- + +#### `Snapshot` + +```julia +struct Snapshot + name::_String + id::_ID + weight::_ScalarInput + + is_representative::Bool + representative::_ID +end + + +``` + +Represent a specific timestamp, that can be tied to timeseries values. + +Each `Snapshot` expects a `name`, that can be used to hold a timestamp (as `String`; therefore supporting arbitrary +formats). The `weight` (default = 1.0) specifies the "probabilistic weight" of this `Snapshot` or the length of the +timeperiod that **begins** there (a `weight` of 2 can therefore represent a 2-hour-resolution; this also allows a +variable temporal resolution throughout the year/month/...). + + + +--- + +#### `Virtual` + +A `Virtual` (component) is a component that does not exist in the model, but one that a user might expect to exist. +These are "components" that refer to a template. If a user creates a component "my_storage_foo", of type "Battery", they +might expect (and want) to be able to interact with "my_storage_foo". Since the template is flattened into explicit +`CoreComponent`s in the back, "my_storage_foo" does not actually exist - a problem that these `Virtual`s solve. + + + +--- + +### Macros + +#### `@check` + +```julia +@check + + +``` + +Check whether the passed expression passes an `ArgCheck.@check`, especially helpful to validate a Template's parameters. + +_**Example**_ + +A `validate` section added to a Template can make use of the `@check` macro to validate the parameters passed to the +template. The macro will properly raise a descriptive error if the condition is not met. + +```yaml +parameters: + p: null + +functions: + validate: | + @check this.get("p") isa Number + @check this.get("p") > 0 +``` + +See ["Template Validation"](@ref manual_templates_validation) in the documentation for more information. + +!!! warning "Usage outside of Core Template validation" +This requires `__component__` to be set to some `String` outside of calling the macro, since it accesses this to +construct a proper error message. + + + +--- + +#### `@config` + +```julia +@config(model, expr, type::Union{Symbol, Expr}=:Any) + + +``` + +Returns or sets a configuration value in the `model`'s configuration dictionary. + +This macro is used to set and retrieve configuration values in the `model`'s configuration dictionary. This resolves the +access properly during compile time. A type can be optionally specified to assert the type of the value. + +_**Example**_ + +```julia +# Setting a configuration value. +@config(model, general.verbosity.core) = "info" + +# Getting a configuration value. +verbosity = @config(model, general.verbosity.core, String) +``` + + + +--- + +#### `@critical` + +```julia +@critical(msg, args...) + + +``` + +A macro that logs an error message and then throws an error with the same message. Use it in the same way you would use `@error`, or `@info`, etc. + +_**Arguments**_ +- `msg`: The main error message to be logged and thrown. +- `args...`: Additional arguments to be included in the error log. + + + +--- + +#### `@profile` + +```julia +@profile(arg1, arg2=nothing) + + +``` + +This macro is used to profile the execution of a function. It captures the time, memory allocation, and number of calls +of the function. The profiling data is stored in the `_profiling` field of the `_IESoptData` structure. The identifier +passed to the macro is used to store the profiling data. If no identifier is provided, the function's name is used as +the identifier. + +Options to use this macro are: +- @profile model "identifier" foo() +- @profile model foo() +- @profile "identifier" foo(model) +- @profile foo(model) + + + +--- + +### Functions + +#### `access` + +```julia +access(e::Expression) + + +``` + +Access the value of an `Expression` object. + + + +--- + +#### `add_term_to_objective!` + +```julia +add_term_to_objective!(model::JuMP.Model, objective::String, term::Union{JuMP.AffExpr, JuMP.VariableRef}) + + +``` + +Add a term to an objective in the model, which can be used for dynamically creating objectives, e.g., in addons. + +The default objective (that always exists) is called "total_cost". Other objectives can be dynamically registered using +`register_objective!`, or they can be added based on the YAML configuration. + +_**Arguments**_ + +- `model::JuMP.Model`: The model to add the term to. +- `objective::String`: The name of the objective to add the term to. +- `term::Union{JuMP.AffExpr, JuMP.VariableRef}`: The term to add to the objective. + +_**Example**_ + +```julia +add_term_to_objective!(model, "my_obj", 2 * x) +``` + + + +--- + +#### `build!` + +```julia +build!(model::JuMP.Model) + + +``` + +Builds and prepares the given IESopt model. This function performs the following steps: + +1. Prepares the model by ensuring necessary conversions before performing consistency checks. +2. Checks the consistency of all parsed components in the model. +3. If any component fails the consistency check, an error is raised. +4. Builds the model if all components pass the consistency checks. +5. Logs profiling results after the build process, displaying the top 5 profiling results. + +_**Arguments**_ +- `model::JuMP.Model`: The IESopt model to be built and prepared. + +_**Errors**_ +- Raises an error if any component does not pass the consistency check. + + + +--- + +#### `compute_IIS` + +```julia +function compute_IIS(model::JuMP.Model; filename::String = "") + + +``` + +Compute the IIS and print it. If `filename` is specified it will instead write all constraints to the given file. This +will fail if the solver does not support IIS computation. + + + +--- + +#### `extract_result` + +```julia +extract_result(model::JuMP.Model; path::String = "./out", write_to_file::Bool=true) + + +``` + +DEPRECATED + + + +--- + +#### `generate!` + +```julia +generate!(filename::String; @nospecialize(kwargs...)) + + +``` + +Generate an IESopt model based on the top-level config in `filename`. + +_**Arguments**_ +- `filename::String`: The name of the file to load. + +_**Keyword Arguments**_ +To be documented. + +_**Returns**_ +- `model::JuMP.Model`: The generated IESopt model. + + + +--- + +#### `get_T` + +```julia +get_T(model::JuMP.Model) + + +``` + +Retrieve the vector `T` from the IESopt model. + +_**Arguments**_ +- `model::JuMP.Model`: The IESopt model from which to extract the vector `T`. + +_**Returns**_ +- `Vector{_ID}`: The vector `T`. + + + +--- + +#### `get_component` + +```julia +function get_component(model::JuMP.Model, component_name::AbstractString) + + +``` + +Get the component `component_name` from `model`. + + + +--- + +#### `get_components` + +```julia +get_components(model::JuMP.Model; tagged::Union{Nothing, String, Vector{String}} = nothing) + + +``` + +Retrieve components from a given IESopt model. + +_**Arguments**_ +- `model::JuMP.Model`: The IESopt model from which to retrieve components. +- `tagged::Union{Nothing, String, Vector{String}}`: Optional argument to specify tagged components to retrieve. +If `nothing`, all components are retrieved. If a `String` or `Vector{String}`, only components with the specified tags are retrieved. + +_**Returns**_ +- `Vector{_CoreComponent}`: A subset of components from the model. + + + +--- + +#### `get_global` + +```julia +get_global(type::String) + + +``` + +Get a global setting for IESopt. + +_**Arguments**_ +- `type::String`: The type of global setting. Currently supports: "parameters", "config", "addons", "carriers", "components", "load_components", "skip_validation". + +_**Returns**_ +- The global setting. + +_**Example**_ +```julia +using IESopt + +get_global("skip_validation") +``` + + + +--- + +#### `get_value_at` + +```julia +get_value_at(x::T, ::_ID) where {T <: Union{Nothing, Real, JuMP.VariableRef, JuMP.AffExpr}} + + +``` + +Returns the value of `x` at any Snapshot index `t`. Can be used to access variables without needing to handle whether +they are vector-valued or not. + +_**Arguments**_ +- `x::T`: The input value which can be of type `Nothing`, `Real`, `JuMP.VariableRef`, or `JuMP.AffExpr`. +- `::_ID`: Unused Snapshot index. + +_**Returns**_ +- The value of `x`. + + + +--- + +#### `get_version` + +```julia +get_version() + + +``` + +Get the current version of IESopt.jl. + +_**Returns**_ +- `String`: The current version of IESopt.jl. + + + +--- + +#### `internal` + +```julia +internal(model::JuMP.Model) + + +``` + +Retrieve the internal data structure from the JuMP model. + + + +--- + +#### `make_base_name` + +```julia +make_base_name(comp::_CoreComponent, str::String) + + +``` + +Create a `JuMP` object (e.g., a variable) for a given component based on the component's name and a user-defined string. + +_**Arguments**_ +- `comp`: The core component for which to create the base name. +- `str`: The user-defined string to append to the component's name. + +_**Returns**_ +- A string containing the base name for the object. + +_**Example**_ +```julia +JuMP.@constraint( + model, + [t = get_T(model)], + profile.exp.value[t] <= 1, + base_name = make_base_name(profile, "con_custom"), + container = Array, +) +``` + + + +--- + +#### `optimize!` + +```julia +optimize!(model::JuMP.Model; kwargs...) + + +``` + +Optimize the given IESopt model with optional keyword arguments. + +_**Arguments**_ +- `model::JuMP.Model`: The IESopt model to be optimized. +- `kwargs...`: Additional keyword arguments to be passed to the `JuMP.optimize!` function. + +_**Description**_ +This function performs the following steps: +1. If there are constraint safety penalties, it relaxes the constraints based on these penalties. +2. Sets the verbosity of the solver output based on the model's configuration. +3. Logs the solver output to a file if logging is enabled and supported by the solver. +4. Calls `JuMP.optimize!` to solve the model. +5. Checks the result count and termination status to log the optimization outcome. +6. Analyzes the constraint safety results if there were any constraint safety penalties. +7. Extracts and saves the results if the model is solved and feasible. +8. Profiles the results after optimization. + +_**Logging**_ +- Logs messages about the relaxation of constraints, solver output, and optimization status. +- Logs warnings if the safety constraint feature is triggered or if unexpected result counts are encountered. +- Logs errors if the solver log file setup fails, if no results are returned, or if extracting results is not possible. + +_**Returns**_ +- `nothing`: This function does not return any value. + + + +--- + +#### `overview` + +```julia +overview(file::String) + + +``` + +Extracts the most important information from an IESopt model file, and returns it as a dictionary. + + + +--- + +#### `pack` + +```julia +pack(file::String; out::String="", method=:store) + + +``` + +Packs the IESopt model specified by the top-level config file `file` into single file. + +The `out` argument specifies the output file name. If not specified, a temporary file is created. Returns the output +file name. The `method` argument specifies the compression method to use. The default is `:store`, which means no +compression is used. The other option is `:deflate`, which uses the DEFLATE compression method. The default (`:auto`) +applies `:store` to all files below 1 MB, `:deflate` otherwise. + + + +--- + +#### `parse!` + +```julia +parse!(model::JuMP.Model, filename::AbstractString; kwargs...) + + +``` + +Parse the model configuration from a specified file and update the given `JuMP.Model` object. + +_**Arguments**_ +- `model::JuMP.Model`: The JuMP model to be updated. +- `filename::AbstractString`: The path to the configuration file. The file must have a `.iesopt.yaml` extension. + +_**Keyword Arguments**_ +To be documented. + +_**Returns**_ +- `Bool`: Returns `true` if the model was successfully parsed. + +_**Errors**_ +- Logs a critical error if the file does not have the `.iesopt.yaml` extension or if there is an error while parsing the model. + + + +--- + +#### `register_objective!` + +```julia +register_objective!(model::JuMP.Model, objective::String) + + +``` + +Register a new objective in the model, which can for dynamically creating objectives, e.g., in addons. + +_**Arguments**_ + +- `model::JuMP.Model`: The model to register the objective in. +- `objective::String`: The name of the objective to register. + +_**Example**_ + +```julia +register_objective!(model, "my_obj") +``` + +which is equivalent to: + +```yaml +config: + optimization: + objectives: + my_obj: [] +``` + + + +--- + +#### `run` + +```julia +run(filename::String; kwargs...) + + +``` + +Build, optimize, and return a model. + +_**Arguments**_ + +- `filename::String`: The path to the top-level configuration file. + +_**Keyword Arguments**_ + +Keyword arguments are passed to the `generate!(...)` function. + + + +--- + +#### `safe_close_filelogger` + +```julia +safe_close_filelogger(model::JuMP.Model) + + +``` + +Safely closes the file logger's iostream if it is open. This function checks if the logger associated with the given `model` is a `LoggingExtras.TeeLogger` and if it contains a `IESopt._FileLogger` as one of its loggers. If the file logger's stream is open, it will be closed. + +_**Arguments**_ +- `model::JuMP.Model`: The IESopt model which contains the logger to be closed. + +_**Returns**_ +- `nothing`: This function does not return any value. + +_**Notes**_ +- The function includes a `try-catch` block to handle any potential errors during the closing process. Currently, the catch block does not perform any actions. + + + +--- + +#### `set_global!` + +```julia +set_global!(type::String, key::String, @nospecialize(value)) + + +``` + +Set a global setting for IESopt. These will be used as defaults for every subsequent function call (that supports +these). User passed settings will override these defaults. + +_**Arguments**_ +- `type::String`: The type of global setting. Currently supports: "parameters", "config", "addons", "carriers", "components", "load_components". +- `key::String`: The key of the global setting. +- `value`: The value of the global setting. + +_**Example**_ +```julia +using IESopt + +set_global!("config", "general.verbosity.core", "error") +``` + + + +--- + +#### `to_table` + +```julia +function to_table(model::JuMP.Model; path::String = "./out", write_to_file::Bool=true) + + +``` + +Turn `model` into a set of CSV files containing all core components that represent the model. + +This can be useful by running +```julia +IESopt.parse!(model, filename) +IESopt.to_table(model) +``` +which will parse the model given by `filename`, without actually building it (which saves a lot of time), and will +output a complete "description" in core components (that are the resolved version of all non-core components). + +If `write_to_file` is `false` it will instead return a dictionary of all DataFrames. + + + +--- + +#### `unpack` + +```julia +unpack(file::String; out::String="", force_overwrite::Bool=false) + + +``` + +Unpacks the IESopt model specified by `file`. + +The `out` argument specifies the output directory. If not specified, a temporary directory is created. Returns the +path to the top-level config file. The `force_overwrite` argument specifies whether to overwrite existing files. + + + +--- + +#### `validate` + +```julia +validate(toplevel_config_file::String) + + +``` + +Validate the model description bsaed on the given top-level configuration file. This function checks the top-level +configuration file and all referenced files for validity. The function returns `true` if the model description is valid, +and `false` otherwise. If the model description is invalid, the function will print an error message. + +_**Arguments**_ +- `toplevel_config_file::String`: The path to the top-level configuration file. + +_**Returns**_ +- `valid::Bool`: Whether the model description is valid (true), or not (false). + + + +--- + +#### `write_to_file` + +```julia +write_to_file(model::JuMP.Model, filename::String; format::JuMP.MOI.FileFormats.FileFormat = JuMP.MOI.FileFormats.FORMAT_AUTOMATIC, kwargs...) + + +``` + +Write the given IESopt model to a file with the specified filename and format. + +Be aware, that this function will overwrite any existing file with the same name! + +_**Arguments**_ +- `model::JuMP.Model`: The IESopt model to be written to a file. +- `filename::String`: The name of the file to which the model should be written. Note that if the format is set to +`FORMAT_AUTOMATIC`, the the file extension will be forced to lower case to allow detection. +- `format::JuMP.MOI.FileFormats.FileFormat` (optional): The format in which the model should be written. The default is +`JuMP.MOI.FileFormats.FORMAT_AUTOMATIC`; if left as that, it will try to automatically determine the format based on +the file extension. + +All additional keyword arguments are passed to the `JuMP.write_to_file` function. + +_**Returns**_ +- `String`: The absolute path to the file to which the model was written. + +_**Example**_ +```julia +import IESopt + +model = IESopt.generate!("config.iesopt.yaml") +IESopt.write_to_file(model, "model.lp") +``` + + + +--- + diff --git a/_sources/pages/manual/julia/resultsduckdb.md.txt b/_sources/pages/manual/julia/resultsduckdb.md.txt new file mode 100644 index 0000000..1865844 --- /dev/null +++ b/_sources/pages/manual/julia/resultsduckdb.md.txt @@ -0,0 +1,22 @@ +# ResultsDuckDB + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +The `ResultsDuckDB` module provides functions to extract results from a JuMP model and store them in a DuckDB database. + + + +## API Reference + +### Types + +### Macros + +### Functions + diff --git a/_sources/pages/manual/julia/resultsjld2.md.txt b/_sources/pages/manual/julia/resultsjld2.md.txt new file mode 100644 index 0000000..a7e8aae --- /dev/null +++ b/_sources/pages/manual/julia/resultsjld2.md.txt @@ -0,0 +1,42 @@ +# ResultsJLD2 + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +This module provides functions to save and load results to and from JLD2 files. + + + +## API Reference + +### Types + +### Macros + +### Functions + +#### `load_results` + +```julia +load_results(filename::String) + + +``` + +Load results from a JLD2 file. + +_**Arguments**_ +- `filename::String`: The path to the JLD2 file. + +_**Returns**_ +- `results`: The IESopt result object. + + + +--- + diff --git a/_sources/pages/manual/julia/utilities.md.txt b/_sources/pages/manual/julia/utilities.md.txt new file mode 100644 index 0000000..c473436 --- /dev/null +++ b/_sources/pages/manual/julia/utilities.md.txt @@ -0,0 +1,112 @@ +# Utilities + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +This module contains utility functions for the IESopt package, that can be helpful in preparing or analysing components. + + + +## API Reference + +### Types + +### Macros + +### Functions + +#### `annuity` + +```julia +annuity(total::Real; lifetime::Real, rate::Float64, fraction::Float64) + + +``` + +Calculate the annuity of a total amount over a lifetime with a given interest rate. + +_**Arguments**_ + +- `total::Real`: The total amount to be annuitized. + +_**Keyword Arguments**_ + +- `lifetime::Real`: The lifetime over which the total amount is to be annuitized. +- `rate::Float64`: The interest rate at which the total amount is to be annuitized. +- `fraction::Float64`: The fraction of a year that the annuity is to be calculated for (default: 1.0). + +_**Returns**_ + +`Float64`: The annuity of the total amount over the lifetime with the given interest rate. + +_**Example**_ + +Calculating a simple annuity, for a total amount of € 1000,- over a lifetime of 10 years with an interest rate of 5%: + +```julia +# Set a parameter inside a template. +set("capex", IESU.annuity(1000.0; lifetime=10, rate=0.05)) +``` + +Calculating a simple annuity, for a total amount of € 1000,- over a lifetime of 10 years with an interest rate of 5%, +for a fraction of a year (given by `MODEL.yearspan`, which is the total timespan of the model in years): + +```julia +# Set a parameter inside a template. +set("capex", IESU.annuity(1000.0; lifetime=10, rate=0.05, fraction=MODEL.yearspan)) +``` + + + +--- + +#### `timespan` + +```julia +timespan(model::JuMP.Model)::Float64 + + +``` + +Calculate the total timespan (duration, time period, ...) of a model in hours. + +_**Arguments**_ + +- `model::JuMP.Model`: The model for which the timespan is to be calculated. + +_**Returns**_ + +`Float64`: The total timespan of the model in hours. + + + +--- + +#### `yearspan` + +```julia +yearspan(model::JuMP.Model)::Float64 + + +``` + +Calculate the total timespan (duration, time period, ...) of a model in years. This assumes that a full year has a total +of 8760 hours. + +_**Arguments**_ + +- `model::JuMP.Model`: The model for which the timespan is to be calculated. + +_**Returns**_ + +`Float64`: The total timespan of the model in years. + + + +--- + diff --git a/_sources/pages/manual/python/configuration.md.txt b/_sources/pages/manual/python/configuration.md.txt new file mode 100644 index 0000000..115021f --- /dev/null +++ b/_sources/pages/manual/python/configuration.md.txt @@ -0,0 +1,106 @@ +# Configuration + +Various configuration options are handled using environment variables. These can easily be configured using `.env` files. You can check out [`python-dotenv`](https://pypi.org/project/python-dotenv/) for more information on how to use `.env` files with Python. + +**How to?** For the basics, create a file called `.env` in the root of your project and add the following: + +```{code-block} text +:caption: A simple `.env` file. + +IESOPT_CORE = 2.0.0 +``` + +:::{tip} +For many applications it is perfectly fine not to use any configuration at all. The defaults are chosen to work well in most cases. Each release of the Python wrapper `iesopt` is bound to a specific version of the Julia package `IESopt.jl`. Changing this is only necessary if you want to use a different version of the Julia package. Further, we install a default version of `HiGHS` as solver - so unless you need to use (and have access to) a commercial solver, you should be fine with the default settings. +::: + +## Julia packages + +## IESopt.jl + +To install a specific version of the Julia core, IESopt.jl, use the following: + +```{code-block} text +:caption: Setting the IESopt.jl version. + +IESOPT_CORE = 2.0.0 +``` + +## Solvers + +Installing solver packages can be done using the following: + +```{code-block} text +:caption: Setting the solver versions. + +IESOPT_SOLVER_HIGHS = 1.12.0 +``` + +Other examples may be setting `IESOPT_SOLVER_CPLEX` or `IESOPT_SOLVER_GUROBI`. + +### Forcing a solver executable version + +For example when installing Gurobi, it might be that the Julia wrapper pulls a version newer than the one you are able +to use according to your license. This can be fixed by explicitly adding the corresponding `_jll` ("binary wrapper") +as dependency: + +```{code-block} text +:caption: Fixing a solver executable version in `.env`. + +IESOPT_PKG_Gurobi_jll = 11.0.3 +IESOPT_SOLVER_GUROBI = 1.6.0 +``` + +This will use the latest `Gurobi_jll` version related to Gurobi 11. + +## Important versions + +The following other entries are potentially used in some projects: + +```{code-block} text +:caption: Fine grained version control. + +IESOPT_JULIA = 1.11.1 +IESOPT_JUMP = 1.23.3 +``` + +## Arbitrary packages + +To install packages that are not part of or related to IESopt, you can use the following: + +```{code-block} text +:caption: Arbitrary packages. + +IESOPT_PKG_ModelingToolkit = 1.0.3 +``` + +Make sure the proper casing of the package name is used. The package name is case-sensitive. Even though environment variables are commonly upper-case only, the package name has to reflect the Julia package's wording. + +## Non-registered packages + +To install non-registered packages, you can use the following syntax: + +```{code-block} text +:caption: Installing packages from GitHub. + +IESOPT_CORE = https://github.com/ait-energy/IESopt.jl#super-important-feature +``` + +When such a repository is updated, we might not be able to automatically update the package. In this case, you can use the following + +```{code-block} python +:caption: Updating packages from GitHub. + +import iesopt +iesopt.julia.seval('import Pkg; Pkg.update("IESopt")') +``` + +to forcefully update a Julia package. + +## Options + +Currently the following options are available: + +:Options: +:`IESOPT_MULTITHREADED`: `yes` or `no` (default). Talk to us before using this. +:`IESOPT_OPTIMIZATION`: `rapid`, `latency` (default), `normal`, or `performance`. Consider using `latency` for small models, or repeatedly executing your code, since it may be faster for these kind of work loads. Set it to `performance` for large models where initial up-front costs are not relevant. `normal` refers to the default settings chosen by "just launching Julia". For iterative development on a single small model, `rapid` might be the best choice - however, it compromises actual performance, even in subsequent runs, so it is not recommended for production code. diff --git a/_sources/pages/manual/python/index.md.txt b/_sources/pages/manual/python/index.md.txt new file mode 100644 index 0000000..5ac6ccf --- /dev/null +++ b/_sources/pages/manual/python/index.md.txt @@ -0,0 +1,16 @@ +# Python + +```{eval-rst} +.. automodule:: iesopt + :members: examples, make_example, run, IESopt +``` + +:::{toctree} +:hidden: + +configuration.md +model.md +results.md +util.md +jump.md +::: diff --git a/_sources/pages/manual/python/jump.md.txt b/_sources/pages/manual/python/jump.md.txt new file mode 100644 index 0000000..c3450df --- /dev/null +++ b/_sources/pages/manual/python/jump.md.txt @@ -0,0 +1,10 @@ +# JuMP + +```{eval-rst} +.. autodata:: iesopt.JuMP + +.. autofunction:: iesopt.jump_value +.. autofunction:: iesopt.jump_dual +.. autofunction:: iesopt.jump_reduced_cost +.. autofunction:: iesopt.jump_shadow_price +``` diff --git a/_sources/pages/manual/python/model.md.txt b/_sources/pages/manual/python/model.md.txt new file mode 100644 index 0000000..1f7fed5 --- /dev/null +++ b/_sources/pages/manual/python/model.md.txt @@ -0,0 +1,10 @@ +# Model + +```{eval-rst} +.. autoclass:: iesopt.Model + :members: + +.. autoclass:: iesopt.model.ModelStatus() + :members: + :undoc-members: +``` diff --git a/_sources/pages/manual/python/results.md.txt b/_sources/pages/manual/python/results.md.txt new file mode 100644 index 0000000..6fde21d --- /dev/null +++ b/_sources/pages/manual/python/results.md.txt @@ -0,0 +1,6 @@ +# Results + +```{eval-rst} +.. autoclass:: iesopt.Results + :members: +``` diff --git a/_sources/pages/manual/python/util.md.txt b/_sources/pages/manual/python/util.md.txt new file mode 100644 index 0000000..6966d7c --- /dev/null +++ b/_sources/pages/manual/python/util.md.txt @@ -0,0 +1,10 @@ +# Utilities + +```{eval-rst} +.. autodata:: iesopt.julia + :annotation: -> juliacall.Main + +.. autofunction:: iesopt.Symbol + +.. autofunction:: iesopt.get_jl_docstr +``` diff --git a/_sources/pages/manual/yaml/core/connection.md.txt b/_sources/pages/manual/yaml/core/connection.md.txt new file mode 100644 index 0000000..3502065 --- /dev/null +++ b/_sources/pages/manual/yaml/core/connection.md.txt @@ -0,0 +1,377 @@ +# Connection + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +A `Connection` is used to model arbitrary flows of energy between `Node`s. It allows for limits, costs, delays, ... + + +## Parameters + +### `node_from` + +This `Connection` models a flow from `node_from` to `node_to` (both are `Node`s). + +- **mandatory:** yes +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `node_to` + +This `Connection` models a flow from `node_from` to `node_to` (both are `Node`s). + +- **mandatory:** yes +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `carrier` + +`Carrier` of this `Connection`. If not given, automatically picks the `carrier` of the `Node`s it connects. This parameter is not necessary, and only exists to allow for a more explicit definition. + +- **mandatory:** no +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `capacity` + +The symmetric bound on this `Connection`'s flow. Results in `lb = -capacity` and `ub = capacity`. Must not be specified if `lb`, `ub`, or both are explicitly stated. + +- **mandatory:** no +- **default:** $+\infty$ +- **values:** numeric, `col@file`, `decision:value` +- **unit:** power + + +### `lb` + +Lower bound of this `Connection`'s flow. + +- **mandatory:** no +- **default:** $-\infty$ +- **values:** numeric, `col@file`, `decision:value` +- **unit:** power + + +### `ub` + +Upper bound of this `Connection`'s flow. + +- **mandatory:** no +- **default:** $+\infty$ +- **values:** numeric, `col@file`, `decision:value` +- **unit:** power + + +### `cost` + +Cost of every unit of energy flow over this connection that is added to the model's objective function. Keep in mind that negative flows will induce negative costs, which can be used to model revenues. Further, a bidirectional `Connection` (if `lb < 0`, which is the default, or if `capacity` is used) with a positive `cost` will lead to negative costs for the reverse flow. If you do not want this, split the `Connection` into two separate ones, each being unidirectional (with `lb: 0`). Remember, that these can share the same "capacity" (which is then set as`ub`), even when using `decision:value` or `col@file` as value. + +- **mandatory:** no +- **default:** $-$ +- **values:** numeric +- **unit:** monetary (per energy) + + +### `loss` + +Fractional loss when transferring energy. Per default, this loss occurs "at the destination", which means that for a loss of 5%, set as `loss: 0.05`, and considering a `Snapshot` where the `Connection` has a flow value of `100`, it will "extract" `100` from `node_from` and "inject" `95` into `node_to`. Since the flow variable is given as power, this would, e.g., translate to consuming 200 units of energy at `node_from` and injecting 190 units at `node_to`, if the `Snapshot` duration is 2 hours. Refer to `loss_mode` for more information on how to modify this behaviour. + +- **mandatory:** no +- **default:** $0$ +- **values:** ``\in [0, 1]`` +- **unit:** - + + +### `loss_mode` + +Configures where the fractional loss (set by `loss`) occurs. `to` means that the loss occurs at the destination, `from` means that the loss occurs at the source, and `split` means that the loss is split between source and destination. Not that for most cases `to` is the correct choice, as it is the most common behaviour - the other options, e.g., lead to the possibility of drawing at a higher power from the `node_from` than the `capacity` of the Connection "allows". + +- **mandatory:** no +- **default:** $to$ +- **values:** to, from, split +- **unit:** - + + +### `delay` + +Delay between withdrawing energy from `node_from` and injecting it into `node_to`. This can be used to model, e.g., river flows, where the water takes some time to travel from one point to another (e.g., in a cascade of reservoirs). Note that this delay is always "cyclic" meaning if it extends over the modeling period, it will be "wrapped around", starting again at the beginning of the period. Further, the flow is always withdrawn at Snapshot `t` and injected at `t + delay`. + +- **mandatory:** no +- **default:** $0$ +- **values:** ``\in [0, \infty]`` +- **unit:** hours + + +### `build_priority` + +Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** - + + +## Detailed reference + + +### Expressions + +#### `in` + +:::{admonition} How to access this expression? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_connection").exp.in +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_connection").exp.in +``` + +::: + +Full implementation and all details: [`connection/exp_in @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/connection/exp_in.jl) + +> The construction of a Connection's in/out expressions is directly done in `_connection_var_flow!(...)`, the function that constructs the variable `flow`. +#### `out` + +:::{admonition} How to access this expression? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_connection").exp.out +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_connection").exp.out +``` + +::: + +Full implementation and all details: [`connection/exp_out @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/connection/exp_out.jl) + +> The construction of a Connection's in/out expressions is directly done in `_connection_var_flow!(...)`, the function that constructs the variable `flow`. +#### `pf_flow` + +:::{admonition} How to access this expression? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_connection").exp.pf_flow +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_connection").exp.pf_flow +``` + +::: + +Full implementation and all details: [`connection/exp_pf_flow @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/connection/exp_pf_flow.jl) + +> Construct the `JuMP.AffExpr` holding the PTDF based flow of this `Connection`. +> +>   +> +> This needs the global addon `Powerflow` with proper settings for `mode`, as well as properly configured power flow parameters for this `Connection` (`pf_V`, `pf_I`, `pf_X`, ...). +### Variables + +#### `flow` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_connection").var.flow +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_connection").var.flow +``` + +::: + +Full implementation and all details: [`connection/var_flow @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/connection/var_flow.jl) + +> Add the variable representing the flow of this `connection` to the `model`. This can be accessed via `connection.var.flow[t]`. +> +>   +> +> Additionally, the flow gets "injected" at the `Node`s that the `connection` is connecting, resulting in +> +>   +> +$$ +\begin{aligned} +& \text{connection.node}_{from}\text{.injection}_t = \text{connection.node}_{from}\text{.injection}_t - \text{flow}_t, \qquad \forall t \in T \\ +& \text{connection.node}_{to}\text{.injection}_t = \text{connection.node}_{to}\text{.injection}_t + \text{flow}_t, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> For "PF controlled" `Connection`s (ones that define the necessary power flow parameters), the flow variable may not be constructed (depending on specific power flow being used). The automatic result extraction will detect this and return the correct values either way. Accessing it manually can be done using `connection.exp.pf_flow[t]`. +### Constraints + +#### `flow_bounds` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_connection").con.flow_bounds +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_connection").con.flow_bounds +``` + +::: + +Full implementation and all details: [`connection/con_flow_bounds @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/connection/con_flow_bounds.jl) + +> Add the constraint defining the bounds of the flow (related to `connection`) to the `model`. +> +>   +> +> Specifying `capacity` will lead to symmetric bounds ($\text{lb} := -capacity$ and $\text{ub} := capacity$), while asymmetric bounds can be set by explicitly specifying `lb` and `ub`. +> +>   +> +:::{note} + +Usage of `etdf` is currently not fully tested, and not documented. +::: +> Upper and lower bounds can be "infinite" (by not setting them) resulting in the respective constraints not being added, and the flow variable therefore being (partially) unconstrained. Depending on the configuration the `flow` is calculated differently: +> +>   +> +> * if `connection.etdf` is set, it is based on an ETDF sum flow, +> * if `connection.exp.pf_flow` is available, it equals this +> * else it equal `connection.var.flow` +> +>   +> +> This flow is then constrained: +> +>   +> +$$ +\begin{aligned} +& \text{flow}_t \geq \text{lb}, \qquad \forall t \in T \\ +& \text{flow}_t \leq \text{ub}, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> +>   +> +:::{admonition} **Constraint safety** +:class: note + +The lower and upper bound constraint are subject to penalized slacks. +::: +### Objectives + +#### `cost` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_connection").obj.cost +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_connection").obj.cost +``` + +::: + +Full implementation and all details: [`connection/obj_cost @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/connection/obj_cost.jl) + +> Add the (potential) cost of this `connection` to the global objective function. +> +>   +> +> The `connection.cost` setting introduces a fixed cost of "transportation" to the flow of this `Connection`. It is based on the directed flow. This means that flows in the "opposite" direction will lead to negative costs: +> +>   +> +$$ +\sum_{t \in T} \text{flow}_t \cdot \text{cost}_t \cdot \omega_t +$$ +> +>   +> +> Here $\omega_t$ is the weight of `Snapshot` `t`. +> +>   +> +:::{admonition} **Costs for flows in both directions** +:class: note + +If you need to apply a cost term to the absolute value of the flow, consider splitting the `Connection` into two different ones, in opposing directions, and including `lb = 0`. +::: diff --git a/_sources/pages/manual/yaml/core/decision.md.txt b/_sources/pages/manual/yaml/core/decision.md.txt new file mode 100644 index 0000000..2848449 --- /dev/null +++ b/_sources/pages/manual/yaml/core/decision.md.txt @@ -0,0 +1,372 @@ +# Decision + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +A `Decision` represents a basic decision variable in the model that can be used as input for various other core component's settings, as well as have associated costs. + + +## Parameters + +### `lb` + +Minimum size of the decision value (considered for each "unit" if count allows multiple "units"). + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** - + + +### `ub` + +Maximum size of the decision value (considered for each "unit" if count allows multiple "units"). + +- **mandatory:** no +- **default:** $+\infty$ +- **values:** numeric +- **unit:** - + + +### `cost` + +Cost that the decision value induces, given as ``cost \cdot value``. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** monetary (per value) + + +### `fixed_value` + +If `mode: fixed`, this value is used as the fixed value of the decision. This can be useful if this `Decision` was used in a previous optimization and its value should be fixed to that value in the next optimization (applying it where ever it is used, instead of needing to find all usages). Furthermore, this allows extracting the dual value of the constraint that fixes the value, assisting in approaches like Benders decomposition. Note that this does not change the induced cost in any way. + +- **mandatory:** no +- **default:** $-$ +- **values:** numeric +- **unit:** - + + +### `fixed_cost` + +This setting activates a "fixed cost" component for this decision variable, which requires that the model's problem type allows for binary variables (e.g., `MILP`). This can be used to model fixed costs that are only incurred if the decision variable is active (e.g., a fixed cost for an investment that is only incurred if the investment is made). If the decision is `0`, no fixed costs have to be paid; however, if the decision is greater than `0`, the fixed cost is incurred. Note that after deciding to activate the decision, the overall value is still determined in the usual (continuous) way, incuring the (variable) `cost` as well. More complex cost functions can be modelled by switching to mode `sos1` or `sos2` and using the `sos` parameter. + +- **mandatory:** no +- **default:** $-$ +- **values:** - +- **unit:** monetary + + +### `mode` + +Type of the decision variable that is constructed. `linear` results in a continuous decision, `integer` results in a integer variable, `binary` constrains it to be either `0` or `1`. `sos1` and `sos2` can be used to activate SOS1 or SOS2 mode (used for piecewise linear costs). See `fixed_value` if setting this to `fixed`. + +- **mandatory:** no +- **default:** $linear$ +- **values:** `linear`, `binary`, `integer`, `sos1`, `sos2`, `fixed` +- **unit:** - + + +### `sos` + +TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example). + +- **mandatory:** no +- **default:** $-$ +- **values:** list +- **unit:** - + + +### `build_priority` + +Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others. + +- **mandatory:** no +- **default:** $1000$ +- **values:** numeric +- **unit:** - + + +## Detailed reference + + +### Expressions + +### Variables + +#### `fixed` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").var.fixed +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").var.fixed +``` + +::: + +Full implementation and all details: [`decision/var_fixed @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/var_fixed.jl) + +> to be added +#### `sos` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").var.sos +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").var.sos +``` + +::: + +Full implementation and all details: [`decision/var_sos @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/var_sos.jl) + +> to be added +#### `value` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").var.value +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").var.value +``` + +::: + +Full implementation and all details: [`decision/var_value @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/var_value.jl) + +> Add the variable describing the `value` of this `decision` to the `model`. If lower and upper bounds (`decision.lb` and `decision.ub`) are the same, the variable will immediately be fixed to that value. This can be accessed via `decision.var.value`. +### Constraints + +#### `fixed` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").con.fixed +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").con.fixed +``` + +::: + +Full implementation and all details: [`decision/con_fixed @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/con_fixed.jl) + +> to be added +#### `sos1` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").con.sos1 +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").con.sos1 +``` + +::: + +Full implementation and all details: [`decision/con_sos1 @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/con_sos1.jl) + +> to be added +#### `sos2` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").con.sos2 +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").con.sos2 +``` + +::: + +Full implementation and all details: [`decision/con_sos2 @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/con_sos2.jl) + +> to be added +#### `sos_value` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").con.sos_value +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").con.sos_value +``` + +::: + +Full implementation and all details: [`decision/con_sos_value @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/con_sos_value.jl) + +> to be added +### Objectives + +#### `fixed` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").obj.fixed +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").obj.fixed +``` + +::: + +Full implementation and all details: [`decision/obj_fixed @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/obj_fixed.jl) + +> to be added ``` +#### `sos` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").obj.sos +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").obj.sos +``` + +::: + +Full implementation and all details: [`decision/obj_sos @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/obj_sos.jl) + +> Add the cost defined by the SOS-based value of this `Decision` to the `model`. +#### `value` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_decision").obj.value +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_decision").obj.value +``` + +::: + +Full implementation and all details: [`decision/obj_value @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/decision/obj_value.jl) + +> Add the cost defined by the value of this `Decision` to the `model`: +> +>   +> +$$ +\text{value} \cdot \text{cost} +$$ diff --git a/_sources/pages/manual/yaml/core/node.md.txt b/_sources/pages/manual/yaml/core/node.md.txt new file mode 100644 index 0000000..705a30e --- /dev/null +++ b/_sources/pages/manual/yaml/core/node.md.txt @@ -0,0 +1,425 @@ +# Node + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +A `Node` represents a basic intersection/hub for energy flows. This can for example be some sort of bus (for electrical systems). It enforces a nodal balance equation (= "energy that flows into it must flow out") for every [`Snapshot`](@ref). Enabling the internal state of the `Node` allows it to act as energy storage, modifying the nodal balance equation. This allows using `Node`s for various storage tasks (like batteries, hydro reservoirs, heat storages, ...). + +:::{admonition} **Basic Examples** +:class: dropdown + +A `Node` that represents an electrical bus: + +```yaml +bus: + type: Node + carrier: electricity +``` + +A `Node` that represents a simplified hydrogen storage: + +```yaml +store: + type: Node + carrier: hydrogen + has_state: true + state_lb: 0 + state_ub: 50 +``` + +::: + +## Parameters + +### `carrier` + +`Carrier` of this `Node`. All connecting components need to respect that. + +- **mandatory:** yes +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `has_state` + +If `true`, the `Node` is considered to have an internal state ("stateful `Node`"). This allows it to act as energy storage. Connect `Connection`s or `Unit`s to it, acting as charger/discharger. + +- **mandatory:** no +- **default:** $false$ +- **values:** `true`, `false` +- **unit:** - + + +### `state_lb` + +Lower bound of the internal state, requires `has_state = true`. + +- **mandatory:** no +- **default:** $-\infty$ +- **values:** numeric, `col@file`, `decision:value` +- **unit:** energy + + +### `state_ub` + +Upper bound of the internal state, requires `has_state = true`. + +- **mandatory:** no +- **default:** $+\infty$ +- **values:** numeric, `col@file`, `decision:value` +- **unit:** energy + + +### `state_cyclic` + +Controls how the state considers the boundary between last and first `Snapshot`. `disabled` disables cyclic behaviour of the state (see also `state_initial`), `eq` leads to the state at the end of the year being the initial state at the beginning of the year, while `geq` does the same while allowing the end-of-year state to be higher (= "allowing to destroy energy at the end of the year"). + +- **mandatory:** no +- **default:** $eq$ +- **values:** `eq`, `geq`, or `disabled` +- **unit:** - + + +### `state_initial` + +Sets the initial state. Must be used in combination with `state_cyclic = disabled`. + +- **mandatory:** no +- **default:** $-$ +- **values:** numeric +- **unit:** energy + + +### `state_final` + +Sets the final state. Must be used in combination with `state_cyclic = disabled`. + +- **mandatory:** no +- **default:** $-$ +- **values:** numeric +- **unit:** energy + + +### `state_percentage_loss` + +Per `Snapshot` percentage loss of state (losing 1% should be set as `0.01`). + +- **mandatory:** no +- **default:** $0$ +- **values:** ``\in [0, 1]`` +- **unit:** - + + +### `nodal_balance` + +Can only be used for `has_state = false`. `enforce` forces total injections to always be zero (similar to Kirchhoff's current law), `create` allows "supply < demand", `destroy` allows "supply > demand", at this `Node`. + +- **mandatory:** no +- **default:** $enforce$ +- **values:** `enforce`, `destroy`, or `create` +- **unit:** - + + +### `sum_window_size` + +TODO. + +- **mandatory:** no +- **default:** $-$ +- **values:** integer +- **unit:** - + + +### `sum_window_step` + +TODO. + +- **mandatory:** no +- **default:** $1$ +- **values:** integer +- **unit:** - + + +### `build_priority` + +Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** - + + +## Detailed reference + + +### Expressions + +#### `injection` + +:::{admonition} How to access this expression? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_node").exp.injection +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_node").exp.injection +``` + +::: + +Full implementation and all details: [`node/exp_injection @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/node/exp_injection.jl) + +> Add an empty (`JuMP.AffExpr(0)`) expression to the `node` that keeps track of feed-in and withdrawal of energy. +> +>   +> +> This constructs the expression $\text{injection}_t, \forall t \in T$ that is utilized in `node.con.nodalbalance`. Core components (`Connection`s, `Profile`s, and `Unit`s) that feed energy into this node add to it, all others subtract from it. A stateless node forces this nodal balance to always equal `0` which essentially describes "generation = demand". +### Variables + +#### `pf_theta` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_node").var.pf_theta +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_node").var.pf_theta +``` + +::: + +Full implementation and all details: [`node/var_pf_theta @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/node/var_pf_theta.jl) + +> Construct the auxiliary phase angle variable for the `linear_angle` power flow algorithm. +> +>   +> +> This needs the global `Powerflow` addon, configured with `mode: linear_angle`, and constructs a variable `var_pf_theta` for each `Snapshot`. If the `pf_slack` property of this `Node` is set to `true`, it does not add a variable but sets `var_pf_theta[t] = 0` for each `Snapshot`. ``` +#### `state` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_node").var.state +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_node").var.state +``` + +::: + +Full implementation and all details: [`node/var_state @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/node/var_state.jl) + +> Add the variable representing the state of this `node` to the `model`, if `node.has_state == true`. This can be accessed via `node.var.state[t]`. +> +>   +> +> Additionally, if the state's initial value is specified via `state_initial` the following gets added: +> +>   +> +$$ +\text{state}_1 = \text{state}_{initial} +$$ +### Constraints + +#### `last_state` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_node").con.last_state +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_node").con.last_state +``` + +::: + +Full implementation and all details: [`node/con_last_state @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/node/con_last_state.jl) + +> Add the constraint defining the bounds of the `node`'s state during the last Snapshot to the `model`, if `node.has_state == true`. +> +>   +> +> This is necessary since it could otherwise happen, that the state following the last Snapshot is actually not feasible (e.g. we could charge a storage by more than it's state allows for). The equations are based on the construction of the overall state variable. +> +>   +> +$$ +\begin{aligned} +& \text{state}_{end} \cdot \text{factor}^\omega_t + \text{injection}_{end} \cdot \omega_t \geq \text{state}_{lb} \\ +& \text{state}_{end} \cdot \text{factor}^\omega_t + \text{injection}_{end} \cdot \omega_t \leq \text{state}_{ub} +\end{aligned} +$$ +> +>   +> +> Here $\omega_t$ is the `weight` of `Snapshot` `t`, and $\text{factor}$ is either `1.0` (if there are now percentage losses configured), or `(1.0 - node.state_percentage_loss)` otherwise. +> +>   +> +:::{admonition} **Constraint safety** +:class: note + +The lower and upper bound constraint are subject to penalized slacks. +::: +#### `nodalbalance` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_node").con.nodalbalance +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_node").con.nodalbalance +``` + +::: + +Full implementation and all details: [`node/con_nodalbalance @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/node/con_nodalbalance.jl) + +> Add the constraint describing the nodal balance to the `model`. +> +>   +> +> Depending on whether the `node` is stateful or not, this constructs different representations: +> +>   +> +> `if node.has_state == true` +> +$$ +\begin{aligned} +& \text{state}_t = \text{state}_{t-1} \cdot \text{factor}^\omega_{t-1} + \text{injection}_{t-1} \cdot \omega_{t-1}, \qquad \forall t \in T \setminus \{1\} \\ +\\ +& \text{state}_1 = \text{state}_{end} \cdot \text{factor}^\omega_{end} + \text{injection}_{end} \cdot \omega_{end} +\end{aligned} +$$ +> +>   +> +> +>   +> +> Here $\omega_t$ is the `weight` of `Snapshot` `t`, and $\text{factor}$ is either `1.0` (if there are now percentage losses configured), or `(1.0 - node.state_percentage_loss)` otherwise. $\text{injection}_{t}$ describes the overall injection (all feed-ins minus all withdrawals). $end$ indicates the last snapshot in $T$. Depending on the setting of `state_cyclic` the second constraint is written as $=$ (`"eq"`) or $\leq$ (`"leq"`). The latter allows the destruction of excess energy at the end of the total time period to help with feasibility. +> +>   +> +> `if node.has_state == false` +> +$$ +\begin{aligned} +& \text{injection}_{t} = 0, \qquad \forall t \in T \\ +\end{aligned} +$$ +> +>   +> +> +>   +> +> This equation can further be configured using the `nodal_balance` parameter, which accepts `enforce` (resulting in $=$), `create` (resulting in $\leq$; allowing the creation of energy - or "negative injections"), and `destroy` ( resulting in $\geq$; allowing the destruction of energy - or "positive injections"). This can be used to model some form of energy that can either be sold (using a `destroy` `Profile` connected to this `Node`), or "wasted into the air" using the `destroy` setting of this `Node`. +#### `state_bounds` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_node").con.state_bounds +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_node").con.state_bounds +``` + +::: + +Full implementation and all details: [`node/con_state_bounds @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/node/con_state_bounds.jl) + +> Add the constraint defining the bounds of the `node`'s state to the `model`, if `node.has_state == true`. +> +>   +> +$$ +\begin{aligned} +& \text{state}_t \geq \text{state}_{lb}, \qquad \forall t \in T \\ +& \text{state}_t \leq \text{state}_{ub}, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +:::{admonition} **Constraint safety** +:class: note + +The lower and upper bound constraint are subject to penalized slacks. +::: +### Objectives + diff --git a/_sources/pages/manual/yaml/core/profile.md.txt b/_sources/pages/manual/yaml/core/profile.md.txt new file mode 100644 index 0000000..6294d95 --- /dev/null +++ b/_sources/pages/manual/yaml/core/profile.md.txt @@ -0,0 +1,350 @@ +# Profile + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +A `Profile` allows representing "model boundaries" - parts of initial problem that are not endogenously modelled - with a support for time series data. Examples are hydro reservoir inflows, electricity demand, importing gas, and so on. Besides modelling fixed profiles, they also allow different ways to modify the value endogenously. + +:::{admonition} **Basic Examples** +:class: dropdown + +A `Profile` that depicts a fixed electricity demand: + +```yaml +demand_XY: + type: Profile + carrier: electricity + node_from: grid + value: demand_XY@input_file +``` + +A `Profile` that handles cost of fuel: + +```yaml +fuel_gas: + type: Profile + carrier: gas + node_to: country_gas_grid + mode: create + cost: 100.0 +``` + +A `Profile` that handles CO2 emission costs: + +```yaml +co2_cost: + type: Profile + carrier: co2 + node_from: total_co2 + mode: destroy + cost: 150.0 +``` + +A `Profile` that handles selling electricity: + +```yaml +sell_electricity: + type: Profile + carrier: electricity + node_from: internal_grid_node + mode: destroy + cost: -30.0 +``` + +::: + +## Parameters + +### `carrier` + +`Carrier` of this `Profile`. Must match the `Carrier` of the `Node` that this connects to. + +- **mandatory:** yes +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `value` + +The concrete value of this `Profile` - either static or as time series. Only applicable if `mode: fixed`. + +- **mandatory:** no +- **default:** $-$ +- **values:** numeric, `col@file` +- **unit:** power + + +### `node_from` + +Name of the `Node` that this `Profile` draws energy from. Exactly one of `node_from` and `node_to` must be set. + +- **mandatory:** no +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `node_to` + +Name of the `Node` that this `Profile` feeds energy to. Exactly one of `node_from` and `node_to` must be set. + +- **mandatory:** no +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `mode` + +The mode of operation of this `Profile`. `fixed` uses the supplied `value`, `ranged` allows ranging between `lb` and `ub`, while `create` (must specify `node_to`) and `destroy` (must specify `node_from`) handle arbitrary energy flows that are bounded from below by `0`. Use `fixed` if you want to fix the value of the `Profile` to a specific value, e.g., a given energy demand. Use `create` to "import" energy into the model, e.g., from a not explicitly modelled gas market, indcucing a certain `cost` for buying that energy. Use `destroy` to "export" energy from the model, e.g., to handle CO2 going into the atmosphere (which may be taxed, etc., by the `cost` of this `Profile`). Use `ranged` if you need more fine grained control over the value of the `Profile`, than what `create` and `destroy` allow (e.g., a grid limited energy supplier). + +- **mandatory:** no +- **default:** $fixed$ +- **values:** - +- **unit:** - + + +### `lb` + +The lower bound of the range of this `Profile` (must be used together with `mode: ranged`). + +- **mandatory:** no +- **default:** $-\infty$ +- **values:** numeric +- **unit:** power + + +### `ub` + +The upper bound of the range of this `Profile` (must be used together with `mode: ranged`). + +- **mandatory:** no +- **default:** $+\infty$ +- **values:** numeric +- **unit:** power + + +### `cost` + +Cost per unit of energy that this `Profile` injects or withdraws from a `Node`. Refer to the basic examples to see how this can be combined with `mode` for different use cases. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** monetary per energy + + +### `build_priority` + +Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** - + + +## Detailed reference + + +### Expressions + +#### `value` + +:::{admonition} How to access this expression? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_profile").exp.value +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_profile").exp.value +``` + +::: + +Full implementation and all details: [`profile/exp_value @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/profile/exp_value.jl) + +> Construct the `JuMP.AffExpr` that keeps the total value of this `Profile` for each `Snapshot`. +> +>   +> +> This is skipped if the `value` of this `Profile` is handled by an `Expression`. Otherwise it is initialized based on `profile.value`. +### Variables + +#### `aux_value` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_profile").var.aux_value +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_profile").var.aux_value +``` + +::: + +Full implementation and all details: [`profile/var_aux_value @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/profile/var_aux_value.jl) + +> Add the variable that is used in this `Profile`s value to the `model`. +> +>   +> +> The variable `var_value[t]` is constructed and is linked to the correct `Node`s. There are different ways, IESopt interprets this, based on the setting of `profile.mode`: +> +>   +> +> 1. **fixed**: The value is already handled by the constant term of `profile.exp.value` and NO variable is constructed. +> 2. **create**, **destroy**, or **ranged**: This models the creation or destruction of energy - used mainly to represent model boundaries, and energy that comes into the model or leaves the model's scope. It is however important that `create` should mostly be used feeding into a `Node` (`profile.node_from = nothing`) and `destroy` withdrawing from a `Node` (`profile.node_to = nothing`). If `lb` and `ub` are defined, `ranged` can be used that allows a more detailed control over the `Profile`, specifying upper and lower bounds for every `Snapshot`. See `_profile_con_value_bounds!(profile::Profile)` for details on the specific bounds for each case. +> +>   +> +> This variable is added to the `profile.exp.value`. Additionally, the energy (that `profile.exp.value` represents) gets "injected" at the `Node`s that the `profile` is connected to, resulting in +> +>   +> +$$ +\begin{aligned} +& \text{profile.node}_{from}\text{.injection}_t = \text{profile.node}_{from}\text{.injection}_t - \text{value}_t, \qquad \forall t \in T \\ +& \text{profile.node}_{to}\text{.injection}_t = \text{profile.node}_{to}\text{.injection}_t + \text{value}_t, \qquad \forall t \in T +\end{aligned} +$$ +### Constraints + +#### `value_bounds` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_profile").con.value_bounds +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_profile").con.value_bounds +``` + +::: + +Full implementation and all details: [`profile/con_value_bounds @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/profile/con_value_bounds.jl) + +> Add the constraint defining the bounds of this `profile` to the `model`. +> +>   +> +> This heavily depends on the `mode` setting, as it does nothing if the `mode` is set to `fixed`, or the `value` is actually controlled by an `Expression`. The variable can be accessed via `profile.var.aux_value[t]`, but using the normal result extraction is recommended, since that properly handles the `profile.exp.value` instead. +> +>   +> +> Otherwise: +> +>   +> +> `if profile.mode === :create or profile.mode === :destroy` +> +$$ +\begin{aligned} +& \text{aux_value}_t \geq 0, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> +>   +> +> `if profile.mode === :ranged` +> +$$ +\begin{aligned} +& \text{value}_t \geq \text{lb}_t, \qquad \forall t \in T \\ +& \text{value}_t \leq \text{ub}_t, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> +>   +> +> Here, `lb` and `ub` can be left empty, which drops the respective constraint. +### Objectives + +#### `cost` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_profile").obj.cost +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_profile").obj.cost +``` + +::: + +Full implementation and all details: [`profile/obj_cost @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/profile/obj_cost.jl) + +> Add the (potential) cost of this `Profile` to the global objective function. +> +>   +> +> The `profile.cost` setting specifies a potential cost for the creation ("resource costs", i.e. importing gas into the model) or destruction ("penalties", i.e. costs linked to the emission of CO2). It can have a unique value for every `Snapshot`, i.e. allowing to model a time-varying gas price throughout the year. +> +>   +> +> The contribution to the global objective function is as follows: +> +>   +> +$$ +\sum_{t\in T} \text{value}_t \cdot \text{profile.cost}_t \cdot \omega_t +$$ +> +>   +> +> Here $\omega_t$ is the `weight` of `Snapshot` `t`, and $\text{value}_t$ actually refers to the value of `profile.exp.value[t]` (and not only on the maybe non-existing variable). diff --git a/_sources/pages/manual/yaml/core/unit.md.txt b/_sources/pages/manual/yaml/core/unit.md.txt new file mode 100644 index 0000000..99940b1 --- /dev/null +++ b/_sources/pages/manual/yaml/core/unit.md.txt @@ -0,0 +1,859 @@ +# Unit + +```{note} +This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to [IESopt.jl](https://github.com/ait-energy/IESopt.jl) for any further details (which may require some familiarity with Julia). + +**If you spot incorrect math-mode rendering**, or similar issues, please [file an issue](https://github.com/ait-energy/iesopt/issues), since rendering documentation from Julia to Python is not the easiest task. +``` + +## Overview + +A `Unit` allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs. + +:::{admonition} **Basic Examples** +:class: dropdown + +A `Unit` that represents a basic gas turbine: + +```yaml +gas_turbine: + type: Unit + inputs: {gas: gas_grid} + outputs: {electricity: node, co2: total_co2} + conversion: 1 gas -> 0.4 electricity + 0.2 co2 + capacity: 10 out:electricity +``` + +A `Unit` that represents a basic wind turbine: + +```yaml +wind_turbine: + type: Unit + outputs: {electricity: node} + conversion: ~ -> 1 electricity + capacity: 10 out:electricity + availability_factor: wind_factor@input_data + marginal_cost: 1.7 per out:electricity +``` + +A `Unit` that represents a basic heat pump, utilizing a varying COP: + +```yaml +heatpump: + type: Unit + inputs: {electricity: grid} + outputs: {heat: heat_system} + conversion: 1 electricity -> cop@inputfile heat + capacity: 10 in:electricity +``` + +::: + +## Parameters + +### `conversion` + +The conversion expression describing how this `Unit` transforms energy. Specified in the form of "$\alpha \cdot carrier_1 + \beta \cdot carrier_2$ -> $\gamma \cdot carrier_3 + \delta \cdot carrier_4$". Coefficients allow simple numerical calculations, but are not allowed to include spaces (so e.g. `(1.0/9.0)` is valid). Coefficients are allowed to be `NumericalInput`s, resulting in `column@data_file` being a valid coefficient (this can be used e.g. for time-varying COPs of heatpumps). + +- **mandatory:** yes +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `capacity` + +Maximum capacity of this `Unit`, to be given in the format `X in/out:carrier` where `X` is the amount, `in` or `out` (followed by `:`) specifies whether the limit is to be placed on the in- our output of this `Unit`, and `carrier` specifies the respective `Carrier`. Example: `100 in:electricity` (to limit the "input rating"). + +- **mandatory:** yes +- **default:** $-$ +- **values:** value dir:carrier +- **unit:** - + + +### `outputs` + +Dictionary specifying the output "ports" of this `Unit`. Refer to the basic examples for the general syntax. + +- **mandatory:** yes +- **default:** $-$ +- **values:** dict +- **unit:** - + + +### `inputs` + +Dictionary specifying the input "ports" of this `Unit`. If not specified (= no explicit input), the `conversion` has to follow the form of `conversion: ~ -> ...`, indicating an "open" input. This may, e.g., be used for renewable energy sources, where the primary energy input (e.g., solar) is not explicitly modeled. + +- **mandatory:** no +- **default:** $-$ +- **values:** dict +- **unit:** - + + +### `availability` + +Time series (or fixed value) that limits the available capacity. If, e.g., `capacity: 100 out:electricity` and `availability: 70`, the available capacity will only be `70 electricity`. Can be used to model non-availability of power plants, e.g., due to maintenance. For time-varying availability of intermittent generators (e.g., wind), it's recommended (most of the time) to use `availability_factor` instead. + +- **mandatory:** no +- **default:** $+\infty$ +- **values:** numeric +- **unit:** power + + +### `availability_factor` + +Similar to `availability`, but given as factor of `capacity` instead. If, e.g., `capacity: 100 out:electricity` and `availability_factor: 0.7`, the available capacity will only be `70 electricity`. This is especially useful for intermittent generators, where the availability is not a fixed value, but depends on the weather, and can be passed, e.g., by setting `availability_factor: wind@input_data_file`. + +- **mandatory:** no +- **default:** $1$ +- **values:** ``\in [0, 1]`` +- **unit:** - + + +### `adapt_min_to_availability` + +If `true`, the minimal partial load will be influenced by the availability. Example: Consider a `Unit` with `capacity: 100 out:electricity`, a `min_conversion` of `0.4`, and an `availability_factor` of `0.5`. This entails having `50 electricity` available, while the minimal partial load is `40 electricity`. This results in the `Unit` at best operating only closely above the minimal partial load. Furthermore, an `availability_factor` below `0.4` would result in no feasible generation, besides shutting the `Unit` off. While this might be the intended mode of operation in many use cases, `adapt_min_to_availability` can change this: If set to `true`, this dynamically changes the minimal partial load. In the previous example, that means `(100 * 0.5) * 0.4 = 20 electricity` (the 50% minimum load are now based on the available 40), changing the overall behaviour (including efficiencies) as well as leading to feasible generations even when the `availability_factor` is below `0.4`. + +- **mandatory:** no +- **default:** $false$ +- **values:** `true`, `false` +- **unit:** - + + +### `marginal_cost` + +Marginal cost of the consumption/generation of one unit of energy of the specified carrier. Has to be given in the format `value per dir:carrier`, e.g. `3.5 per out:electricity` for a marginal cost of 3.5 monetary units per unit of electricity generated. + +- **mandatory:** no +- **default:** $0$ +- **values:** `value per dir:carrier` +- **unit:** monetary per energy + + +### `enable_ramp_up` + +Enables calculation of upward ramps. Ramping is based on the carrier specified in `capacity`. + +- **mandatory:** no +- **default:** $false$ +- **values:** `true`, `false` +- **unit:** - + + +### `enable_ramp_down` + +Enables calculation of downward ramps. Ramping is based on the carrier specified in `capacity`. + +- **mandatory:** no +- **default:** $false$ +- **values:** `true`, `false` +- **unit:** - + + +### `ramp_up_cost` + +Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** monetary per power + + +### `ramp_down_cost` + +Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** monetary per power + + +### `ramp_up_limit` + +Limits the allowed ramping up based on this factor of the total capacity. If `capacity: 100 in:electricity` with `ramp_up_limit: 0.2`, this limits the total increase of usage of electricity (on the input) to 20 units (power) per hour. For example, starting at an input of 35, after one hour the input has to be lesser than or equal to 55. If a `Snapshot`'s duration is set to, e.g., two hours, this would allow a total increase of 40 units. + +- **mandatory:** no +- **default:** $1$ +- **values:** ``\in [0, 1]`` +- **unit:** - + + +### `ramp_down_limit` + +Limits the allowed ramping down based on this factor of the total capacity. See `ramp_up_limit`. + +- **mandatory:** no +- **default:** $1$ +- **values:** ``\in [0, 1]`` +- **unit:** - + + +### `min_on_time` + +Minimum on-time of the `Unit`. If set, the `Unit` has to be on for at least this amount of time, after turning on. It is highly recommended to only use this with `unit_commitment: binary`, unless you know why it's fine to use with another mode. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** hours + + +### `min_off_time` + +Minimum off-time of the `Unit`. If set, the `Unit` has to be off for at least this amount of time, after turning off. It is highly recommended to only use this with `unit_commitment: binary`, unless you know why it's fine to use with another mode. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** hours + + +### `on_time_before` + +Time that this `Unit` has already been running before the optimization starts. Can be used in combination with `min_on_time`. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** hours + + +### `off_time_before` + +Time that this `Unit` has already been off before the optimization starts. Can be used in combination with `min_off_time`. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** hours + + +### `is_on_before` + +Number of `Unit`s that should be considered to have been running before the optimization starts. Can be used in combination with `on_time_before`, especially for `unit_count` greater than 1. + +- **mandatory:** no +- **default:** $1$ +- **values:** numeric +- **unit:** - + + +### `unit_commitment` + +Controls how the unit commitment of this `Unit` is handled. `linear` results in the ability to startup parts of the unit (so `0.314159` is a feasible amount of "turned on unit"), while `binary` restricts the `Unit` to either be on (converting the `conversion_at_min` + possible additional conversion above that minimum) or off (converting nothing); `integer` is needed to consider `binary` unit commitment for `Unit`s with more than 1 "grouped unit" (see `unit_count`). + +- **mandatory:** no +- **default:** $off$ +- **values:** `off`, `linear`, `binary`, `integer` +- **unit:** - + + +### `unit_count` + +Number of units aggregated in this `Unit`. Besides interacting with the mode of `unit_commitment`, this mainly is responsible for scaling the output (e.g. grouping 47 of the same wind turbine, ...). + +- **mandatory:** no +- **default:** $1$ +- **values:** numeric +- **unit:** - + + +### `min_conversion` + +If `unit_commitment` is not set to `off`, this specifies the percentage that is considered to be the minimal feasible partial load this `Unit` can operate at. Operating below that setpoint is not allowed, at that point the `conversion_at_min` coefficients are used, and above that they are scaled to result in `conversion` when running at full capacity. + +- **mandatory:** no +- **default:** $-$ +- **values:** ``\in [0, 1]`` +- **unit:** - + + +### `conversion_at_min` + +The conversion expression while running on the minimal partial load. Only applicable if `unit_commitment` is not `off` and `min_conversion` is explicitly set. Follows the same form as `conversion`. + +- **mandatory:** no +- **default:** $-$ +- **values:** string +- **unit:** - + + +### `startup_cost` + +Costs per startup (also applicable if startups are not binary or integer). This is necessary to allow `conversion_at_min` to have (at least partially) the effect that one expects, if `unit_commitment: linear`. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** monetary per start + + +### `build_priority` + +Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others. + +- **mandatory:** no +- **default:** $0$ +- **values:** numeric +- **unit:** - + + +## Detailed reference + + +### Expressions + +### Variables + +#### `conversion` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").var.conversion +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").var.conversion +``` + +::: + +Full implementation and all details: [`unit/var_conversion @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/var_conversion.jl) + +> Add the variable describing the `unit`'s conversion to the `model`. +> +>   +> +> This can be accessed via `unit.var.conversion[t]`; this does not describe the full output of the `Unit` since that maybe also include fixed generation based on the `ison` variable. +> +>   +> +:::{info} + +This applies some heavy recalculation of efficiencies to account for minimum load and so on, that are currently not fully documented. This essentially comes down to the following: As long as minimum load is not enabled, that is rather simple (using the conversion expression to withdraw energy from the inputs and push energy into the outputs). If a separate minimum load conversion is specified it results in the following: (1) if running at minimum load the supplied minimum load conversion will be used; (2) if running at maximum capacity the "normal" conversion expression will be used; (3) for any point in-between a linear interpolation scales up all coefficients of the conversion expression to "connect" case (1) and (2). +::: +#### `ison` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").var.ison +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").var.ison +``` + +::: + +Full implementation and all details: [`unit/var_ison @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/var_ison.jl) + +> Add the variable describing the current "online" state of the `unit` to the `model`. +> +>   +> +> The variable can be further parameterized using the `unit.unit_commitment` setting ("linear", "binary", "integer"). It will automatically enforce the constraints $0 \leq \text{ison} \leq \text{unitcount}$, with $\text{unitcount}$ describing the number of units that are aggregated in this `unit` (set by `unit.unit_count`). This can be accessed via `unit.var.ison[t]`. +#### `ramp` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").var.ramp +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").var.ramp +``` + +::: + +Full implementation and all details: [`unit/var_ramp @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/var_ramp.jl) + +> Add the variable describing the per-snapshot ramping to the `model`. +> +>   +> +> This adds two variables per snapshot to the model (if the respective setting `unit.enable_ramp_up` or `unit.enable_ramp_down` is activated). Both are preconstructed with a fixed lower bound of `0`. This describes the amount of change in conversion that occurs during the current snapshot. These can be accessed via `unit.var.ramp_up[t]` and `unit.var.ramp_down[t]`. +> +>   +> +> These variables are only used for ramping **costs**. The limits are enforced directly on the conversion, which means this variable only exists if costs are specified! +#### `startup` + +:::{admonition} How to access this variable? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").var.startup +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").var.startup +``` + +::: + +Full implementation and all details: [`unit/var_startup @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/var_startup.jl) + +> Add the variable describing the per-snapshot startup to the `model`. +> +>   +> +> This adds a variable per snapshot to the model (if the respective setting `unit.unit_commitment` is activated). The variable can be further parameterized using the `unit.unit_commitment` setting ("linear", "binary", "integer"). It will automatically enforce the constraints $0 \leq \text{startup} \leq \text{unitcount}$, with $\text{unitcount}$ describing the number of units that are aggregated in this `unit` (set by `unit.unit_count`). This describes the startup that happens during the current snapshot and can be accessed via `unit.var.startup`. +### Constraints + +#### `conversion_bounds` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").con.conversion_bounds +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").con.conversion_bounds +``` + +::: + +Full implementation and all details: [`unit/con_conversion_bounds @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/con_conversion_bounds.jl) + +> Add the constraint defining the `unit`'s conversion bounds to the `model`. +> +>   +> +> This makes use of the current `min_capacity` (describing the lower limit of conversion; either 0 if no minimum load applies or the respective value of the minimum load) as well as the `online_capacity` (that can either be the full capacity if unit commitment is disabled, or the amount that is currently active). +> +>   +> +> Depending on how the "availability" of this `unit` is handled it constructs the following constraints: +> +>   +> +> `if !isnothing(unit.availability)` +> +$$ +\begin{aligned} +& \text{conversion}_t \geq \text{capacity}_{\text{min}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{capacity}_{\text{online}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{availability}_t, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> +>   +> +> This effectively results in $\text{conversion}_t \leq \min(\text{capacity}_{\text{online}, t}, \text{availability}_t)$. +> +>   +> +> `if !isnothing(unit.availability_factor)` +> +$$ +\begin{aligned} +& \text{conversion}_t \geq \text{capacity}_{\text{min}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{capacity}_{\text{online}, t} \cdot \text{availability}_{\text{factor}, t}, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> +>   +> +:::{info} + +If one is able to choose between using `availability` or `availability_factor` (e.g. for restricting available capacity during a planned revision to half the units capacity), enabling `availability_factor` (in this example 0.5) will result in a faster model (build and probably solve) since it makes use of one less constraint. +::: +> If no kind of availability limiting takes place, the following bounds are enforced: +> +>   +> +$$ +\begin{aligned} +& \text{conversion}_t \geq \text{capacity}_{\text{min}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{capacity}_{\text{online}, t}, \qquad \forall t \in T +\end{aligned} +$$ +#### `ison` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").con.ison +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").con.ison +``` + +::: + +Full implementation and all details: [`unit/con_ison @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/con_ison.jl) + +> Construct the upper bound for `var_ison`, based on `unit.unit_count`, if it is handled by an external `Decision`. +#### `min_onoff_time` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").con.min_onoff_time +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").con.min_onoff_time +``` + +::: + +Full implementation and all details: [`unit/con_min_onoff_time @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/con_min_onoff_time.jl) + +> Add the constraints modeling min on- or off-time of a `Unit` to the `model`. +> +>   +> +> This constructs the constraints +> +>   +> +$$ +\begin{align} +& \sum_{t' = t}^{t + \text{min\_on\_time}} ison_{t'} >= \text{min\_on\_time} \cdot (ison_t - ison_{t-1}) \qquad \forall t \in T \\ +& \sum_{t' = t}^{t + \text{min\_off\_time}} (1 - ison_{t'}) >= \text{min\_off\_time} \cdot (ison_{t-1} - ison_t) \qquad \forall t \in T +\end{align} +$$ +> +>   +> +> respecting `on_time_before` and `off_time_before`, and `is_on_before`. See the code for more details. +> +>   +> +:::{admonition} **Aggregated units** +:class: info + +This is currently not fully adapted to account for `Unit`s with `unit_count > 1`. +::: +#### `ramp` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").con.ramp +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").con.ramp +``` + +::: + +Full implementation and all details: [`unit/con_ramp @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/con_ramp.jl) + +> Add the auxiliary constraint that enables calculation of per snapshot ramping to the `model`. +> +>   +> +> Depending on whether ramps are enabled, none, one, or both of the following constraints are constructed: +> +>   +> +$$ +\begin{aligned} +& \text{ramp}_{\text{up}, t} \geq \text{conversion}_{t} - \text{conversion}_{t-1}, \qquad \forall t \in T \\ +& \text{ramp}_{\text{down}, t} \geq \text{conversion}_{t-1} - \text{conversion}_{t}, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if: +> +>   +> +> * `out[5] = 100` and `out[4] = 50`, then `ramp_up[5] = 50` and `ramp_down[5] = 0` +> * `ramp_up[1] = ramp_down[1] = 0` +> +>   +> +:::{info} + +This currently does not support pre-setting the initial states of the unit (it can be done manually but there is no exposed parameter), which will be implemented in the future to allow for easy / correct rolling optimization runs. +::: +#### `ramp_limit` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").con.ramp_limit +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").con.ramp_limit +``` + +::: + +Full implementation and all details: [`unit/con_ramp_limit @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/con_ramp_limit.jl) + +> Add the constraint describing the ramping limits of this `unit` to the `model`. +> +>   +> +> This makes use of the maximum capacity of the `unit`, which is just the total installed capacity. Both, up- and downwards ramps can be enabled separately (via `unit.ramp_up_limit` and `unit.ramp_down_limit`), resulting in either or both of: +> +>   +> +$$ +\begin{aligned} +& \text{ramp}_{\text{up}, t} \leq \text{ramplimit}_\text{up} \cdot \text{capacity}_\text{max} \cdot \omega_t, \qquad \forall t \in T \\ +& \text{ramp}_{\text{down}, t} \leq \text{ramplimit}_\text{down} \cdot \text{capacity}_\text{max} \cdot \omega_t, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> This does **not** make use of the ramping variable (that is only used for costs - if there are costs). +> +>   +> +> This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if: +> +>   +> +> * `out[5] = 100` and `out[4] = 50`, then `ramp_up[5] = 50` and `ramp_down[5] = 0` +> * `ramp_up[1] = ramp_down[1] = 0` +#### `startup` + +:::{admonition} How to access this constraint? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").con.startup +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").con.startup +``` + +::: + +Full implementation and all details: [`unit/con_startup @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/con_startup.jl) + +> Add the auxiliary constraint that enables calculation of per snapshot startup to the `model`. +> +>   +> +> Depending on whether startup handling is enabled, the following constraint is constructed: +> +>   +> +$$ +\begin{aligned} +& \text{startup}_{\text{up}, t} \geq \text{ison}_{t} - \text{ison}_{t-1}, \qquad \forall t \in T +\end{aligned} +$$ +> +>   +> +> This calculates the startup that happens from the PREVIOUS snapshot to this one. That means that if: +> +>   +> +> * `ison[5] = 1` and `ison[4] = 0`, then `startup[5] = 1` +### Objectives + +#### `marginal_cost` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").obj.marginal_cost +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").obj.marginal_cost +``` + +::: + +Full implementation and all details: [`unit/obj_marginal_cost @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/obj_marginal_cost.jl) + +> Add the (potential) cost of this `unit`'s conversion (`unit.marginal_cost`) to the global objective function. +> +>   +> +$$ +\sum_{t \in T} \text{conversion}_t \cdot \text{marginalcost}_t \cdot \omega_t +$$ +#### `ramp_cost` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").obj.ramp_cost +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").obj.ramp_cost +``` + +::: + +Full implementation and all details: [`unit/obj_ramp_cost @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/obj_ramp_cost.jl) + +> Add the (potential) cost of this `unit`'s ramping to the global objective function. +> +>   +> +> To allow for finer control, costs of up- and downwards ramping can be specified separately (using `unit.ramp_up_cost` and `unit.ramp_down_cost`): +> +>   +> +$$ +\sum_{t \in T} \text{ramp}_{\text{up}, t} \cdot \text{rampcost}_{\text{up}} + \text{ramp}_{\text{down}, t} \cdot \text{rampcost}_{\text{down}} +$$ +#### `startup_cost` + +:::{admonition} How to access this objective? +:class: dropdown + +```julia +# Using Julia (`IESopt.jl`): +import IESopt + +model = IESopt.run(...) # assuming this is your model +IESopt.get_component(model, "your_unit").obj.startup_cost +``` + +```python +# Using Python (`iesopt`): +import iesopt + +model = iesopt.run(...) # assuming this is your model +model.get_component("your_unit").obj.startup_cost +``` + +::: + +Full implementation and all details: [`unit/obj_startup_cost @ IESopt.jl`](https://github.com/ait-energy/IESopt.jl/tree/main/src/core/unit/obj_startup_cost.jl) + +> Add the (potential) cost of this `unit`'s startup behaviour (configured by `unit.startup_cost` if `unit.unit_commitment != :off`). +> +>   +> +$$ +\sum_{t \in T} \text{startup}_t \cdot \text{startupcost} +$$ diff --git a/_sources/pages/manual/yaml/core_components.md.txt b/_sources/pages/manual/yaml/core_components.md.txt new file mode 100644 index 0000000..5b22080 --- /dev/null +++ b/_sources/pages/manual/yaml/core_components.md.txt @@ -0,0 +1,23 @@ +# Components + +- A [Connection](./core/connection.md) is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, ... +- A [Decision](./core/decision.md) represents a basic decision variable in the model that can be used as input for various other core component's settings, as well as have associated costs. +- A [Node](./core/node.md) represents a basic intersection/hub for energy flows. This can for example be some sort of bus (for electrical systems). It enforces a nodal balance equation (= "energy that flows into it must flow out") for every Snapshot. Enabling the internal state of the `Node` allows it to act as energy storage, modifying the nodal balance equation. This allows using `Node`s for various storage tasks (like batteries, hydro reservoirs, heat storages, ...). +- A [Profile](./core/profile.md) allows representing "model boundaries" - parts of initial problem that are not endogenously modelled - with a support for time series data. Examples are hydro reservoir inflows, electricity demand, importing gas, and so on. Besides modelling fixed [Profiles](/pages/manual/yaml/core/profile.md)s, they also allow different ways to modify the value endogenously. +- A [Unit](./core/unit.md) allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs. + +:::{tip} +There exists a further type, called `Virtual`, that works almost exactly the same as any other core component - but is only a virtual placeholder for non-existing components of your model. These are ones that a user might expect to exist, but are removed due to flattening the model topology: Each template that gets instantiated is dropped from the model and replaced by the components that it actually constructs. To allow accessing these components, `Virtual` components are used. + +Their usage, and various internal details, are rather advanced and currently not fully documented. +::: + +:::{toctree} +:hidden: + +core/connection.md +core/decision.md +core/node.md +core/profile.md +core/unit.md +::: diff --git a/_sources/pages/manual/yaml/index.md.txt b/_sources/pages/manual/yaml/index.md.txt new file mode 100644 index 0000000..4806fc3 --- /dev/null +++ b/_sources/pages/manual/yaml/index.md.txt @@ -0,0 +1,38 @@ +# YAML + +Most configuration settings for the IESopt optimization model are stored in YAML files. This section provides a reference for the YAML syntax and the specific settings used in IESopt. + +## Yet another markup language? + +YAML - which actually stands for _YAML Ain't Markup Language_ - is a human-readable data serialization language. It is often used for configuration files and data exchange between languages. YAML is a superset of JSON, meaning that any valid JSON file is also a valid YAML file. + +You can find more information at: + +- [The official YAML website](https://yaml.org/) +- [The `YAML.jl` Julia package](https://github.com/JuliaData/YAML.jl) +- Various good Python oriented sources, such as this [realpython tutorial](https://realpython.com/python-yaml/) or the [PyYAML package](https://github.com/yaml/pyyaml) + +## Good to know + +The following points offer hints, notes, or recommendations for working with YAML files in IESopt: + +- **Indentation**: YAML files make use of indentation: The recommended indentation is two spaces. +- **Comments**: Comments in YAML files are preceded by a `#` character. +- **Dictionaries**: YAML files are often used to define dictionaries. In Python (and Julia), dictionaries are/were unordered[^order], and the order of (dictionary) entries are therefore not considered to have any specific order. However, the order given in the documentation is recommended for readability. + +```{danger} +Since we are using dictionaries to store most configurations inside the YAML files, be aware that keys have to be unique. No key can exist twice in the same dictionary - this means, e.g., that you cannot use two components with the same name. However, depending on the implementation of the YAML parser, duplicate keys might not raise an error: After loading a YAML file IESopt will only be able to see the last value of a duplicate key, without any way to detect that ambiguity. + +**This is a common source of errors, so be careful!** +``` + +## Contents + +See the documentation of the built-in YAML here: + +:::{toctree} +top_level.md +core_components.md +::: + +[^order]: While `dict`s are standardized to be ordered starting with Python 3.7, and options like `OrderedCollections.jl` in Julia exist, the existence of these options is not guaranteed in all environments, and considering all potential parsers. diff --git a/_sources/pages/manual/yaml/top_level.md.txt b/_sources/pages/manual/yaml/top_level.md.txt new file mode 100644 index 0000000..da6a883 --- /dev/null +++ b/_sources/pages/manual/yaml/top_level.md.txt @@ -0,0 +1,529 @@ +# Top-level config + +The top-level YAML configuration file, often named `config.iesopt.yaml`, is the main configuration file for the IESopt optimization model. It may contain the following main sections: + +1. `parameters` (optional): General global parameters. +2. `config`: General configuration settings for the optimization model. +3. `addons` (optional): Additional settings for specific addons. +4. `carriers`: Definition of all energy carriers. +5. `components` (and/or `load_components`): Definitions of core components. + +```{note} As mentioned in the [intro section](./index.md) of the YAML reference, dictionaries are considered to not guarantee order. Therefore, the order of the sections in the YAML file is not important - however, the order given above is recommended for readability. +``` + +This means a basic top-level configuration file could look like this: + +```{code-block} yaml +:caption: Rough outline of a simple `config.iesopt.yaml`. + +config: + optimization: + problem_type: LP + snapshots: + count: 24 + +carriers: + electricity: {} + +components: + main_grid_node: + type: Node + carrier: electricity +``` + +## `parameters` + +General global parameters, that the whole model can access. Can be set from outside when, e.g., when calling {py:func}`iesopt.run`. + +```{note} +This section is optional and only needed if you want to global model parameters. +``` + +The base approach is passing a dictionary, with all global parameters and their default values: + +```{code-block} yaml +:caption: Example for a `dict`-based `parameters` section. + +parameters: + ng_emission_factor: 0.202 + co2_cost: 125 + elec_cost: null +``` + +Here `elec_cost` defaults to `null` - which is the equivalent of `None` (Python) or `nothing` (Julia). Either it is used internally for some setting that accepts `null` as a valid value, or it is meant to be set from outside. + +However, with a growing number of parameters, it might be more convenient to store them in a separate file. This can be done by passing the path to the file: + +```{code-block} yaml +:caption: Example for file-based `parameters` section. + +parameters: global_params_scenarioHIGH.iesopt.param.yaml +``` + +The file `global_params_scenarioHIGH.iesopt.param.yaml` could then look like this: + +```{code-block} yaml +:caption: Example content of `global_params_scenarioHIGH.iesopt.param.yaml`. + +ng_emission_factor: 0.202 # t CO2 / MWh_ng +co2_cost: 125 # EUR / t CO2 +elec_cost: null # EUR / MWh_el +``` + +```{hint} +Make sure to use comments to better explain the parameters and their units, and structure the file in a way that makes it easy to read and understand. Note: The same is true for the dictionary approach - use comments! - but the file approach is more likely to be used with a large number of parameters. +``` + +## `config` + +General configuration settings for the optimization model. + +```{code-block} yaml +:caption: Example for the `config` section in the top-level YAML configuration file. + +config: + general: + version: + core: 1.1.0 + python: 1.4.7 + name: + model: FarayOptIndustry + scenario: Base_2022_LOW + optimization: + problem_type: LP + snapshots: + count: 168 + files: + data: inputs_2022.csv + results: + enabled: true + paths: + files: data/ + templates: templates/ + components: model/ + results: out/ +``` + +The following subsections explain each part of the `config` section in more detail. + +### `general` + +This contains general settings. + +#### `version` + +This section allows specifying various IESopt versions, which is important to ensure reproducibility and compatibility: Executing a model based on a `config.iesopt.yaml` file with a different version of IESopt might lead to **unexpected results or errors**. + +```{code-block} yaml +:caption: Example for the `version` section. + +config: + general: + version: + core: 1.1.0 + python: 1.4.7 +``` + +We try to stick to [semantic versioning](https://semver.org/), which means that changes to the minor or patch version should not contain breaking changes, but: + +1. A bug fix to `IESopt.jl` might lead to different results of your model, meaning every bug fix might be a breaking change "for you". +2. We might mess up and introduce a breaking change in a minor version, without intending it. + +Therefore: Make sure you **KNOW** what changes between versions, and **TEST** your models when upgrading. + +:Parameters: +:`core`: The version of the core IESopt framework (`IESopt.jl`). +:`python`: The version of the Python interface (`iesopt`). + +:::{tip} +You may add arbitrary versions to this section, e.g., for personal use in specific addons or other dependencies. +::: + +#### `name` + +This section does not directly affect the model but can be used to store the name of the model and the scenario. This can be useful for logging and debugging purposes, as well as for the results output. + +```{note} +This section is optional and only needed if you want to global model parameters. +``` + +:Parameters: +:`model`: Name of the model, e.g., a high-level description, or the name of the project. +:`scenario`: A specific scenario or case, e.g., the year, a specific set of parameters, or a version of input files. + +```{tip} +The `scenario` setting supports dynamic placeholders. Currently, you can use `$TIME$` which will automatically be replaced by the timestamp of the model being built. This way, each result will have a unique scenario name, which prevents overwriting results. +``` + +Consider the following example for the `name` section: + +```{code-block} yaml +:caption: Example for the `name` section. + +config: + general: + name: + model: FarayOptIndustry + scenario: Base_2022_LOW_T-$TIME$ +``` + +When running the model containing this configuration, the results will be stored in a folder structure that looks like this: + +```{code-block} text +:caption: Example folder structure for the results of the model run. + +. +├── data/ +│ └── ... +├── out/ +│ ├── FarayOptIndustry/ +│ │ ├── Base_2022_LOW_T-2024_09_11_09520837.iesopt.result.jld2 +│ │ ├── Base_2022_LOW_T-2024_09_11_09520837.iesopt.log +│ │ ├── Base_2022_LOW_T-2024_09_11_09520837.highs.log +│ │ └── ... +│ └── FarayOptIndustry_Baseline/ +│ └── ... +└── config.iesopt.yaml +``` + +In this example, the `$TIME$` placeholder was replaced by the current timestamp, which is `2024_09_11_09520837` in this case. You can see the top-level config file (`config.iesopt.yaml`), a data folder (`data/`; see the [files](#files) and [paths](#paths) sections), the results folder (`out/`; see the [results](#results) and [paths](#paths) sections). Inside the results folder, IESopt creates a folder for each "model name". Each executed run creates its result files inside that, using the "scenario name" as base filename. + +#### `verbosity` + +Controls the verbosity of various parts of a model run. + +:Parameters: +:`core` (`str`, default = `info`): Verbosity of the (Julia) core, `IESopt.jl`. Supports: `debug`, `info` (default), `warning`, `error`. +:`progress` (`str`): Whether to show progress bars (`on` or `off`), defaults to `on` unless `core` is set to `error` - in that case it defaults to `off`. +:`python` (`str`): Verbosity of the Python wrapper, `iesopt`. Supports: `debug`, `info`, `warning`, `error`. Defaults to the verbosity set in `core`. +:`solver` (`str`): Whether to silence solver prints/outputs (`on` or `off`), defaults to `on` unless `core` is set to `error` - in that case it defaults to `off`. + +#### `performance` + +Controls various "performance" related settings of the model. + +:Parameters: +:`string_names` (`bool`, default = `true`): Activates (or deactivates) "string names" for expressions, variables, and constraints. These make the model readable for humans, but cost (a lot of) performance. Refer to the [`JuMP.jl` documentation](https://jump.dev/JuMP.jl/stable/tutorials/getting_started/performance_tips/#Disable-string-names) and its `set_string_names_on_creation` function, or read upon how this upstream functionality originated from a discussion during the early stages of IESopt development ([discourse.julialang](https://discourse.julialang.org/t/optimal-model-creation-and-garbage-collection/72619), [this issue](https://github.com/jump-dev/JuMP.jl/issues/2817), and some time later [this PR](https://github.com/jump-dev/JuMP.jl/pull/2978)). +:`logfile` (`bool`, default = `true`): Controls whether or not to write an IESopt logfile (which captures everything even if verbosity prevents displaying it). While this does save a bit of time it also prevents tracking down errors, so disable it wisely. This is most important to disable in iterative solves of small models, where logging accounts for a larger portion of overall time. +:`force_addon_reload` (`bool`, default = `true`): If set to `false`, addons that have already been loaded once will not be reloaded. This can help with repeated solves of the same model. Be aware, that this might interfere with reloading changes to an addon's code that you have made, so unless you need it (and understand it) it is best left as `true`. + +```{code-block} yaml +:caption: Example for the `performance` section. + +config: + general: + performance: + string_names: false + logfile: true +``` + +### `optimization` + +#### `problem_type` + +:Options: +:`LP`: Restricted to (continuous) linear programming formulations. +:`MILP`: Enables mixed-integer linear programming formulations. +:`MO`: Enables multi-objective optimization formulations. +:`MGA`: Enables modelling-to-generate-alternatives formulations. +:`SDDP`: Enables stochastic dual dynamic programming formulations. + +Options can be combined using `+` (where applicable), e.g., `LP+MO`. + +```{code-block} yaml +:caption: Example for the `problem_type` section. + +config: + optimization: + problem_type: LP +``` + +```{note} +The options `MO`, `MGA`, and `SDDP` represent very advanced functionality. These are not fully documented, or may be partially broken in any given release. If you are interested in these features, please reach out to the developers. +``` + +#### `solver` + +:Parameters: +:`name` (`str`, default = `highs`): The solver to use for the optimization problem. If not set, the default solver (currently [HiGHS](https://highs.dev/)) is used. +:`log` (`bool`, default = `true`): Whether to log the solver output to a file. +:`attributes` (`dict`, optional): Additional attributes to pass to the solver. + +```{code-block} yaml +:caption: Example for the `solver` section. + +config: + optimization: + solver: + name: highs + log: false + attributes: + solver: ipm +``` + +#### `snapshots` + +Here we define the Snapshots (IESopt's representation of time steps, including auxiliary information). + +:Parameters: +:`count` (`int`): The number of Snapshots to use in the optimization. +:`names` (`str`, optional): Linked column of a loaded file (using the usual `col@file` syntax) that contains the names of the Snapshots. These are purely for aesthetic purposes (e.g., result data frames) and are not used in the optimization. +:`weights` (`float`, default = `1`): The duration of each Snapshot in hours. Values below `1` are interpreted as fractions of an hour, e.g., `0.25` for 15 minutes. + +```{code-block} yaml +:caption: Example for the `snapshots` section. + +config: + optimization: + snapshots: + count: 24 + weights: 4 +``` + +#### `objectives` + +```{caution} +This setting is part of advanced functionality. It is not fully documented, or may be partially broken in any given release. If you are interested in this feature, please reach out to the developers. +``` + +This allows defining custom objective expressions for the optimization problem. This can be useful if you want to optimize for a specific objective that is not directly supported by IESopt. Per default, IESopt is optimizing the only base objective expression, which is the model's total cost (called `total_cost`). + +```{code-block} yaml +:caption: Example for the `objectives` section. + +config: + optimization: + objectives: + emissions: [co2_emissions.exp.value] +``` + +The above constructs a new objective expression that only consists of `co2_emissions.exp.value`. It may also be initialized empty (`[]`), in which case you can add terms later on in the model definition. + +#### `multiobjective` + +```{caution} +This setting is part of advanced functionality. It is not fully documented, or may be partially broken in any given release. If you are interested in this feature, please reach out to the developers. +``` + +:Parameters: +:`mode` (`str`): The mode to use for multi-objective optimization, currently supported modes are: `EpsilonConstraint`, `Lexicographic`, `Hierarchical`. +:`terms` (`list[str]`): A list of objectives to optimize. +:`settings` (`dict`): Settings for the multi-objective optimization, refer to the documentation of [MultiObjectiveAlgorithms.jl](https://github.com/jump-dev/MultiObjectiveAlgorithms.jl) for more information. + +```{code-block} yaml +:caption: Example for the `multiobjective` section. + +config: + optimization: + multiobjective: + mode: EpsilonConstraint + terms: [total_cost, emissions] + settings: + MOA.SolutionLimit: 5 +``` + +```{note} +Refer to the related example models for more information on how to use multi-objective optimization in IESopt. +``` + +#### `soft_constraints` + +This was called `constraint_safety` in all versions previous to `v2.0.0`. It controls if and how certain constraints of the model are relaxed to allow penalized violation of said constraints. This can be helpful, e.g., if your model is infeasible for certain external settings and you might want to ensure getting an approximate solution. + +```{code-block} yaml +:caption: Example for the `soft_constraints` section. + +config: + optimization: + soft_constraints: + active: true + penalty: 1e6 +``` + +:Parameters: +:`active` (`bool`, default = `false`): Activate the feature. +:`penalty` (`float`): Penalty that is used to penalize constraint violations. + +### `files` + +This can be used to define input files that are later referenced in the configuration file. Each filename (possibly including a path) is linked to a name that can be used to reference the file later on. + +```{code-block} yaml +:caption: Example for the `files` section. + +config: + files: + data: inputs_2023_base.csv +``` + +In the above example, the file `inputs_2023_base.csv` is linked to the name `data`. This means that the file can be referenced in the configuration file using `data`, accessing a column - e.g., "pv_generation" - using `pv_generation@data`. + +:::{note} +Assuming that, in the above example, we call `data` the file's "descriptor" and `inputs_2023_base.csv` the file's "name", then make sure that **the descriptor does not start with an underscore**, and further only uses alphanumeric characters and underscores (so no `!`, `~`, or other "unexpected" special characters). +::: + +#### `_csv_config` + +:::{caution} +This feature is experimental and can change, break, or be discontinued at any time. +::: + +You can configure the behavior of the CSV reader by adding a `_csv_config` section to the `files` section. + +:Parameters: +:`comment` (`string`, default = `null`): If empty/not-given, no "comments" are recognized. If set to any string, all lines starting with this string are considered comments and ignored. Common options are `#` or `;`. Make sure to properly escape the string if needed, e.g., `comment: "#"`. +:`delim` (`char`, default = `,`): The delimiter used in the CSV file. Common options are `,` (EN locale), `;` (DE locale), or `\t` (tab-separated). +:`decimal` (`char`, default = `.`): The decimal separator used in the CSV file. Common options are `.` (EN locale) or `,` (DE locale). Make sure this is set correctly in conjunction with the `delim` setting. + +```{code-block} yaml +:caption: Ignore comment rows in input CSVs. + +config: + files: + _csv_config: + comment: "#" +``` + +```{code-block} yaml +:caption: Switch to a format often used on German systems. + +config: + files: + _csv_config: + delim: ";" + decimal: "," +``` + +### `results` + +Settings for the results output. Details to be added. + +:Parameters: +:`enabled` (`bool`, default = `true`): Whether to enable the automatic extraction of results. If this is set to `false`, no results will read from the solver after optimizing the model - you can however still access the solver results directly, see for example {py:func}`iesopt.jump_value`. +:`memory_only` (`bool`, default = `true`): Whether to store the results in memory only, without writing them to disk. This can be useful if you plan to access and further process the results directly in Python or Julia, or only want to store specific results. +:`compress` (`bool`, default = `false`): Whether to compress the results when writing them to disk. This can save disk space but might increase the time needed to write and read the results. Refer to [JLD2.jl](https://github.com/JuliaIO/JLD2.jl) for more information about compression. +:`include` (`str`, default = `none` or `nput+log`): A list of result extraction modes to activate, see below for more details. The default depends on the setting of `memory_only`: If `memory_only` is `true`, the default is `none`, otherwise it is `input+log`. You can use `all` to activate all possible settings. +:`backend` (`str`, default = `jld2`): Backend to use in the result extraction. Currently `jld2` is the main/working one, while we are trying to improve the `duckdb` backend. + +```{code-block} yaml +:caption: Example for the `results` section. + +config: + results: + enabled: true + memory_only: true +``` + +```{admonition} Details: Result extraction modes +Currently the following options exist for the `include` setting: + +:`input`: Add information about the model's input data to the results. +:`git`: Add information about the status of any git setup that exists in the model's folder. +:`log`: Add IESopt's full log of the optimization run to the results. + +These modes can be combined using `+`. For example, `input+git` will include both the input data and the git information in the results. If `include` is set to `all`, all modes are included. If `include` is set to `none`, no modes are included. +``` + +```{note} +Refer to the [name](#name) section for information on how the model and scenario names are used in the results output. +``` + +### `paths` + +This section as a whole, or the individual paths, are optional - defaults will be used for those not set. + +:Parameters: +:`files` (`str`, default = `./files/`): The path to the directory where input files are stored. +:`templates` (`str`, default = `./templates/`): The path to the directory where templates are stored. +:`components` (`str`, default = `./model/`): The path to the directory where model topology files are stored. These allow loading a large number of components at once. Refer to the [components](#components) section for more information. +:`addons` (`str`, default = `./addons/`): The path to the directory where addons (`*.jl` files) are stored. +:`results` (`str`, default = `./out/`): The path to the directory where results should be stored. + +```{code-block} yaml +:caption: Example for the `paths` section. + +config: + paths: + files: data/ + templates: templates/ + components: model/ + results: out/ +``` + +```{note} +The paths are all relative to the location of the top-level configuration file. +``` + +## `addons` + +Additional settings for specific addons. + +```{code-block} yaml +:caption: Example for the `addons` section in the top-level YAML configuration file. + +addons: + ModifyHeatStorages: + alpha: 0.93 + temperature: 60.0 +``` + +```{note} +This section is optional and only needed if you want to use addons. The settings for the addons are specific to the addon. +``` + +## `carriers` + +Definition of all energy carriers. + +```{code-block} yaml +:caption: Example for the `carriers` section in the top-level YAML configuration file. + +carriers: + electricity: {} + co2: {} +``` + +## `components` + +This is the main section where all model components are defined (or loaded). There are two possibilities to define components: `components` and `load_components`. The former is used to define components directly in the top-level configuration file, while the latter is used to load components from a file. At least one of these sections must be present in the top-level configuration file - both can be used at the same time. + +```{code-block} yaml +:caption: Basic example for the `components` section. + +components: + main_grid_node: + type: Node + carrier: electricity +``` + +When using `load_components`, the easiest way is to pass a list of filenames. These files are then loaded from the `components` path (see the [paths](#paths) section) and the components are added to the model. Note that using `components` is not necessary when using `load_components`, and is only done here for demonstration purposes. + +```{code-block} yaml +:caption: Advanced example, utilizing `load_components`. + +load_components: + - units.csv + - nodes.csv + - profiles.csv + +components: + co2_emissions: + type: Profile + carrier: co2 + mode: destroy + node_from: total_co2 + cost: 100 +``` + +`````{note} +The `load_components` section allows more complex ways to read components from files: You can pass `.csv` as sole list entry to load all CSV files in the `components` path, or you can pass one, or multiple, regex patterns as list entries to load only specific files. For example, using + +```{code-block} yaml +load_components: + - ^09[/\\]((?!snapshots\.csv$).)*\.csv$ +``` + +will load all CSV files in the `components` path that are in a subfolder `09` except for `snapshots.csv`. + +**Note:** It is recommended to use `[/\\]` as path separators, like in the regex above, so the model can work on both UNIX and Windows systems. +````` diff --git a/_sources/pages/references/projects.md.txt b/_sources/pages/references/projects.md.txt new file mode 100644 index 0000000..9469307 --- /dev/null +++ b/_sources/pages/references/projects.md.txt @@ -0,0 +1,41 @@ +# Projects + +## Overview + +This maintains a list of projects where IESopt was applied as part of the modeling +approach. Entries are in alphabetical order. If you want to contribute a new project, please follow the +instructions in the [section on contributing](#contributing) below. + +## List of references + +To be added. + +## Contributing + +To contribute a new reference, either + +- fork the [iesopt](https://github.com/ait-energy/iesopt) repository, and directly add to the above list, or +- open an issue with the reference details. + +See the template below for the structure of a reference. + +### Template + +To be added. + +### Creating citation badges + +You can use [shields.io](https://shields.io/badges) to create badges, or use standardized ones that you already have +(e.g., from Zenodo), otherwise stick to the ones provided below. + +**Pure:** _(publications.ait.ac.at)_ + +> ```markdown +> [![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-none?style=social)](ADDYOURLINKHERE) +> ``` + +**DOI:** + +> ```markdown +> [![CITATION](https://img.shields.io/badge/DOI-10.XXXX%2Fname.YYYY.ZZZZZZ-none?style=social)](https://doi.org/10.XXXX/name.YYYY.ZZZZZZ) +> ``` diff --git a/_sources/pages/references/publications.md.txt b/_sources/pages/references/publications.md.txt new file mode 100644 index 0000000..60e9ef2 --- /dev/null +++ b/_sources/pages/references/publications.md.txt @@ -0,0 +1,88 @@ +# Publications + +## Overview + +This maintains a list of publications (journals, conferences, ...) where IESopt was applied as part of the modeling +approach. Entries are in alphabetical order. If you want to contribute a new publication or project, please follow the +instructions in the [section on contributing](#contributing) below. + +## List of references + +### Heat Highway - Heat Transmission Network Design Optimization and Robustness Analysis for a Case Study in Tyrol - Methodology + +[![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-blue?style=social)](https://publications.ait.ac.at/en/publications/heat-highway-heat-transmission-network-design-optimization-and-ro) + +> _**Abstract --**_ The majority of district heating (DH) networks today are fueled by combustion processes based on fossil or biogenic fuels. For the decarbonization of DH networks various uncertainties regarding the future development of key factors, such as energy prices, need to be considered. Within the project “HeatHighway” a hypothetical inter-regional heat transfer network (HTN) in the region of the Inn valley in Tyrol, Austria was investigated. + +> _**Keywords --**_ Future district heating, Waste heat sources, 4th generation DH, Heat transmission networks, Deterministic optimization, Monte Carlo simulation + +```{admonition} Expand to show citation +:class: dropdown + +Marx, N. O., Schmidt, R. R., Blakcori, R., Maggauer, K., Strömer, S., & Forster, T. (2023, September). Heat Highway - Heat Transmission Network Design Optimization and Robustness Analysis for a Case Study in Tyrol - Methodology. In _9th International Conference on Smart Energy Systems_ (pp. 103-104). +``` + +--- + +### Optimizing the Domestic Production and Infrastructure for Green Hydrogen in Austria for 2030 + +[![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-blue?style=social)](https://publications.ait.ac.at/en/publications/optimizing-the-domestic-production-and-infrastructure-for-green-h) + +> _**Abstract --**_ The decarbonisation of the Austrian energy system is expected to be facilitated by the uptake of hydrogen-based technologies, which requires the establishment of a hydrogen infrastructure to meet the rising demand. While large quantities of hydrogen are expected to be imported in the future, current developments in the energy market suggest that domestic production of hydrogen should not be ignored to ensure the security of supply. As domestic production ramps up, locating electrolysers to ensure optimal system integration is still an open question. To address this challenge, the "HyTechonomy" project developed an optimisation model that identifies the most promising domestic locations for green hydrogen production and optimal means of hydrogen transport for the year 2030. + +> _**Keywords --**_ Hydrogen infrastructure, Energy system modelling, centralised electrolysis, decentralised electrolysis + +```{admonition} Expand to show citation +:class: dropdown + +Reuter, S., Strömer, S., Traninger, M., & Beck, A. (2023, September). Optimizing the Domestic Production and Infrastructure for Green Hydrogen in Austria for 2030. In _Book of Abstracts: 9th International Conference on Smart Energy Systems_ (pp. 278-279). +``` + +## Contributing + +To contribute a new reference, either + +- fork the [iesopt](https://github.com/ait-energy/iesopt) repository, and directly add to the above list, or +- open an issue with the reference details. + +See the template below for the structure of a reference. + +### Template + +Please stick to APA format here, and always include a link as badge (if possible a DOI, if not other links are okay +too). + +`````markdown +### Title of the Publication + +[![CITATION](url-of-your-badge)](link-to-doi-or-pure-or-other) + +> _**Abstract --**_ Put your abstract text here. + +> _**Keywords --**_ Put some, Keywords, In this, List + +```{admonition} Expand to show citation +:class: dropdown + +Add your (APA styled!) citation here +``` + +--- +````` + +### Creating citation badges + +You can use [shields.io](https://shields.io/badges) to create badges, or use standardized ones that you already have +(e.g., from Zenodo), otherwise stick to the ones provided below. + +**Pure:** _(publications.ait.ac.at)_ + +> ```markdown +> [![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-none?style=social)](ADDYOURLINKHERE) +> ``` + +**DOI:** + +> ```markdown +> [![CITATION](https://img.shields.io/badge/DOI-10.XXXX%2Fname.YYYY.ZZZZZZ-none?style=social)](https://doi.org/10.XXXX/name.YYYY.ZZZZZZ) +> ``` diff --git a/_sources/pages/tutorials/addons.md.txt b/_sources/pages/tutorials/addons.md.txt new file mode 100644 index 0000000..85a4f75 --- /dev/null +++ b/_sources/pages/tutorials/addons.md.txt @@ -0,0 +1,3 @@ +# Addons + +To be written as part of our next workshop. diff --git a/_sources/pages/tutorials/templates_1.md.txt b/_sources/pages/tutorials/templates_1.md.txt new file mode 100644 index 0000000..445392c --- /dev/null +++ b/_sources/pages/tutorials/templates_1.md.txt @@ -0,0 +1,446 @@ +# Templates: Part I + +Templates are a powerful feature of IESopt that allow you to define new types of "components" by yourself. This makes +use of the existing `CoreComponent`s, and combines them in multiple ways, which allows for a high degree of flexibility +without having to write any mathematical model yourself. + +This tutorial will guide you through the process of creating a new template, and we will do that on the example of +creating the `HeatPump` template (a template shipped via IESoptLib). + +## The basic structure + +A template is defined by a YAML file, similar to the `config.iesopt.yaml` file that you already know. First, we need to +think about the parameters that we want to define for our heat pump. Let's create a new file for that. The pre-defined +one in IESoptLib is called `HeatPump`, so we need a different name: Templates must always have a unique name. + +Possibilities for that could be: + +- `CustomHeatPump`, if you do not have any more details +- `GroundSourceHeatPump`, if we want to implement a ground-source heat pump with different parameters/features than the + standard one +- `FooHeatPump`, if you need it specifically for a project called "Foo" + +!!! info "Naming conventions" + Templates follow a naming convention similar to `PascalCase`: + - The name must start with an upper-case letter + - It must consist of at least two letters + - Numbers and special characters are not allowed + +Let's go with `CustomHeatPump` for now. Create a new file `CustomHeatPump.iesopt.template.yaml` (if you are already +working on a model, the best place to put this would be a `templates/` folder), and add the following lines: + +```yaml +parameters: + p_nom: null + electricity_from: null + heat_from: null + heat_to: null + cop: null +``` + +This defines the basic parameters that we want to use for our heat pump. The `null` values indicate that they all +default to `nothing` in Julia, which corresponds to `None` in Python. Let's go through them: + +- `p_nom`: The nominal power of the heat pump, which will be specified on the electricity input side +- `electricity_from`: The `Node` that this heat pump is connected to for electricity input +- `heat_from`: The `Node` that this heat pump is connected to for heat input +- `heat_to`: The `Node` that this heat pump is connected to for heat output +- `cop`: The coefficient of performance of the heat pump + +Next, we will set up the actual component. This is done in the `component` section of the template file. Let's add the +following lines: + +```yaml +component: + type: Unit + inputs: {electricity: , heat: } + outputs: {heat: } + conversion: 1 electricity + ( - 1) heat -> heat + capacity: in:electricity +``` + +This defines the component that we want to create. The `type` is `Unit`, which is a core component type in IESopt that +you are already familiar with. Instead of providing fixed values, we make use of the parameters that we defined above. +This is done by using the `<...>` syntax. + +That's it! You have created a new template. You can now use this template in your model configuration, as you would with +any other component. For example, you could add the following lines to your `config.iesopt.yaml` file: + +```yaml +# other parts of the configuration file +# ... + +components: + # some other components + # ... + + heat_pump: + template: CustomHeatPump + parameters: + p_nom: 10 + electricity_from: electricity + heat_from: ambient + heat_to: heating + cop: 3 +``` + +## Accounting for different configurations + +But wait. What if you want to have different configurations for your heat pump? For example, you might want to have a +heat pump that does not explicitly consume any heat, because they low-temperature heat source is not explicitly modeled. +Currently, the template does not allow for that, because the `heat_from` parameter is mandatory. + +Why do we know it is mandatory? Because it is used in the `inputs` section of the `Unit` definition. But that is not +clear, or transparent. Before we continue, we will fill in the mandatory documentation fields for the template. We do +that by adding the following information directly at the beginning of the template file, right before the `parameters`: + +```yaml +# # Custom Heat Pump + +# A (custom) heat pump that consumes electricity and heat, and produces heat. + +# ## Parameters +# - `p_nom`: The nominal power (electricity) of the heat pump. +# - `electricity_from`: The `Node` that this heat pump is connected to for electricity input. +# - `heat_from`: The `Node` that this heat pump is connected to for heat input. +# - `heat_to`: The `Node` that this heat pump is connected to for heat output. +# - `cop`: The coefficient of performance of the heat pump. + +# ## Components +# _to be added_ + +# ## Usage +# _to be added_ + +# ## Details +# _to be added_ + +parameters: + # ... +``` + +!!! info "Docstring format" + All of that is actually just Markdown inserted into your template. However, make sure to stick to separating the + leading `#` from the actual text by a space, as this is required for IESopt to better understand your documentation. + +Now, every user of the template will see this information, and they will notice, that none of the parameters are marked +as optional. As you see, there are a lot of other sections to be added, but we will fill them out at the end, after we +have finished the template, see the section on [finalizing the docstring](#Finalizing-the-docstring). + +Let's continue with accounting for different configurations. We will cover the following steps: + +1. Making the `heat_from` parameter optional +2. Extending the template to allow for sizing the heat pump (an investment decision) +3. Handling more complex COP configurations + +### Optional parameter and sizing decision + +While there are multiple ways to make a parameter optional, we will make use of the most powerful one, so that you are +able to apply it for your models as well. For that, we will add "complex" functionalities to the template, which is done +using three different "functions": + +1. `validate`: This function is called when the template is parsed, and it is used to check if the parameters are valid. + If they are not, an error is thrown. This helps to inform the user of any misconfiguration. +2. `prepare`: This function is called when the template is instantiated, and it is used to prepare the component for + usage. This can be used to set default values, or to calculate derived parameters (which we will use to tackle the + three additions mentioned above). +3. `finalize`: This function is called when the template is finalized, and it enables a wide range of options. We will + use this to allow a smooth result extraction for the heat pump, but you could also use it to add additional (more + complex) constraints to the component, or even modify the model's objective function. + +Let's start by adding the `functions` entry (which we suggest doing at the end of the file): + +```yaml +# ... the whole docstring ... + +parameters: + # ... + +component: + # ... + +functions: + validate: | + # ... we will put the validation code here ... + prepare: | + # ... we will put the preparation code here ... + finalize: | + # ... we will put the finalization code here ... +``` + +> The `|` at the end of the line indicates that the following lines are a multiline string. This is a YAML feature that +> allows you to write more complex code in a more readable way. + +Let's start by filling out the validation function. Everything you do and write here, is interpreted as Julia code, and +compiled directly into your model. This means that you can use all the power of Julia, but also that you need to be +careful with what you do. You have access to certain helper functions and constants, which we will introduce here. If +you have never written a line of Julia code, don't worry. We will guide you through this - it's actually (at least for +the parts that you will need) extremely similar to Python. + +#### Validation + +The validation function is used to check if the parameters are valid. Add the following code to the `validate` section: + +```yaml +functions: + validate: | + # Check if `p_nom` is non-negative. + @check get("p_nom") isa Number + @check get("p_nom") >= 0 + + # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`. + @check get("electricity_from") isa String + @check get("heat_from") isa String || isnothing(get("heat_from")) + @check get("heat_to") isa String + + # Check if `cop` is positive. + @check get("cop") isa Number + @check get("cop") > 0 + # ... the rest of the template ... +``` + +Let's go through this step by step: + +- You can start comments (as separate line or inline) with `#`, as you would in Python. +- You can use `get("some_param")` to access the value of a parameter. +- You can use `@check` to check if a condition is met. If it is not, an error will be thrown. All statements starting + with `@` are so called "macros", which are just "special" functions. You can do `@check(condition)` or + `@check condition`, since macros do not require parentheses. +- You can use `isa` to check if a value is of a certain type. This is similar to `isinstance` in Python. While it is a + special keyword, if you prefer, you can also call it in a more conventional way: `isa(get("p_nom"), Number)`. +- Data types are capitalized in Julia, so it is `String` instead of `string`, and `Number` is a superset of all numeric + types (if necessary you could instead, e.g., check for `get("some_param") isa Int`). +- Logical operators are similar to Python, so `||` is like `or`, and `&&` is like `and`. +- If all checks pass, the template is considered valid, and the model can be built. + +#### Preparation + +Next, we will add the preparation function. This function is used to prepare the component for usage. Since we would +like to make the `heat_from` parameter optional, and we would like to account for optional sizing, we will first modify +the parameters accordingly: + +```yaml +parameters: + p_nom: null + p_nom_max: null + electricity_from: null + heat_from: null + heat_to: null + cop: null + _inputs: null + _conversion: null + _capacity: null + _invest: null +``` + +One step at a time. We added the following parameters: + +- `p_nom_max`: The maximum nominal power of the heat pump. This is optional, and if not specified, it will default to + `p_nom`, which will disable the sizing feature. +- `_inputs`: This is an internal / private parameter (since it starts with an underscore), which we will user later. + These parameters are not exposed to the user, and can not be set or modified from the outside. +- `_capacity`: This is another internal parameter, which we will use to store the capacity of the heat pump (which could + now either bne `p_nom` or whatever the investment decision results in). +- `_conversion`: This is another internal parameter, which we will use to store the conversion formula. + +Before we can actually add the code for the `prepare` function, we need to modify our component definition, as well. +We (1) will change from `component` to `components` (since it now contains more than just one), (2) will add a +`Decision` that should handle the sizing / investment, and modify the `Unit` slightly: + +```yaml +components: + unit: + type: Unit + inputs: <_inputs> + outputs: {heat: } + conversion: <_conversion> + capacity: <_capacity> in:electricity + + decision: + type: Decision + enabled: <_invest> + lb: + ub: +``` + +So ... a lot of changes. Let's go through them step by step: + +- We changed `component` to `components`, because we now have multiple components. +- We added a `unit` component, which is the actual heat pump. We replaced the fixed values with the internal parameters. +- We added a new component `decision`, which is a `Decision`. This component is used to handle investment + decisions. It is enabled if `_invest` evaluates to `true`. It has a lower bound `lb` and an upper bound `ub`, which + are the minimum and maximum values that the decision can take. In our case, the decision is the nominal power of the + heat pump, which can be between `p_nom` and `p_nom_max`. + +!!! info "Naming the components" + The names of the components are arbitrary, and you can choose whatever you like. However, it is recommended to use + meaningful names, so that you can easily understand what the component does. Component names follow a naming + convention similar to `snake_case`: They must start with a lower-case letter, and can contain numbers and + underscores (but are not allowed to end in an `_`). They can further contain `.`, but this is "dangerous" and an + expert feature, that you should not use unless you know what it does, and why you need it. + +Onto the actual functionality. Let's add the `prepare` function, and some additional validation code: + +```yaml +functions: + validate: | + # ... the previous validation code ... + + # Check if `p_nom_max` is either `nothing` or at least `p_nom`. + @check isnothing(get("p_nom_max")) || (get("p_nom_max") isa Number && get("p_nom_max") >= get("p_nom")) + prepare: | + # Determine if investment should be enabled, and set the parameter (used to enable `decision`). + invest = !isnothing(get("p_nom_max")) && get("p_nom_max") > get("p_nom") + set("_invest", invest) + + if invest + # Set the capacity to the size of the decision variable. + myself = get("self") + set("_capacity", "$(myself).decision:value") + else + # Set the capacity to the value of `p_nom`. + set("_capacity", get("p_nom")) + end + + # Prepare some helper variables to make the code afterwards more readable. + elec_from = get("electricity_from") + heat_from = get("heat_from") + cop = get("cop") + + # Handle the optional `heat_from` parameter. + if isnothing(heat_from) + # If `heat_from` is not specified, we just use electricity as input. + set("_inputs", "{electricity: $(elec_from)}") + set("_conversion", "1 electricity -> $(cop) heat") + else + # If `heat_from` is specified, we now have to account for two inputs. + set("_inputs", "{electricity: $(elec_from), heat: $(heat_from)}") + set("_conversion", "1 electricity + $(cop - 1) heat -> $(cop) heat") + end +``` + +Once again, let's go through this step by step: + +To be added. + +### Complex COP configurations + +To be added. + +## The `finalize` function + +To be added. + +## Finalizing the docstring + +To be added. + +## Conclusion + +To be added. + +!!! details "Complete template YAML" + ```yaml + # # Custom Heat Pump + + # A (custom) heat pump that consumes electricity and heat, and produces heat. + + # ## Parameters + # - `p_nom`: The nominal power (electricity) of the heat pump. + # - `electricity_from`: The `Node` that this heat pump is connected to for electricity input. + # - `heat_from`: The `Node` that this heat pump is connected to for heat input. + # - `heat_to`: The `Node` that this heat pump is connected to for heat output. + # - `cop`: The coefficient of performance of the heat pump. + + # ## Components + # _to be added_ + + # ## Usage + # _to be added_ + + # ## Details + # _to be added_ + + parameters: + p_nom: null + p_nom_max: null + electricity_from: null + heat_from: null + heat_to: null + cop: null + _inputs: null + _conversion: null + _capacity: null + _invest: null + + components: + unit: + type: Unit + inputs: <_inputs> + outputs: {heat: } + conversion: <_conversion> + capacity: <_capacity> in:electricity + + decision: + type: Decision + enabled: <_invest> + lb: + ub: + + functions: + validate: | + # Check if `p_nom` is non-negative. + @check get("p_nom") isa Number + @check get("p_nom") >= 0 + + # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`. + @check get("electricity_from") isa String + @check get("heat_from") isa String || isnothing(get("heat_from")) + @check get("heat_to") isa String + + # Check if `cop` is positive. + @check get("cop") isa Number + @check get("cop") > 0 + + # Check if `p_nom_max` is either `nothing` or at least `p_nom`. + @check isnothing(get("p_nom_max")) || (get("p_nom_max") isa Number && get("p_nom_max") >= get("p_nom")) + prepare: | + # Determine if investment should be enabled, and set the parameter (used to enable `decision`). + invest = !isnothing(get("p_nom_max")) && get("p_nom_max") > get("p_nom") + set("_invest", invest) + + if invest + # Set the capacity to the size of the decision variable. + myself = get("self") + set("_capacity", "$(myself).decision:value") + else + # Set the capacity to the value of `p_nom`. + set("_capacity", get("p_nom")) + end + + # Prepare some helper variables to make the code afterwards more readable. + elec_from = get("electricity_from") + heat_from = get("heat_from") + cop = get("cop") + + # Handle the optional `heat_from` parameter. + if isnothing(heat_from) + # If `heat_from` is not specified, we just use electricity as input. + set("_inputs", "{electricity: $(elec_from)}") + set("_conversion", "1 electricity -> $(cop) heat") + else + # If `heat_from` is specified, we now have to account for two inputs. + set("_inputs", "{electricity: $(elec_from), heat: $(heat_from)}") + set("_conversion", "1 electricity + $(cop - 1) heat -> $(cop) heat") + end + ``` + +## Next steps + +While the above template is already quite powerful, it can become hard to maintain and understand if it grows too large. +In the next tutorial, we will cover how to separate the `functions` part of the template into a separate file, and later +will see how this approach can then be extended even further (a concept that we call `Addon`s), which allows +intercepting steps of the model build process. + +But ... before we go there, let's start "small". Check out the section [Templates: Part II](./templates_2.md), where we walk +through the process of "out-sourcing" the `functions` part of the template. diff --git a/_sources/pages/tutorials/templates_2.md.txt b/_sources/pages/tutorials/templates_2.md.txt new file mode 100644 index 0000000..a410973 --- /dev/null +++ b/_sources/pages/tutorials/templates_2.md.txt @@ -0,0 +1,29 @@ +# Templates: Part II + +To be added. + +## Catching errors + +To be added. + +## Separate `.jl` files + +To be added. + +## Some examples + +### `HeatPump` + +To be added. + +### `BESS` + +To be added. + +### `PV` + +To be added. + +### `CHP` + +To be added. diff --git a/_sources/pages/user_guides/general/best_practice.md.txt b/_sources/pages/user_guides/general/best_practice.md.txt new file mode 100644 index 0000000..5f2403d --- /dev/null +++ b/_sources/pages/user_guides/general/best_practice.md.txt @@ -0,0 +1,38 @@ +# Best practices + +## Separate energy carriers + +Imagine a system where a heat pump is used to produce _heat_ for both heating and hot water. One may be tempted to use +`heat` as corresponding energy carrier for both purposes. However, this is not recommended. Instead, one could use +`heating` and `hotwater` as energy carriers (or any other naming, as long as it creates a distinction). + +**Why?** + +1. **Clarity:** It is easier to understand the system when energy carriers are clearly separated. +2. **Flexibility:** It is easier to change the system later on. For example, if one wants to replace the heat pump with + a gas boiler - but just for hot water - it is easier to do so when the energy carriers are separated. +3. **Consistency:** It is easier to compare different systems when the energy carriers are consistently named. +4. **Error prevention:** It is less likely to make mistakes when the energy carriers are clearly separated. Mistkenly + connecting two [Nodes](/pages/manual/yaml/core/node.md) with `heat`, that in reality could not be connected since they are part of two different + systems, cannot happen when the energy carriers are separated. +5. **Plotting:** It is easier to plot the system when the energy carriers are separated. For example, one can plot the + heat demand for heating and hot water separately, or immediately see how much energy is being spent to supply the + different demands. + +> These are just a few reasons why it is recommended to separate energy carriers. Different systems may have different +> requirements, so it is up to the user to decide how to name the energy carriers. However, it is recommended to keep +> the energy carriers as separate as reasonable. + +:::{admonition} Definition: Energy carrier +:class: tip + +_According to ISO 13600, an energy carrier is either a substance or a phenomenon that can be used to produce +mechanical work or heat or to operate chemical or physical processes._ + +This may be seen as motivation why, in this +case, the argument "both are heat" may not be valid: In the end, the actual "carrier" is most likely `water`, and not +`heat` (even if that is most commonly used as "carrier"). However, no one would argue to actually use `water` in +this example, which shows that the choice of `heat` would already be "not 100% exact"; therefore, the separation +into different two carriers does not "mis-represent" reality, but instead just makes our "abstraction" (= not using +`water` as carrier) more explicit. +::: diff --git a/_sources/pages/user_guides/general/common_errors.md.txt b/_sources/pages/user_guides/general/common_errors.md.txt new file mode 100644 index 0000000..f91c64a --- /dev/null +++ b/_sources/pages/user_guides/general/common_errors.md.txt @@ -0,0 +1,7 @@ +# Common errors + +## During package load + +Errors that may occur when executing `import iesopt`. + +- _"Exception: no version of Julia is compatible with =x.y.z - perhaps you need to update JuliaUp"_: Open a terminal and execute `juliaup update`. If it still reports the same error afterwards, checkout the specific version that it fails to find, and manually install that. diff --git a/_sources/pages/user_guides/general/linking_components.md.txt b/_sources/pages/user_guides/general/linking_components.md.txt new file mode 100644 index 0000000..1014abe --- /dev/null +++ b/_sources/pages/user_guides/general/linking_components.md.txt @@ -0,0 +1,52 @@ +# Linking components + +Sometimes, two or more components need to be linked together. While this can be achieved using addons, it can be achieved using custom functionality within a template. Consider the following (shortened) version of the `CHP` template: + +```yaml +parameters: + p_max: null + h_max: null + power_ratio: 0.55 + power_loss_ratio: 0.20 + efficiency: 0.40 + fuel: gas + fuel_co2_emission_factor: 0.2 + fuel_in: null + power_out: null + heat_out: null + co2_out: null + +components: + power: + type: Unit + inputs: {: } + outputs: {electricity: , co2: } + conversion: 1 -> electricity + co2 + capacity: out:electricity + + heat: + type: Unit + inputs: {: } + outputs: {heat: , co2: } + conversion: 1 -> / heat + co2 + capacity: out:heat + +functions: + finalize: | + # Parameters. + cm = get("power_ratio") + cv = get("power_loss_ratio") + p_max = get("p_max") + + # Output expressions. + out_heat = access("heat").exp.out_heat + out_elec = access("power").exp.out_electricity + + # Add constraints. + @constraint(MODEL.model, cm .* out_heat .<= out_elec) + @constraint(MODEL.model, out_elec .<= p_max .- cv .* out_heat) +``` + +This makes use of the `finalize(...)` function to link the `power` and `heat` components. Using an addon can be complicated, because the components do not inherently know about each other. The `finalize(...)` function however is attached to the template itself, can access both components, and is called after both are fully constructed, which means it can freely access their variables and expressions. + +> **Note:** A similar approach is possible by using the `build_priority` parameter in the component definition. This parameter allows you to specify the order in which components are built, which can be used to ensure that one component is built before another. diff --git a/_sources/pages/user_guides/general/solvers.md.txt b/_sources/pages/user_guides/general/solvers.md.txt new file mode 100644 index 0000000..843fb93 --- /dev/null +++ b/_sources/pages/user_guides/general/solvers.md.txt @@ -0,0 +1,101 @@ +# Solvers + +## Recommended Configurations + +The following configurations can be seen as helpful starting point on how to configure different solvers for large-scale +models. They are largely based on other model's defaults (see e.g. +[PyPSA](https://github.com/PyPSA/pypsa-eur-sec/blob/master/config.default.yaml)). + +More information can be found at: + +- HiGHS: + - https://ergo-code.github.io/HiGHS/stable/options/definitions/ +- Gurobi: + - https://www.gurobi.com/wp-content/uploads/2022-10-Paris_Advanced_Algorithms.pdf + - https://www.gurobi.com/documentation/current/refman/parameters.html +- CPLEX: + - https://www.ibm.com/docs/en/icos/22.1.1?topic=cplex-list-parameters + +### HiGHS + +```yaml +solver: + name: highs + attributes: + threads: 4 + solver: "ipm" + run_crossover: "off" + small_matrix_value: 1e-6 + large_matrix_value: 1e9 + primal_feasibility_tolerance: 1e-5 + dual_feasibility_tolerance: 1e-5 + ipm_optimality_tolerance: 1e-4 + parallel: "on" + random_seed: 1234 +``` + +### Gurobi + +```yaml +solver: + name: gurobi + attributes: + Method: 2 + Crossover: 0 + BarConvTol: 1.e-6 + Seed: 123 + AggFill: 0 + PreDual: 0 + GURO_PAR_BARDENSETHRESH: 200 + Threads: 8 + Seed: 1234 +``` + +### Gurobi (NumFocus) + +For models with "challenging" numerical properties, the following can be useful: + +```yaml +solver: + name: gurobi + attributes: + NumericFocus: 3 + Method: 2 + Crossover: 0 + BarHomogeneous: 1 + BarConvTol: 1.e-5 + FeasibilityTol: 1.e-4 + OptimalityTol: 1.e-4 + ObjScale: -0.5 + Threads: 8 + Seed: 1234 +``` + +### Gurobi (fallback) + +```yaml +solver: + name: gurobi + attributes: + Crossover: 0 + Method: 2 + BarHomogeneous: 1 + BarConvTol: 1.e-5 + FeasibilityTol: 1.e-5 + OptimalityTol: 1.e-5 + Threads: 8 + Seed: 1234 +``` + +### CPLEX + +```yaml +solver: + name: cplex + attributes: + threads: 4 + lpmethod: 4 + solutiontype: 2 + barrier_convergetol: 1.e-5 + feasopt_tolerance: 1.e-6 +``` diff --git a/_sources/pages/user_guides/index.md.txt b/_sources/pages/user_guides/index.md.txt new file mode 100644 index 0000000..83f906c --- /dev/null +++ b/_sources/pages/user_guides/index.md.txt @@ -0,0 +1,8 @@ +# Intro + +These user guides cover a wide range of topics, going into detail on specifics, but do not "take you by the hand" as much as a tutorial would: + +## Contents + +- [General](./toc_general.md): Crafted user guides covering a specific topic. These are mostly used to explain advanced topics, that are too complex to fully explain in a tutorial, or are linked as "dive deeper" options to read up on something. +- [Q & A](./toc_qna.md): These are based on previous questions that users had and discussions that occurred to clarify. They can be used to point users to for any questions that repeatedly occur. diff --git a/_sources/pages/user_guides/qna/dynamic_marginal_costs.md.txt b/_sources/pages/user_guides/qna/dynamic_marginal_costs.md.txt new file mode 100644 index 0000000..f8ced62 --- /dev/null +++ b/_sources/pages/user_guides/qna/dynamic_marginal_costs.md.txt @@ -0,0 +1,67 @@ +# Output-dependent marginal costs + +**Question:** +> How can I model a [Unit](/pages/manual/yaml/core/unit.md) where the marginal cost changes based on the level of energy output? For example, the cost is 5 €/MWh for outputs below 5 MW of power and 10 €/MWh for outputs above 5 MW of power. + +**Answer:** +> To model changing marginal costs based on output levels in IESopt, you can split the [Unit](/pages/manual/yaml/core/unit.md) into two separate [Units](/pages/manual/yaml/core/unit.md): + +## Details + +### Explanation + +1. **Low-output [Unit](/pages/manual/yaml/core/unit.md):** + - Represents the output up to 5 MW. + - Has a marginal cost of 5 €/MWh. + - Capacity is limited to 5 MW. + +2. **High-output [Unit](/pages/manual/yaml/core/unit.md):** + - Represents any output above 5 MW. + - Has a marginal cost of 10 €/MWh. + - The capacity is given as total capacity of the "real" [Unit](/pages/manual/yaml/core/unit.md), reduced by 5 MW. + +By doing this, the model will prioritize the low-cost [Unit](/pages/manual/yaml/core/unit.md) up to its capacity before utilizing the high-cost [Unit](/pages/manual/yaml/core/unit.md) for additional demand. + +### Implementation + +```yaml +unit_pool_1: + type: Unit + inputs: {} # fill this with the original configuration + outputs: {} # fill this with the original configuration + conversion: foo -> bar # fill this with the original configuration + capacity: 5 out:electricity + marginal_cost: 5 per out:electricity + +unit_pool_2: + type: Unit + inputs: {} # fill this with the original configuration + outputs: {} # fill this with the original configuration + conversion: foo -> bar # fill this with the original configuration + capacity: 95 out:electricity # `100 - 5` MW, assuming 100 MW is the max. + marginal_cost: 10 per out:electricity +``` + +## Summary + +### Notes + +- **Flexibility:** This method allows you to create multiple cost bands by adding more [Units](/pages/manual/yaml/core/unit.md) with different capacities and marginal costs. +- **Intuitiveness:** Splitting [Units](/pages/manual/yaml/core/unit.md) makes the model easier to understand and maintain. +- **Accuracy:** The model will naturally favor the lower-cost [Unit](/pages/manual/yaml/core/unit.md) up to its capacity limit before using higher-cost options. + +But ... + +- This might be an over complicated way to represent stuff like actual cost-power curve dynamics. +- This might add too many [Units](/pages/manual/yaml/core/unit.md), making the model hard to understand or analyse. +- If there is any reason that the model might save costs by not running the "lower price" [Unit](/pages/manual/yaml/core/unit.md) it will do so! This means the "higher price" part of your asset could be used with out running the "lower price", which in reality is not possible (where it's one asset). + +Note that the last point most likely will not occur for your model, but ... it's something to keep in mind. + +### Conclusion + +By representing different output levels with separate [Units](/pages/manual/yaml/core/unit.md) in IESopt, you can effectively model [Units](/pages/manual/yaml/core/unit.md) with changing marginal costs based on their energy output. This approach is straightforward and leverages the existing capabilities of IESopt without the need for complex cost functions. + +### Improvements + +The "best" or final way to represent stuff like this will always be a custom made addon (potentially making use of SOS variables). diff --git a/_sources/pages/user_guides/qna/passive_charging.md.txt b/_sources/pages/user_guides/qna/passive_charging.md.txt new file mode 100644 index 0000000..e777d57 --- /dev/null +++ b/_sources/pages/user_guides/qna/passive_charging.md.txt @@ -0,0 +1,78 @@ +# Passive charging of an underground heat storage + +## Intro + +**Question:** +> How can I represent passive charging in a borehole heat storage within my energy system optimization model? + +**Answer:** +> It's complicated ... But the only generally applicable answer is: A custom addon. The underlying idea is to separate the passive charging from the storage, into a separate "artificial" Profile, that can then accurately depict the passive behavior. + +## The problem + +When modeling a borehole heat storage - a deep underground heat storage surrounded by warm soil - it's important to account for passive charging from the surrounding soil. This guide explains how to represent this passive energy input in your model. + +### Common misconception + +You might consider using the `state_percentage_loss` parameter with a negative value to simulate passive charging: + +```yaml +state_percentage_loss: -0.01 # Attempting to charge 1% per timestep +``` + +However, `state_percentage_loss` is designed for losses based on the **current storage level**. Using a negative value would incorrectly charge the storage by a percentage of its **existing energy content**, not the energy it lacks. + +Therefore, while `-0.01` might be an allowed or in other cases even reasonable choice, it's not made for what is needed here. + +## Recommended solution + +Instead, model passive charging as an additional input that depends on how much energy the storage lacks. Here's how: + +### Naive static charging + +Create a new component using a ranged Profile: + +```yaml +passive_charging: + type: Profile + mode: ranged + lb: 0 + ub: MAX_PASSIVE_POWER # Use any estimation of maximum passive charging power +``` + +The mode setting `ranged` transforms a basic Profile (with default `mode: fixed`), that cannot change it's value, to a more advanced one that can freely pick it's value from the interval `[0, MAX_PASSIVE_POWER]` - independently for each snapshot. This means, charging `0` is a feasible choice, resulting in no charge happening when the storage is already full. If the storage is not full, it will charge how much is economically optimal: Most of the time it will fully use it's passive charging power, but if - for any reason - getting rid of excess heat might not be possible, or very costly, it can choose to not use the passive charging (which in reality would not be possible). + +### Custom constraint + +Use an addon to add a constraint limiting the passive charging based on the storage's unfilled capacity. Assuming that `heat_storage` is the name of the stateful [Node](/pages/manual/yaml/core/node.md) that represents the underground storage, then: + +```julia +# ... other stuff in your addon ... + +function add_passive_charging(model::JuMP.Model) + c_passive_charging = IESopt.get_component(model, "passive_charging") + c_storage = IESopt.get_component(model, "heat_storage") + + IESopt.@constraint(model, [t in T], c_passive_charging.exp.value[t] <= 0.01 * (c_storage.state_ub - c_storage.var.state[t])) +end + +# ... other stuff in your addon ... +``` + +This ensures the passive charging at time `t` doesn't exceed 1% of the storage's remaining capacity. + +> Earlier we discussed the possibility of the model foregoing available passive charging, if economically beneficial. If that is something that you explicitly want to prevent, you can also use `=` in the above constraint, which will force the Profile to the exact value, without the possibility to charge less! + +## Summary + +### Steps to implement + +1. **Create the storage component**: Define your underground storage [Node](/pages/manual/yaml/core/node.md) without using `state_percentage_loss` for passive charging. +2. **Add the Passive Charging Input**: Introduce a Profile to represent the passive energy inflow. +3. **Set Profile Bounds**: Use the mode `ranged` for the Profile with appropriate lower (`lb`) and upper (`ub`) bounds. +4. **Add the Constraint**: Implement the constraint to tie the passive charging rate to the storage's unfilled capacity. +5. **Refine as Needed**: Adjust the constraint for more complex behaviors if necessary. There might be a lot (!) that you might want to specialize. + +### Conclusion + +By modeling passive charging as a constrained input energy flow based on the storage's remaining capacity, you accurately represent the thermal interactions of an underground heat storage with its environment. diff --git a/_sources/pages/user_guides/qna/profiles_sign.md.txt b/_sources/pages/user_guides/qna/profiles_sign.md.txt new file mode 100644 index 0000000..d2a8635 --- /dev/null +++ b/_sources/pages/user_guides/qna/profiles_sign.md.txt @@ -0,0 +1,69 @@ +# Sign convention for time series + +## Intro + +**Question:** +> How does IESopt interpret positive and negative values in a Profile connected using `node_from`? + +**Answer:** +> When modeling energy systems in IESopt, you might have a fixed Profile representing both energy demand and supply — the proper sign of generation / consumption then depends on the way it is configured and connects to other components. Understanding how to correctly connect this Profile to a [Node](/pages/manual/yaml/core/node.md) is crucial for accurate results. + +## Details + +### Understanding `node_from` and `node_to` + +Generally the following intuition applies: + +- **`node_from`:** Indicates that the component draws energy from the specified [Node](/pages/manual/yaml/core/node.md). +- **`node_to`:** Indicates that the component injects energy into the specified [Node](/pages/manual/yaml/core/node.md). + +#### Example: `node_from` + +If you connect your fixed Profile (`mode: fixed`, which is the default) to a [Node](/pages/manual/yaml/core/node.md) using `node_from`, here's how IESopt interprets the values: + +- **Positive Values:** The Profile draws energy from the [Node](/pages/manual/yaml/core/node.md) (consumption). +- **Negative Values:** The Profile effectively injects energy into the [Node](/pages/manual/yaml/core/node.md) (generation). + +### Why does this happen? + +In IESopt, drawing a negative amount of energy from a [Node](/pages/manual/yaml/core/node.md) (`-x kWh`) is mathematically equivalent to injecting a positive amount of energy (`+x kWh`) into it. This means, the following are equal: + +- A negative value in a `node_from`-configured Profile +- A positive value in a `node_to`-configured Profile +- Injecting `x > 0` units of energy into a [Node](/pages/manual/yaml/core/node.md) +- Withdrawing a negative amount of energy from a [Node](/pages/manual/yaml/core/node.md) + +## Practical example + +Suppose you have a fixed Profile connected to an electricity grid [Node](/pages/manual/yaml/core/node.md): + +```yaml +myprofile: + type: Profile + carrier: electricity + node_from: elec_grid_node + value: [100, -50, 150, -75] # in kW +``` + +- **At time step 1:** The [Profile](/pages/manual/yaml/core/profile.md) draws with a power of 100 kW from the [Node](/pages/manual/yaml/core/node.md) (consumption). +- **At time step 2:** The [Profile](/pages/manual/yaml/core/profile.md) injects with a power of 50 kW into the [Node](/pages/manual/yaml/core/node.md) (generation). +- **And so on.** + +### Alternative approach with `node_to` + +If you prefer to handle injections explicitly, you can use `node_to`: + +- Connect your [Profile](/pages/manual/yaml/core/profile.md) using `node_to`. +- Positive values will inject energy into the [Node](/pages/manual/yaml/core/node.md). +- Negative values will draw energy from the [Node](/pages/manual/yaml/core/node.md). + +## Summary + +- **Using `node_from`:** + - Positive values = Draw from [Node](/pages/manual/yaml/core/node.md). + - Negative values = Inject into [Node](/pages/manual/yaml/core/node.md). +- **Using `node_to`:** + - Positive values = Inject into [Node](/pages/manual/yaml/core/node.md). + - Negative values = Draw from [Node](/pages/manual/yaml/core/node.md). + +By correctly setting up your Profile and understanding the sign conventions, you ensure that your model accurately reflects the intended energy flows. \ No newline at end of file diff --git a/_sources/pages/user_guides/qna/snapshot_duration.md.txt b/_sources/pages/user_guides/qna/snapshot_duration.md.txt new file mode 100644 index 0000000..0e9fd4c --- /dev/null +++ b/_sources/pages/user_guides/qna/snapshot_duration.md.txt @@ -0,0 +1,119 @@ +# Time resolution & power vs. energy + +## Intro + +**Question:** +> I'm moving from hourly to daily time steps in my IESopt model. How should I adjust my input data, especially capacities and costs, to ensure accurate results? Should I express capacities as energy per time step (e.g., kWh/day) instead of power (kW)? + +**Answer:** +> When working with IESopt models, it's crucial to understand how the model interprets units of power and energy, especially when changing the duration of your time steps (snapshots). + +## Details + +The following steps guide you through the changes: + +1. Update the duration of each time step using the `weights` parameter in your `snapshots` configuration. +2. Make sure that you are using the correct power/energy convention for configuring the model's components. +3. Resample time series data, where necessary. + +### Configuring snapshots + +First, update your `snapshots` section of the top-level YAML config. This can be used not only to set the total number of snapshots in your model, but also how long each one is, using the `weights` parameter. + +```yaml +config: + optimization: + # ... other settings ... + snapshots: + count: 365 # Number of time steps (e.g., days in a year) + weights: 24 # Duration of each time step in hours (for daily steps) +``` + +> **Example:** +> If each time step represents one day, setting `weights: 24` tells the model that each snapshot spans 24 hours. + +#### Time step duration is handled internally + +Accounting for the "conversion" between power and energy terms is handled internally: + +- IESopt multiplies power by the duration of each time step to calculate energy. +- You don't need to convert capacities to energy units per time step; the model does this for you. + +> Internally, the total cost is calculated using a formula along the lines of: +> +> $$ +> \text{Cost (€)} = \text{Power (kW)} \times \text{Duration (h)} \times \text{Cost per Energy Unit (€/kWh)} +> $$ + +### Checking power/energy units + +#### Keep capacities in power units + +- **Unit Capacities:** Always express capacities in power units (e.g., kW), regardless of the time step duration. +- **Profiles and Connections:** Similarly, [Profiles](/pages/manual/yaml/core/profile.md) (e.g., time series data) and [connection](/pages/manual/yaml/core/connection.md) bounds should remain in power units. + +#### Costs are per unit of energy + +- **Variable Costs:** Input costs as monetary units per unit of energy (e.g., €/kWh). +- **Consistency:** This approach ensures that cost calculations remain accurate, as the model internally accounts for time step durations. + +#### Storage units are in energy units + +- **Exception for storage:** Capacities for storage units are specified in energy units (e.g., kWh) because they represent stored energy, not power output. + +#### Practical example + +Suppose you have a generator with: + +- **Capacity:** 100 kW +- **Marginal cost:** 0.10 €/kWh +- **Time step duration:** 24 hours (daily) + +The model calculates the energy produced and the cost as follows: + +- **Energy produced per time step:** + + $$ + \text{Energy (kWh)} = \text{Power (kW)} \times \text{Duration (h)} = 100 \times 24 = 2,400 \text{ kWh} + $$ + +- **Total cost per time step:** + + $$ + \text{Cost (€)} = \text{Energy (kWh)} \times \text{Cost per Energy Unit (€/kWh)} = 2,400 \times 0.10 = 240 \text{ €} + $$ + +### Resampling time series data + +Sometimes, or rather most of the time, you will need to update the input time series data. Not because a change of units happened, but because moving from hourly to daily snapshots will essentially reduce the length of your data by the factor 24. A quick "checklist" on what to use: + +- Resampling a time series given in a power unit is done by taking **the mean** of all grouped time steps _(you should almost only need this)_. +- Resampling a time series given in an energy unit is done by taking **the sum** of all grouped time steps _(you most likely never need this)_. + +> If you are using Python, a simple way to achieve this is using the `groupby` or `resample` functionalities that a `pandas.DataFrame` offers. + +#### Example + +Consider a time series of energy demand. This is used as input to configure a Profile. That means: **It's given in power units** - and if it's not then make sure it is, because the reason it worked for a "1-hourly" model was only because the numerical values mapping `kW -> kWh` are the same; however, for any other time resolution that fails. + +If we now aggregate / group 24 time steps, and then the next 24 and so on, then the most common way to calculate the proper daily power is by taking the mean. Let's assume the demand is 10 kW for the first 12 hours, and 20 kW for the last 12 hours. Taking the mean would tell us to use 15 kW as value of our demand time series, when using a daily resolution. + +_Let's check if that is correct ..._ + +1. The total energy consumption in the first 12 hours is 120 kWh, and 240 kWh for the last 12 hours, for a total of 360 kWh during the day. +2. A resampled demand time series of 15 kW would results in $15 kW \times 24h = 360 kWh$. + +So, we see that the energy consumed stays the same. Note: Other methods of resampling the power time series could be valid in certain cases, but this here is the only one that preserves the total energy consumed - which is what drives costs in our model and is therefore one of the most important parameters. + +## Summary + +### Key takeaways + +- **No need to adjust units:** Keep capacities in power units; do not convert them to energy per time step. +- **Costs remain per energy unit:** Continue to provide costs in €/kWh or similar units. +- **Model handles duration:** The model uses the `weights` parameter to account for the duration of each time step in calculations. +- **Consistency is crucial:** By keeping units consistent, you ensure accurate modeling results without additional adjustments. + +### Conclusion + +Changing the time step duration in your IESopt model doesn't require you to alter your input units. By specifying capacities in power units and costs per unit of energy, and by correctly configuring the time step durations, IESopt will accurately compute the energy and costs internally. \ No newline at end of file diff --git a/_sources/pages/user_guides/toc_general.md.txt b/_sources/pages/user_guides/toc_general.md.txt new file mode 100644 index 0000000..06ed54a --- /dev/null +++ b/_sources/pages/user_guides/toc_general.md.txt @@ -0,0 +1,19 @@ +# General + +Crafted user guides covering a specific topic. These are mostly used to explain advanced topics, that are too complex to fully explain in a tutorial, or are linked as "dive deeper" options to read up on something. + +## Contents + +:::{toctree} +:maxdepth: 1 +:titlesonly: + +general/best_practice.md +general/linking_components.md +general/common_errors.md +general/solvers.md +::: + +## Format & Structure + +Currently, the general topic user guides do not follow a specific format. diff --git a/_sources/pages/user_guides/toc_qna.md.txt b/_sources/pages/user_guides/toc_qna.md.txt new file mode 100644 index 0000000..3e8d158 --- /dev/null +++ b/_sources/pages/user_guides/toc_qna.md.txt @@ -0,0 +1,65 @@ +# Q & A + +These are based on previous questions that users had and discussions that occurred to clarify. They can be used to point users to for any questions that repeatedly occur. + +## Contents + +:::{toctree} +:maxdepth: 1 +:titlesonly: + +qna/snapshot_duration.md +qna/profiles_sign.md +qna/dynamic_marginal_costs.md +qna/passive_charging.md +::: + +## Assisted writing + +To simplify the process of converting these interactions into actually usable user guides for the future, one may apply a LLM of their choice. If doing that, the following base prompt might be helpful (**tip: hover your mouse & one-click copy in the top right**): + +```text +You are a seasoned developer and work on energy system optimization models; write a short and concise tutorial / user-guide based on the interaction of a user with our support and Q&A team, that covers the discussed topic and addresses the initial problem or misunderstanding. Make sure to keep it short. Use simple and generally understandable wording. You can assume basic familiarity with programming, especially with Python. Output everything in markdown format. Start the tutorial with a proper short first-level header, followed by a "virtual question" that a user might ask themself. Base that question on the submitted information - no need to repeat any question directly verbatim. Remove all names from your answer if there are any. Header texts should start with an upper case letter, but use proper English case (mostly lower) for all other words. + +Background information is available in the following documentation: https://ait-energy.github.io/iesopt/ + +The exchange below in triple quotes is the chat that you can use: + +""" +... add stuff here ... +""" +``` + +Make sure to read over the returned text and properly check it for various commonly occurring issues. Proceed with caution ... + +## Format & Structure + +> Only loosely defined guidelines, feel free to do what's best for the docs. + +These user guides roughly follow the following structure: + +```markdown +# Title header + +## Intro + +**Question:** +> I'm moving from hourly to daily time steps in my IESopt model. How should I adjust my input data, especially capacities and costs, to ensure accurate results? Should I express capacities as energy per time step (e.g., kWh/day) instead of power (kW)? + +**Answer:** +> When working with IESopt models, it's crucial to understand how the model interprets units of power and energy, especially when changing the duration of your time steps (snapshots). Here's how to approach this: + +## Details + +_... a more detailed answer and further explanations go here ..._ + +## Summary + +_... a summary goes here ..._ + +_... often this is the place for further notes, comments, key takeaways ..._ + +### Conclusion + +_... and finally a 1-3 sentence conclusion ..._ +``` diff --git a/_sources/tocs/tutorials_extracting_results.md.txt b/_sources/tocs/tutorials_extracting_results.md.txt new file mode 100644 index 0000000..db82667 --- /dev/null +++ b/_sources/tocs/tutorials_extracting_results.md.txt @@ -0,0 +1,8 @@ +# Extracting results + +:::{toctree} +:maxdepth: 2 + +Part I <../notebooks/custom_results_1> +Part II <../notebooks/custom_results_2> +::: diff --git a/_sources/tocs/tutorials_templates.md.txt b/_sources/tocs/tutorials_templates.md.txt new file mode 100644 index 0000000..16e61db --- /dev/null +++ b/_sources/tocs/tutorials_templates.md.txt @@ -0,0 +1,8 @@ +# Templates + +:::{toctree} +:maxdepth: 2 + +Part I <../pages/tutorials/templates_1.ipynb> +Part II <../pages/tutorials/templates_2.ipynb> +::: diff --git a/_static/0fc70aa4dfe4d16d7073.woff b/_static/0fc70aa4dfe4d16d7073.woff new file mode 100644 index 0000000..2a9fff0 Binary files /dev/null and b/_static/0fc70aa4dfe4d16d7073.woff differ diff --git a/_static/583e3f428bf2362b546d.woff b/_static/583e3f428bf2362b546d.woff new file mode 100644 index 0000000..e2e3d20 Binary files /dev/null and b/_static/583e3f428bf2362b546d.woff differ diff --git a/_static/5be6ec379613f10aea3f.woff b/_static/5be6ec379613f10aea3f.woff new file mode 100644 index 0000000..dc524f7 Binary files /dev/null and b/_static/5be6ec379613f10aea3f.woff differ diff --git a/_static/76c1862325ea6f70eeff.woff2 b/_static/76c1862325ea6f70eeff.woff2 new file mode 100644 index 0000000..3d3c5d7 Binary files /dev/null and b/_static/76c1862325ea6f70eeff.woff2 differ diff --git a/_static/83710c128240451d95af.woff b/_static/83710c128240451d95af.woff new file mode 100644 index 0000000..f89b15f Binary files /dev/null and b/_static/83710c128240451d95af.woff differ diff --git a/_static/a63d39a1c104a2b3e87e.woff2 b/_static/a63d39a1c104a2b3e87e.woff2 new file mode 100644 index 0000000..3149aac Binary files /dev/null and b/_static/a63d39a1c104a2b3e87e.woff2 differ diff --git a/_static/awesome-docsearch.css b/_static/awesome-docsearch.css new file mode 100644 index 0000000..3332a0a --- /dev/null +++ b/_static/awesome-docsearch.css @@ -0,0 +1 @@ +:root{--docsearch-primary-color:hsl(var(--primary));--docsearch-muted-color:hsl(var(--muted-foreground));--docsearch-key-gradient:transparent;--docsearch-key-shadow:transparent;--docsearch-text-color:hsl(var(--popover-foreground));--docsearch-modal-width:760px;--docsearch-modal-background:hsl(var(--popover));--docsearch-footer-background:hsl(var(--popover));--docsearch-searchbox-focus-background:hsl(var(--popover));--docsearch-container-background:hsl(var(--background)/0.8);--docsearch-spacing:0.5rem;--docsearch-hit-active-color:hsl(var(--accent-foreground));--docsearch-hit-background:transparent;--docsearch-searchbox-shadow:none;--docsearch-hit-shadow:none;--docsearch-modal-shadow:none;--docsearch-footer-shadow:none}.DocSearch-Button{background-color:initial;border-color:hsl(var(--input));border-radius:.5em;border-style:solid;border-width:1px;display:flex;font-size:.875rem;line-height:1.25rem;width:90%;--tw-ring-offset-color:hsl(var(--background));transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.DocSearch-Button:hover{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.DocSearch-Button:focus,.DocSearch-Button:hover{background-color:hsl(var(--accent));color:hsl(var(--accent-foreground))}.DocSearch-Button:focus-visible{outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:hsl(var(--ring));--tw-ring-offset-width:2px}.DocSearch-Button-Placeholder{display:block;font-size:.875rem;font-weight:500;line-height:1.25rem}.DocSearch-Button-Key{background-color:hsl(var(--muted));border-color:hsl(var(--border));border-radius:.25rem;border-style:solid;border-width:1px;color:hsl(var(--muted-foreground));font-size:12px}.DocSearch-Container{position:fixed;--tw-backdrop-blur:blur(4px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.DocSearch-Modal{border-color:hsl(var(--border));border-radius:var(--radius);border-width:1px}.DocSearch-SearchBar{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom-width:1px;border-color:hsl(var(--input));border-top-left-radius:var(--radius);border-top-right-radius:var(--radius);padding:0}.DocSearch-Form{border-bottom-left-radius:0;border-bottom-right-radius:0;border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.DocSearch-Cancel{color:hsl(var(--muted-foreground));font-size:.875rem;line-height:1.25rem;padding-left:.5rem;padding-right:.5rem}.DocSearch-MagnifierLabel,.DocSearch-Search-Icon{stroke-width:2;opacity:.5}.DocSearch-Hit-source{color:hsl(var(--muted-foreground))}.DocSearch-Hit,.DocSearch-Hit a{border-radius:calc(var(--radius) - 4px)}.DocSearch-Hit a:focus-visible{outline-offset:-2px}.DocSearch-Hit[aria-selected=true] a{background-color:hsl(var(--accent));color:hsl(var(--accent-foreground))}.DocSearch-Commands{display:none}.DocSearch-Footer{border-color:hsl(var(--border));border-top-width:1px} diff --git a/_static/awesome-docsearch.js b/_static/awesome-docsearch.js new file mode 100644 index 0000000..e69de29 diff --git a/_static/awesome-sphinx-design.css b/_static/awesome-sphinx-design.css new file mode 100644 index 0000000..a13c5d8 --- /dev/null +++ b/_static/awesome-sphinx-design.css @@ -0,0 +1 @@ +:root{--sd-color-tabs-label-active:hsl(var(--foreground));--sd-color-tabs-underline-active:hsl(var(--accent-foreground));--sd-color-tabs-label-hover:hsl(var(--accent-foreground));--sd-color-tabs-overline:hsl(var(--border));--sd-color-tabs-underline:hsl(var(--border))}.sd-card{background-color:hsl(var(--card));border-color:hsl(var(--border));border-radius:var(--radius);border-width:1px;color:hsl(var(--card-foreground));margin-top:1.5rem}.sd-container-fluid{margin-bottom:1.5rem;margin-top:1.5rem}.sd-card-title{font-weight:600!important}.sd-summary-title{color:hsl(var(--muted-foreground));font-weight:500!important}.sd-card-footer,.sd-card-header{font-size:.875rem;line-height:1.25rem}.sd-tab-set{margin-top:1.5rem}.sd-tab-content>p{margin-bottom:1.5rem}.sd-tab-content pre:first-of-type{margin-top:0}.sd-tab-set>label{font-weight:500;letter-spacing:.05em}details.sd-dropdown,details.sd-dropdown:not([open])>.sd-card-header{border-color:hsl(var(--border))}details.sd-dropdown summary:focus{outline-style:solid}.sd-cards-carousel{overflow-x:auto}.sd-shadow-sm{--tw-shadow:0 0 #0000!important;--tw-shadow-colored:0 0 #0000!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important} diff --git a/_static/awesome-sphinx-design.js b/_static/awesome-sphinx-design.js new file mode 100644 index 0000000..e69de29 diff --git a/_static/b659956119f91f2342bc.woff2 b/_static/b659956119f91f2342bc.woff2 new file mode 100644 index 0000000..69e029c Binary files /dev/null and b/_static/b659956119f91f2342bc.woff2 differ diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..7ebbd6d --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,914 @@ +/* + * Sphinx stylesheet -- basic theme. + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin-top: 10px; +} + +ul.search li { + padding: 5px 0; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/bb50084be2b43ba7b98c.woff2 b/_static/bb50084be2b43ba7b98c.woff2 new file mode 100644 index 0000000..be878e6 Binary files /dev/null and b/_static/bb50084be2b43ba7b98c.woff2 differ diff --git a/_static/ce1e40901d7a0d88d483.woff2 b/_static/ce1e40901d7a0d88d483.woff2 new file mode 100644 index 0000000..3a4e333 Binary files /dev/null and b/_static/ce1e40901d7a0d88d483.woff2 differ diff --git a/_static/custom.css b/_static/custom.css new file mode 100644 index 0000000..f3dbeb3 --- /dev/null +++ b/_static/custom.css @@ -0,0 +1,35 @@ +/* + Format code related blocks from MyST-NB + - Standard background color + - stderr and similar blocks with accent color + - Stream and text blocks with muted color +*/ +div.cell_input.docutils.container { + background-color: hsl(var(--background)) !important; +} +div.output.stderr > div.highlight, div.output.stdout > div.highlight { + background-color: hsl(var(--accent)) !important; +} +div.output.stream, div.output.text_plain, div.output.text_html { + background-color: hsl(var(--muted)) !important; +} + +/* + Format tables (needed to render pd.DataFrames): + - Standard background color + - Alternate row background color (odd rows) + - Text color to foreground color + - Hover row background color based on destructive color (50% opacity) +*/ +div.cell_output tbody { + background: hsl(var(--background)) !important; +} +div.cell_output tbody tr:nth-child(odd) { + background: hsl(var(--muted)) !important; +} +div.cell_output table { + color: hsl(var(--foreground)) !important; +} +div.cell_output tbody tr:hover { + background: hsl(var(--destructive) / 0.5) !important; +} diff --git a/_static/d04352f240062b100fba.woff2 b/_static/d04352f240062b100fba.woff2 new file mode 100644 index 0000000..5858873 Binary files /dev/null and b/_static/d04352f240062b100fba.woff2 differ diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..0398ebb --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,149 @@ +/* + * Base JavaScript utilities for all Sphinx HTML documentation. + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..9bd42bc --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '2.4.3.dev0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/f1cdf5c21de970ee0592.woff b/_static/f1cdf5c21de970ee0592.woff new file mode 100644 index 0000000..4342415 Binary files /dev/null and b/_static/f1cdf5c21de970ee0592.woff differ diff --git a/_static/fd994e8d90d9cab651b0.woff b/_static/fd994e8d90d9cab651b0.woff new file mode 100644 index 0000000..3c971ff Binary files /dev/null and b/_static/fd994e8d90d9cab651b0.woff differ diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 0000000..c7fe6c6 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,192 @@ +/* + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css b/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css new file mode 100644 index 0000000..3356631 --- /dev/null +++ b/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css @@ -0,0 +1,2342 @@ +/* Variables */ +:root { + --mystnb-source-bg-color: #f7f7f7; + --mystnb-stdout-bg-color: #fcfcfc; + --mystnb-stderr-bg-color: #fdd; + --mystnb-traceback-bg-color: #fcfcfc; + --mystnb-source-border-color: #ccc; + --mystnb-source-margin-color: green; + --mystnb-stdout-border-color: #f7f7f7; + --mystnb-stderr-border-color: #f7f7f7; + --mystnb-traceback-border-color: #ffd6d6; + --mystnb-hide-prompt-opacity: 70%; + --mystnb-source-border-radius: .4em; + --mystnb-source-border-width: 1px; +} + +/* Whole cell */ +div.container.cell { + padding-left: 0; + margin-bottom: 1em; +} + +/* Removing all background formatting so we can control at the div level */ +.cell_input div.highlight, +.cell_output pre, +.cell_input pre, +.cell_output .output { + border: none; + box-shadow: none; +} + +.cell_output .output pre, +.cell_input pre { + margin: 0px; +} + +/* Input cells */ +div.cell div.cell_input, +div.cell details.above-input>summary { + padding-left: 0em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + background-color: var(--mystnb-source-bg-color); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; + border-radius: var(--mystnb-source-border-radius); +} + +div.cell_input>div, +div.cell_output div.output>div.highlight { + margin: 0em !important; + border: none !important; +} + +/* All cell outputs */ +.cell_output { + padding-left: 1em; + padding-right: 0em; + margin-top: 1em; +} + +/* Text outputs from cells */ +.cell_output .output.text_plain, +.cell_output .output.traceback, +.cell_output .output.stream, +.cell_output .output.stderr { + margin-top: 1em; + margin-bottom: 0em; + box-shadow: none; +} + +.cell_output .output.text_plain, +.cell_output .output.stream { + background: var(--mystnb-stdout-bg-color); + border: 1px solid var(--mystnb-stdout-border-color); +} + +.cell_output .output.stderr { + background: var(--mystnb-stderr-bg-color); + border: 1px solid var(--mystnb-stderr-border-color); +} + +.cell_output .output.traceback { + background: var(--mystnb-traceback-bg-color); + border: 1px solid var(--mystnb-traceback-border-color); +} + +/* Collapsible cell content */ +div.cell details.above-input div.cell_input { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed; +} + +div.cell div.cell_input.above-output-prompt { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +div.cell details.above-input>summary { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed; + padding-left: 1em; + margin-bottom: 0; +} + +div.cell details.above-output>summary { + background-color: var(--mystnb-source-bg-color); + padding-left: 1em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + border-radius: var(--mystnb-source-border-radius); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; +} + +div.cell details.below-input>summary { + background-color: var(--mystnb-source-bg-color); + padding-left: 1em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + border-top: none; + border-bottom-left-radius: var(--mystnb-source-border-radius); + border-bottom-right-radius: var(--mystnb-source-border-radius); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; +} + +div.cell details.hide>summary>span { + opacity: var(--mystnb-hide-prompt-opacity); +} + +div.cell details.hide[open]>summary>span.collapsed { + display: none; +} + +div.cell details.hide:not([open])>summary>span.expanded { + display: none; +} + +@keyframes collapsed-fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} +div.cell details.hide[open]>summary~* { + -moz-animation: collapsed-fade-in 0.3s ease-in-out; + -webkit-animation: collapsed-fade-in 0.3s ease-in-out; + animation: collapsed-fade-in 0.3s ease-in-out; +} + +/* Math align to the left */ +.cell_output .MathJax_Display { + text-align: left !important; +} + +/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */ +div.cell_output table { + border: none; + border-collapse: collapse; + border-spacing: 0; + color: black; + font-size: 1em; + table-layout: fixed; +} + +div.cell_output thead { + border-bottom: 1px solid black; + vertical-align: bottom; +} + +div.cell_output tr, +div.cell_output th, +div.cell_output td { + text-align: right; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} + +div.cell_output th { + font-weight: bold; +} + +div.cell_output tbody tr:nth-child(odd) { + background: #f5f5f5; +} + +div.cell_output tbody tr:hover { + background: rgba(66, 165, 245, 0.2); +} + +/** source code line numbers **/ +span.linenos { + opacity: 0.5; +} + +/* Inline text from `paste` operation */ + +span.pasted-text { + font-weight: bold; +} + +span.pasted-inline img { + max-height: 2em; +} + +tbody span.pasted-inline img { + max-height: none; +} + +/* Font colors for translated ANSI escape sequences +Color values are copied from Jupyter Notebook +https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21 +Background colors from +https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors +*/ +div.highlight .-Color-Bold { + font-weight: bold; +} + +div.highlight .-Color[class*=-Black] { + color: #3E424D +} + +div.highlight .-Color[class*=-Red] { + color: #E75C58 +} + +div.highlight .-Color[class*=-Green] { + color: #00A250 +} + +div.highlight .-Color[class*=-Yellow] { + color: #DDB62B +} + +div.highlight .-Color[class*=-Blue] { + color: #208FFB +} + +div.highlight .-Color[class*=-Magenta] { + color: #D160C4 +} + +div.highlight .-Color[class*=-Cyan] { + color: #60C6C8 +} + +div.highlight .-Color[class*=-White] { + color: #C5C1B4 +} + +div.highlight .-Color[class*=-BGBlack] { + background-color: #3E424D +} + +div.highlight .-Color[class*=-BGRed] { + background-color: #E75C58 +} + +div.highlight .-Color[class*=-BGGreen] { + background-color: #00A250 +} + +div.highlight .-Color[class*=-BGYellow] { + background-color: #DDB62B +} + +div.highlight .-Color[class*=-BGBlue] { + background-color: #208FFB +} + +div.highlight .-Color[class*=-BGMagenta] { + background-color: #D160C4 +} + +div.highlight .-Color[class*=-BGCyan] { + background-color: #60C6C8 +} + +div.highlight .-Color[class*=-BGWhite] { + background-color: #C5C1B4 +} + +/* Font colors for 8-bit ANSI */ + +div.highlight .-Color[class*=-C0] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC0] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C1] { + color: #800000 +} + +div.highlight .-Color[class*=-BGC1] { + background-color: #800000 +} + +div.highlight .-Color[class*=-C2] { + color: #008000 +} + +div.highlight .-Color[class*=-BGC2] { + background-color: #008000 +} + +div.highlight .-Color[class*=-C3] { + color: #808000 +} + +div.highlight .-Color[class*=-BGC3] { + background-color: #808000 +} + +div.highlight .-Color[class*=-C4] { + color: #000080 +} + +div.highlight .-Color[class*=-BGC4] { + background-color: #000080 +} + +div.highlight .-Color[class*=-C5] { + color: #800080 +} + +div.highlight .-Color[class*=-BGC5] { + background-color: #800080 +} + +div.highlight .-Color[class*=-C6] { + color: #008080 +} + +div.highlight .-Color[class*=-BGC6] { + background-color: #008080 +} + +div.highlight .-Color[class*=-C7] { + color: #C0C0C0 +} + +div.highlight .-Color[class*=-BGC7] { + background-color: #C0C0C0 +} + +div.highlight .-Color[class*=-C8] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC8] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C9] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC9] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C10] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC10] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C11] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC11] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C12] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC12] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C13] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC13] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C14] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC14] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C15] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC15] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C16] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC16] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C17] { + color: #00005F +} + +div.highlight .-Color[class*=-BGC17] { + background-color: #00005F +} + +div.highlight .-Color[class*=-C18] { + color: #000087 +} + +div.highlight .-Color[class*=-BGC18] { + background-color: #000087 +} + +div.highlight .-Color[class*=-C19] { + color: #0000AF +} + +div.highlight .-Color[class*=-BGC19] { + background-color: #0000AF +} + +div.highlight .-Color[class*=-C20] { + color: #0000D7 +} + +div.highlight .-Color[class*=-BGC20] { + background-color: #0000D7 +} + +div.highlight .-Color[class*=-C21] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC21] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C22] { + color: #005F00 +} + +div.highlight .-Color[class*=-BGC22] { + background-color: #005F00 +} + +div.highlight .-Color[class*=-C23] { + color: #005F5F +} + +div.highlight .-Color[class*=-BGC23] { + background-color: #005F5F +} + +div.highlight .-Color[class*=-C24] { + color: #005F87 +} + +div.highlight .-Color[class*=-BGC24] { + background-color: #005F87 +} + +div.highlight .-Color[class*=-C25] { + color: #005FAF +} + +div.highlight .-Color[class*=-BGC25] { + background-color: #005FAF +} + +div.highlight .-Color[class*=-C26] { + color: #005FD7 +} + +div.highlight .-Color[class*=-BGC26] { + background-color: #005FD7 +} + +div.highlight .-Color[class*=-C27] { + color: #005FFF +} + +div.highlight .-Color[class*=-BGC27] { + background-color: #005FFF +} + +div.highlight .-Color[class*=-C28] { + color: #008700 +} + +div.highlight .-Color[class*=-BGC28] { + background-color: #008700 +} + +div.highlight .-Color[class*=-C29] { + color: #00875F +} + +div.highlight .-Color[class*=-BGC29] { + background-color: #00875F +} + +div.highlight .-Color[class*=-C30] { + color: #008787 +} + +div.highlight .-Color[class*=-BGC30] { + background-color: #008787 +} + +div.highlight .-Color[class*=-C31] { + color: #0087AF +} + +div.highlight .-Color[class*=-BGC31] { + background-color: #0087AF +} + +div.highlight .-Color[class*=-C32] { + color: #0087D7 +} + +div.highlight .-Color[class*=-BGC32] { + background-color: #0087D7 +} + +div.highlight .-Color[class*=-C33] { + color: #0087FF +} + +div.highlight .-Color[class*=-BGC33] { + background-color: #0087FF +} + +div.highlight .-Color[class*=-C34] { + color: #00AF00 +} + +div.highlight .-Color[class*=-BGC34] { + background-color: #00AF00 +} + +div.highlight .-Color[class*=-C35] { + color: #00AF5F +} + +div.highlight .-Color[class*=-BGC35] { + background-color: #00AF5F +} + +div.highlight .-Color[class*=-C36] { + color: #00AF87 +} + +div.highlight .-Color[class*=-BGC36] { + background-color: #00AF87 +} + +div.highlight .-Color[class*=-C37] { + color: #00AFAF +} + +div.highlight .-Color[class*=-BGC37] { + background-color: #00AFAF +} + +div.highlight .-Color[class*=-C38] { + color: #00AFD7 +} + +div.highlight .-Color[class*=-BGC38] { + background-color: #00AFD7 +} + +div.highlight .-Color[class*=-C39] { + color: #00AFFF +} + +div.highlight .-Color[class*=-BGC39] { + background-color: #00AFFF +} + +div.highlight .-Color[class*=-C40] { + color: #00D700 +} + +div.highlight .-Color[class*=-BGC40] { + background-color: #00D700 +} + +div.highlight .-Color[class*=-C41] { + color: #00D75F +} + +div.highlight .-Color[class*=-BGC41] { + background-color: #00D75F +} + +div.highlight .-Color[class*=-C42] { + color: #00D787 +} + +div.highlight .-Color[class*=-BGC42] { + background-color: #00D787 +} + +div.highlight .-Color[class*=-C43] { + color: #00D7AF +} + +div.highlight .-Color[class*=-BGC43] { + background-color: #00D7AF +} + +div.highlight .-Color[class*=-C44] { + color: #00D7D7 +} + +div.highlight .-Color[class*=-BGC44] { + background-color: #00D7D7 +} + +div.highlight .-Color[class*=-C45] { + color: #00D7FF +} + +div.highlight .-Color[class*=-BGC45] { + background-color: #00D7FF +} + +div.highlight .-Color[class*=-C46] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC46] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C47] { + color: #00FF5F +} + +div.highlight .-Color[class*=-BGC47] { + background-color: #00FF5F +} + +div.highlight .-Color[class*=-C48] { + color: #00FF87 +} + +div.highlight .-Color[class*=-BGC48] { + background-color: #00FF87 +} + +div.highlight .-Color[class*=-C49] { + color: #00FFAF +} + +div.highlight .-Color[class*=-BGC49] { + background-color: #00FFAF +} + +div.highlight .-Color[class*=-C50] { + color: #00FFD7 +} + +div.highlight .-Color[class*=-BGC50] { + background-color: #00FFD7 +} + +div.highlight .-Color[class*=-C51] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC51] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C52] { + color: #5F0000 +} + +div.highlight .-Color[class*=-BGC52] { + background-color: #5F0000 +} + +div.highlight .-Color[class*=-C53] { + color: #5F005F +} + +div.highlight .-Color[class*=-BGC53] { + background-color: #5F005F +} + +div.highlight .-Color[class*=-C54] { + color: #5F0087 +} + +div.highlight .-Color[class*=-BGC54] { + background-color: #5F0087 +} + +div.highlight .-Color[class*=-C55] { + color: #5F00AF +} + +div.highlight .-Color[class*=-BGC55] { + background-color: #5F00AF +} + +div.highlight .-Color[class*=-C56] { + color: #5F00D7 +} + +div.highlight .-Color[class*=-BGC56] { + background-color: #5F00D7 +} + +div.highlight .-Color[class*=-C57] { + color: #5F00FF +} + +div.highlight .-Color[class*=-BGC57] { + background-color: #5F00FF +} + +div.highlight .-Color[class*=-C58] { + color: #5F5F00 +} + +div.highlight .-Color[class*=-BGC58] { + background-color: #5F5F00 +} + +div.highlight .-Color[class*=-C59] { + color: #5F5F5F +} + +div.highlight .-Color[class*=-BGC59] { + background-color: #5F5F5F +} + +div.highlight .-Color[class*=-C60] { + color: #5F5F87 +} + +div.highlight .-Color[class*=-BGC60] { + background-color: #5F5F87 +} + +div.highlight .-Color[class*=-C61] { + color: #5F5FAF +} + +div.highlight .-Color[class*=-BGC61] { + background-color: #5F5FAF +} + +div.highlight .-Color[class*=-C62] { + color: #5F5FD7 +} + +div.highlight .-Color[class*=-BGC62] { + background-color: #5F5FD7 +} + +div.highlight .-Color[class*=-C63] { + color: #5F5FFF +} + +div.highlight .-Color[class*=-BGC63] { + background-color: #5F5FFF +} + +div.highlight .-Color[class*=-C64] { + color: #5F8700 +} + +div.highlight .-Color[class*=-BGC64] { + background-color: #5F8700 +} + +div.highlight .-Color[class*=-C65] { + color: #5F875F +} + +div.highlight .-Color[class*=-BGC65] { + background-color: #5F875F +} + +div.highlight .-Color[class*=-C66] { + color: #5F8787 +} + +div.highlight .-Color[class*=-BGC66] { + background-color: #5F8787 +} + +div.highlight .-Color[class*=-C67] { + color: #5F87AF +} + +div.highlight .-Color[class*=-BGC67] { + background-color: #5F87AF +} + +div.highlight .-Color[class*=-C68] { + color: #5F87D7 +} + +div.highlight .-Color[class*=-BGC68] { + background-color: #5F87D7 +} + +div.highlight .-Color[class*=-C69] { + color: #5F87FF +} + +div.highlight .-Color[class*=-BGC69] { + background-color: #5F87FF +} + +div.highlight .-Color[class*=-C70] { + color: #5FAF00 +} + +div.highlight .-Color[class*=-BGC70] { + background-color: #5FAF00 +} + +div.highlight .-Color[class*=-C71] { + color: #5FAF5F +} + +div.highlight .-Color[class*=-BGC71] { + background-color: #5FAF5F +} + +div.highlight .-Color[class*=-C72] { + color: #5FAF87 +} + +div.highlight .-Color[class*=-BGC72] { + background-color: #5FAF87 +} + +div.highlight .-Color[class*=-C73] { + color: #5FAFAF +} + +div.highlight .-Color[class*=-BGC73] { + background-color: #5FAFAF +} + +div.highlight .-Color[class*=-C74] { + color: #5FAFD7 +} + +div.highlight .-Color[class*=-BGC74] { + background-color: #5FAFD7 +} + +div.highlight .-Color[class*=-C75] { + color: #5FAFFF +} + +div.highlight .-Color[class*=-BGC75] { + background-color: #5FAFFF +} + +div.highlight .-Color[class*=-C76] { + color: #5FD700 +} + +div.highlight .-Color[class*=-BGC76] { + background-color: #5FD700 +} + +div.highlight .-Color[class*=-C77] { + color: #5FD75F +} + +div.highlight .-Color[class*=-BGC77] { + background-color: #5FD75F +} + +div.highlight .-Color[class*=-C78] { + color: #5FD787 +} + +div.highlight .-Color[class*=-BGC78] { + background-color: #5FD787 +} + +div.highlight .-Color[class*=-C79] { + color: #5FD7AF +} + +div.highlight .-Color[class*=-BGC79] { + background-color: #5FD7AF +} + +div.highlight .-Color[class*=-C80] { + color: #5FD7D7 +} + +div.highlight .-Color[class*=-BGC80] { + background-color: #5FD7D7 +} + +div.highlight .-Color[class*=-C81] { + color: #5FD7FF +} + +div.highlight .-Color[class*=-BGC81] { + background-color: #5FD7FF +} + +div.highlight .-Color[class*=-C82] { + color: #5FFF00 +} + +div.highlight .-Color[class*=-BGC82] { + background-color: #5FFF00 +} + +div.highlight .-Color[class*=-C83] { + color: #5FFF5F +} + +div.highlight .-Color[class*=-BGC83] { + background-color: #5FFF5F +} + +div.highlight .-Color[class*=-C84] { + color: #5FFF87 +} + +div.highlight .-Color[class*=-BGC84] { + background-color: #5FFF87 +} + +div.highlight .-Color[class*=-C85] { + color: #5FFFAF +} + +div.highlight .-Color[class*=-BGC85] { + background-color: #5FFFAF +} + +div.highlight .-Color[class*=-C86] { + color: #5FFFD7 +} + +div.highlight .-Color[class*=-BGC86] { + background-color: #5FFFD7 +} + +div.highlight .-Color[class*=-C87] { + color: #5FFFFF +} + +div.highlight .-Color[class*=-BGC87] { + background-color: #5FFFFF +} + +div.highlight .-Color[class*=-C88] { + color: #870000 +} + +div.highlight .-Color[class*=-BGC88] { + background-color: #870000 +} + +div.highlight .-Color[class*=-C89] { + color: #87005F +} + +div.highlight .-Color[class*=-BGC89] { + background-color: #87005F +} + +div.highlight .-Color[class*=-C90] { + color: #870087 +} + +div.highlight .-Color[class*=-BGC90] { + background-color: #870087 +} + +div.highlight .-Color[class*=-C91] { + color: #8700AF +} + +div.highlight .-Color[class*=-BGC91] { + background-color: #8700AF +} + +div.highlight .-Color[class*=-C92] { + color: #8700D7 +} + +div.highlight .-Color[class*=-BGC92] { + background-color: #8700D7 +} + +div.highlight .-Color[class*=-C93] { + color: #8700FF +} + +div.highlight .-Color[class*=-BGC93] { + background-color: #8700FF +} + +div.highlight .-Color[class*=-C94] { + color: #875F00 +} + +div.highlight .-Color[class*=-BGC94] { + background-color: #875F00 +} + +div.highlight .-Color[class*=-C95] { + color: #875F5F +} + +div.highlight .-Color[class*=-BGC95] { + background-color: #875F5F +} + +div.highlight .-Color[class*=-C96] { + color: #875F87 +} + +div.highlight .-Color[class*=-BGC96] { + background-color: #875F87 +} + +div.highlight .-Color[class*=-C97] { + color: #875FAF +} + +div.highlight .-Color[class*=-BGC97] { + background-color: #875FAF +} + +div.highlight .-Color[class*=-C98] { + color: #875FD7 +} + +div.highlight .-Color[class*=-BGC98] { + background-color: #875FD7 +} + +div.highlight .-Color[class*=-C99] { + color: #875FFF +} + +div.highlight .-Color[class*=-BGC99] { + background-color: #875FFF +} + +div.highlight .-Color[class*=-C100] { + color: #878700 +} + +div.highlight .-Color[class*=-BGC100] { + background-color: #878700 +} + +div.highlight .-Color[class*=-C101] { + color: #87875F +} + +div.highlight .-Color[class*=-BGC101] { + background-color: #87875F +} + +div.highlight .-Color[class*=-C102] { + color: #878787 +} + +div.highlight .-Color[class*=-BGC102] { + background-color: #878787 +} + +div.highlight .-Color[class*=-C103] { + color: #8787AF +} + +div.highlight .-Color[class*=-BGC103] { + background-color: #8787AF +} + +div.highlight .-Color[class*=-C104] { + color: #8787D7 +} + +div.highlight .-Color[class*=-BGC104] { + background-color: #8787D7 +} + +div.highlight .-Color[class*=-C105] { + color: #8787FF +} + +div.highlight .-Color[class*=-BGC105] { + background-color: #8787FF +} + +div.highlight .-Color[class*=-C106] { + color: #87AF00 +} + +div.highlight .-Color[class*=-BGC106] { + background-color: #87AF00 +} + +div.highlight .-Color[class*=-C107] { + color: #87AF5F +} + +div.highlight .-Color[class*=-BGC107] { + background-color: #87AF5F +} + +div.highlight .-Color[class*=-C108] { + color: #87AF87 +} + +div.highlight .-Color[class*=-BGC108] { + background-color: #87AF87 +} + +div.highlight .-Color[class*=-C109] { + color: #87AFAF +} + +div.highlight .-Color[class*=-BGC109] { + background-color: #87AFAF +} + +div.highlight .-Color[class*=-C110] { + color: #87AFD7 +} + +div.highlight .-Color[class*=-BGC110] { + background-color: #87AFD7 +} + +div.highlight .-Color[class*=-C111] { + color: #87AFFF +} + +div.highlight .-Color[class*=-BGC111] { + background-color: #87AFFF +} + +div.highlight .-Color[class*=-C112] { + color: #87D700 +} + +div.highlight .-Color[class*=-BGC112] { + background-color: #87D700 +} + +div.highlight .-Color[class*=-C113] { + color: #87D75F +} + +div.highlight .-Color[class*=-BGC113] { + background-color: #87D75F +} + +div.highlight .-Color[class*=-C114] { + color: #87D787 +} + +div.highlight .-Color[class*=-BGC114] { + background-color: #87D787 +} + +div.highlight .-Color[class*=-C115] { + color: #87D7AF +} + +div.highlight .-Color[class*=-BGC115] { + background-color: #87D7AF +} + +div.highlight .-Color[class*=-C116] { + color: #87D7D7 +} + +div.highlight .-Color[class*=-BGC116] { + background-color: #87D7D7 +} + +div.highlight .-Color[class*=-C117] { + color: #87D7FF +} + +div.highlight .-Color[class*=-BGC117] { + background-color: #87D7FF +} + +div.highlight .-Color[class*=-C118] { + color: #87FF00 +} + +div.highlight .-Color[class*=-BGC118] { + background-color: #87FF00 +} + +div.highlight .-Color[class*=-C119] { + color: #87FF5F +} + +div.highlight .-Color[class*=-BGC119] { + background-color: #87FF5F +} + +div.highlight .-Color[class*=-C120] { + color: #87FF87 +} + +div.highlight .-Color[class*=-BGC120] { + background-color: #87FF87 +} + +div.highlight .-Color[class*=-C121] { + color: #87FFAF +} + +div.highlight .-Color[class*=-BGC121] { + background-color: #87FFAF +} + +div.highlight .-Color[class*=-C122] { + color: #87FFD7 +} + +div.highlight .-Color[class*=-BGC122] { + background-color: #87FFD7 +} + +div.highlight .-Color[class*=-C123] { + color: #87FFFF +} + +div.highlight .-Color[class*=-BGC123] { + background-color: #87FFFF +} + +div.highlight .-Color[class*=-C124] { + color: #AF0000 +} + +div.highlight .-Color[class*=-BGC124] { + background-color: #AF0000 +} + +div.highlight .-Color[class*=-C125] { + color: #AF005F +} + +div.highlight .-Color[class*=-BGC125] { + background-color: #AF005F +} + +div.highlight .-Color[class*=-C126] { + color: #AF0087 +} + +div.highlight .-Color[class*=-BGC126] { + background-color: #AF0087 +} + +div.highlight .-Color[class*=-C127] { + color: #AF00AF +} + +div.highlight .-Color[class*=-BGC127] { + background-color: #AF00AF +} + +div.highlight .-Color[class*=-C128] { + color: #AF00D7 +} + +div.highlight .-Color[class*=-BGC128] { + background-color: #AF00D7 +} + +div.highlight .-Color[class*=-C129] { + color: #AF00FF +} + +div.highlight .-Color[class*=-BGC129] { + background-color: #AF00FF +} + +div.highlight .-Color[class*=-C130] { + color: #AF5F00 +} + +div.highlight .-Color[class*=-BGC130] { + background-color: #AF5F00 +} + +div.highlight .-Color[class*=-C131] { + color: #AF5F5F +} + +div.highlight .-Color[class*=-BGC131] { + background-color: #AF5F5F +} + +div.highlight .-Color[class*=-C132] { + color: #AF5F87 +} + +div.highlight .-Color[class*=-BGC132] { + background-color: #AF5F87 +} + +div.highlight .-Color[class*=-C133] { + color: #AF5FAF +} + +div.highlight .-Color[class*=-BGC133] { + background-color: #AF5FAF +} + +div.highlight .-Color[class*=-C134] { + color: #AF5FD7 +} + +div.highlight .-Color[class*=-BGC134] { + background-color: #AF5FD7 +} + +div.highlight .-Color[class*=-C135] { + color: #AF5FFF +} + +div.highlight .-Color[class*=-BGC135] { + background-color: #AF5FFF +} + +div.highlight .-Color[class*=-C136] { + color: #AF8700 +} + +div.highlight .-Color[class*=-BGC136] { + background-color: #AF8700 +} + +div.highlight .-Color[class*=-C137] { + color: #AF875F +} + +div.highlight .-Color[class*=-BGC137] { + background-color: #AF875F +} + +div.highlight .-Color[class*=-C138] { + color: #AF8787 +} + +div.highlight .-Color[class*=-BGC138] { + background-color: #AF8787 +} + +div.highlight .-Color[class*=-C139] { + color: #AF87AF +} + +div.highlight .-Color[class*=-BGC139] { + background-color: #AF87AF +} + +div.highlight .-Color[class*=-C140] { + color: #AF87D7 +} + +div.highlight .-Color[class*=-BGC140] { + background-color: #AF87D7 +} + +div.highlight .-Color[class*=-C141] { + color: #AF87FF +} + +div.highlight .-Color[class*=-BGC141] { + background-color: #AF87FF +} + +div.highlight .-Color[class*=-C142] { + color: #AFAF00 +} + +div.highlight .-Color[class*=-BGC142] { + background-color: #AFAF00 +} + +div.highlight .-Color[class*=-C143] { + color: #AFAF5F +} + +div.highlight .-Color[class*=-BGC143] { + background-color: #AFAF5F +} + +div.highlight .-Color[class*=-C144] { + color: #AFAF87 +} + +div.highlight .-Color[class*=-BGC144] { + background-color: #AFAF87 +} + +div.highlight .-Color[class*=-C145] { + color: #AFAFAF +} + +div.highlight .-Color[class*=-BGC145] { + background-color: #AFAFAF +} + +div.highlight .-Color[class*=-C146] { + color: #AFAFD7 +} + +div.highlight .-Color[class*=-BGC146] { + background-color: #AFAFD7 +} + +div.highlight .-Color[class*=-C147] { + color: #AFAFFF +} + +div.highlight .-Color[class*=-BGC147] { + background-color: #AFAFFF +} + +div.highlight .-Color[class*=-C148] { + color: #AFD700 +} + +div.highlight .-Color[class*=-BGC148] { + background-color: #AFD700 +} + +div.highlight .-Color[class*=-C149] { + color: #AFD75F +} + +div.highlight .-Color[class*=-BGC149] { + background-color: #AFD75F +} + +div.highlight .-Color[class*=-C150] { + color: #AFD787 +} + +div.highlight .-Color[class*=-BGC150] { + background-color: #AFD787 +} + +div.highlight .-Color[class*=-C151] { + color: #AFD7AF +} + +div.highlight .-Color[class*=-BGC151] { + background-color: #AFD7AF +} + +div.highlight .-Color[class*=-C152] { + color: #AFD7D7 +} + +div.highlight .-Color[class*=-BGC152] { + background-color: #AFD7D7 +} + +div.highlight .-Color[class*=-C153] { + color: #AFD7FF +} + +div.highlight .-Color[class*=-BGC153] { + background-color: #AFD7FF +} + +div.highlight .-Color[class*=-C154] { + color: #AFFF00 +} + +div.highlight .-Color[class*=-BGC154] { + background-color: #AFFF00 +} + +div.highlight .-Color[class*=-C155] { + color: #AFFF5F +} + +div.highlight .-Color[class*=-BGC155] { + background-color: #AFFF5F +} + +div.highlight .-Color[class*=-C156] { + color: #AFFF87 +} + +div.highlight .-Color[class*=-BGC156] { + background-color: #AFFF87 +} + +div.highlight .-Color[class*=-C157] { + color: #AFFFAF +} + +div.highlight .-Color[class*=-BGC157] { + background-color: #AFFFAF +} + +div.highlight .-Color[class*=-C158] { + color: #AFFFD7 +} + +div.highlight .-Color[class*=-BGC158] { + background-color: #AFFFD7 +} + +div.highlight .-Color[class*=-C159] { + color: #AFFFFF +} + +div.highlight .-Color[class*=-BGC159] { + background-color: #AFFFFF +} + +div.highlight .-Color[class*=-C160] { + color: #D70000 +} + +div.highlight .-Color[class*=-BGC160] { + background-color: #D70000 +} + +div.highlight .-Color[class*=-C161] { + color: #D7005F +} + +div.highlight .-Color[class*=-BGC161] { + background-color: #D7005F +} + +div.highlight .-Color[class*=-C162] { + color: #D70087 +} + +div.highlight .-Color[class*=-BGC162] { + background-color: #D70087 +} + +div.highlight .-Color[class*=-C163] { + color: #D700AF +} + +div.highlight .-Color[class*=-BGC163] { + background-color: #D700AF +} + +div.highlight .-Color[class*=-C164] { + color: #D700D7 +} + +div.highlight .-Color[class*=-BGC164] { + background-color: #D700D7 +} + +div.highlight .-Color[class*=-C165] { + color: #D700FF +} + +div.highlight .-Color[class*=-BGC165] { + background-color: #D700FF +} + +div.highlight .-Color[class*=-C166] { + color: #D75F00 +} + +div.highlight .-Color[class*=-BGC166] { + background-color: #D75F00 +} + +div.highlight .-Color[class*=-C167] { + color: #D75F5F +} + +div.highlight .-Color[class*=-BGC167] { + background-color: #D75F5F +} + +div.highlight .-Color[class*=-C168] { + color: #D75F87 +} + +div.highlight .-Color[class*=-BGC168] { + background-color: #D75F87 +} + +div.highlight .-Color[class*=-C169] { + color: #D75FAF +} + +div.highlight .-Color[class*=-BGC169] { + background-color: #D75FAF +} + +div.highlight .-Color[class*=-C170] { + color: #D75FD7 +} + +div.highlight .-Color[class*=-BGC170] { + background-color: #D75FD7 +} + +div.highlight .-Color[class*=-C171] { + color: #D75FFF +} + +div.highlight .-Color[class*=-BGC171] { + background-color: #D75FFF +} + +div.highlight .-Color[class*=-C172] { + color: #D78700 +} + +div.highlight .-Color[class*=-BGC172] { + background-color: #D78700 +} + +div.highlight .-Color[class*=-C173] { + color: #D7875F +} + +div.highlight .-Color[class*=-BGC173] { + background-color: #D7875F +} + +div.highlight .-Color[class*=-C174] { + color: #D78787 +} + +div.highlight .-Color[class*=-BGC174] { + background-color: #D78787 +} + +div.highlight .-Color[class*=-C175] { + color: #D787AF +} + +div.highlight .-Color[class*=-BGC175] { + background-color: #D787AF +} + +div.highlight .-Color[class*=-C176] { + color: #D787D7 +} + +div.highlight .-Color[class*=-BGC176] { + background-color: #D787D7 +} + +div.highlight .-Color[class*=-C177] { + color: #D787FF +} + +div.highlight .-Color[class*=-BGC177] { + background-color: #D787FF +} + +div.highlight .-Color[class*=-C178] { + color: #D7AF00 +} + +div.highlight .-Color[class*=-BGC178] { + background-color: #D7AF00 +} + +div.highlight .-Color[class*=-C179] { + color: #D7AF5F +} + +div.highlight .-Color[class*=-BGC179] { + background-color: #D7AF5F +} + +div.highlight .-Color[class*=-C180] { + color: #D7AF87 +} + +div.highlight .-Color[class*=-BGC180] { + background-color: #D7AF87 +} + +div.highlight .-Color[class*=-C181] { + color: #D7AFAF +} + +div.highlight .-Color[class*=-BGC181] { + background-color: #D7AFAF +} + +div.highlight .-Color[class*=-C182] { + color: #D7AFD7 +} + +div.highlight .-Color[class*=-BGC182] { + background-color: #D7AFD7 +} + +div.highlight .-Color[class*=-C183] { + color: #D7AFFF +} + +div.highlight .-Color[class*=-BGC183] { + background-color: #D7AFFF +} + +div.highlight .-Color[class*=-C184] { + color: #D7D700 +} + +div.highlight .-Color[class*=-BGC184] { + background-color: #D7D700 +} + +div.highlight .-Color[class*=-C185] { + color: #D7D75F +} + +div.highlight .-Color[class*=-BGC185] { + background-color: #D7D75F +} + +div.highlight .-Color[class*=-C186] { + color: #D7D787 +} + +div.highlight .-Color[class*=-BGC186] { + background-color: #D7D787 +} + +div.highlight .-Color[class*=-C187] { + color: #D7D7AF +} + +div.highlight .-Color[class*=-BGC187] { + background-color: #D7D7AF +} + +div.highlight .-Color[class*=-C188] { + color: #D7D7D7 +} + +div.highlight .-Color[class*=-BGC188] { + background-color: #D7D7D7 +} + +div.highlight .-Color[class*=-C189] { + color: #D7D7FF +} + +div.highlight .-Color[class*=-BGC189] { + background-color: #D7D7FF +} + +div.highlight .-Color[class*=-C190] { + color: #D7FF00 +} + +div.highlight .-Color[class*=-BGC190] { + background-color: #D7FF00 +} + +div.highlight .-Color[class*=-C191] { + color: #D7FF5F +} + +div.highlight .-Color[class*=-BGC191] { + background-color: #D7FF5F +} + +div.highlight .-Color[class*=-C192] { + color: #D7FF87 +} + +div.highlight .-Color[class*=-BGC192] { + background-color: #D7FF87 +} + +div.highlight .-Color[class*=-C193] { + color: #D7FFAF +} + +div.highlight .-Color[class*=-BGC193] { + background-color: #D7FFAF +} + +div.highlight .-Color[class*=-C194] { + color: #D7FFD7 +} + +div.highlight .-Color[class*=-BGC194] { + background-color: #D7FFD7 +} + +div.highlight .-Color[class*=-C195] { + color: #D7FFFF +} + +div.highlight .-Color[class*=-BGC195] { + background-color: #D7FFFF +} + +div.highlight .-Color[class*=-C196] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC196] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C197] { + color: #FF005F +} + +div.highlight .-Color[class*=-BGC197] { + background-color: #FF005F +} + +div.highlight .-Color[class*=-C198] { + color: #FF0087 +} + +div.highlight .-Color[class*=-BGC198] { + background-color: #FF0087 +} + +div.highlight .-Color[class*=-C199] { + color: #FF00AF +} + +div.highlight .-Color[class*=-BGC199] { + background-color: #FF00AF +} + +div.highlight .-Color[class*=-C200] { + color: #FF00D7 +} + +div.highlight .-Color[class*=-BGC200] { + background-color: #FF00D7 +} + +div.highlight .-Color[class*=-C201] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC201] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C202] { + color: #FF5F00 +} + +div.highlight .-Color[class*=-BGC202] { + background-color: #FF5F00 +} + +div.highlight .-Color[class*=-C203] { + color: #FF5F5F +} + +div.highlight .-Color[class*=-BGC203] { + background-color: #FF5F5F +} + +div.highlight .-Color[class*=-C204] { + color: #FF5F87 +} + +div.highlight .-Color[class*=-BGC204] { + background-color: #FF5F87 +} + +div.highlight .-Color[class*=-C205] { + color: #FF5FAF +} + +div.highlight .-Color[class*=-BGC205] { + background-color: #FF5FAF +} + +div.highlight .-Color[class*=-C206] { + color: #FF5FD7 +} + +div.highlight .-Color[class*=-BGC206] { + background-color: #FF5FD7 +} + +div.highlight .-Color[class*=-C207] { + color: #FF5FFF +} + +div.highlight .-Color[class*=-BGC207] { + background-color: #FF5FFF +} + +div.highlight .-Color[class*=-C208] { + color: #FF8700 +} + +div.highlight .-Color[class*=-BGC208] { + background-color: #FF8700 +} + +div.highlight .-Color[class*=-C209] { + color: #FF875F +} + +div.highlight .-Color[class*=-BGC209] { + background-color: #FF875F +} + +div.highlight .-Color[class*=-C210] { + color: #FF8787 +} + +div.highlight .-Color[class*=-BGC210] { + background-color: #FF8787 +} + +div.highlight .-Color[class*=-C211] { + color: #FF87AF +} + +div.highlight .-Color[class*=-BGC211] { + background-color: #FF87AF +} + +div.highlight .-Color[class*=-C212] { + color: #FF87D7 +} + +div.highlight .-Color[class*=-BGC212] { + background-color: #FF87D7 +} + +div.highlight .-Color[class*=-C213] { + color: #FF87FF +} + +div.highlight .-Color[class*=-BGC213] { + background-color: #FF87FF +} + +div.highlight .-Color[class*=-C214] { + color: #FFAF00 +} + +div.highlight .-Color[class*=-BGC214] { + background-color: #FFAF00 +} + +div.highlight .-Color[class*=-C215] { + color: #FFAF5F +} + +div.highlight .-Color[class*=-BGC215] { + background-color: #FFAF5F +} + +div.highlight .-Color[class*=-C216] { + color: #FFAF87 +} + +div.highlight .-Color[class*=-BGC216] { + background-color: #FFAF87 +} + +div.highlight .-Color[class*=-C217] { + color: #FFAFAF +} + +div.highlight .-Color[class*=-BGC217] { + background-color: #FFAFAF +} + +div.highlight .-Color[class*=-C218] { + color: #FFAFD7 +} + +div.highlight .-Color[class*=-BGC218] { + background-color: #FFAFD7 +} + +div.highlight .-Color[class*=-C219] { + color: #FFAFFF +} + +div.highlight .-Color[class*=-BGC219] { + background-color: #FFAFFF +} + +div.highlight .-Color[class*=-C220] { + color: #FFD700 +} + +div.highlight .-Color[class*=-BGC220] { + background-color: #FFD700 +} + +div.highlight .-Color[class*=-C221] { + color: #FFD75F +} + +div.highlight .-Color[class*=-BGC221] { + background-color: #FFD75F +} + +div.highlight .-Color[class*=-C222] { + color: #FFD787 +} + +div.highlight .-Color[class*=-BGC222] { + background-color: #FFD787 +} + +div.highlight .-Color[class*=-C223] { + color: #FFD7AF +} + +div.highlight .-Color[class*=-BGC223] { + background-color: #FFD7AF +} + +div.highlight .-Color[class*=-C224] { + color: #FFD7D7 +} + +div.highlight .-Color[class*=-BGC224] { + background-color: #FFD7D7 +} + +div.highlight .-Color[class*=-C225] { + color: #FFD7FF +} + +div.highlight .-Color[class*=-BGC225] { + background-color: #FFD7FF +} + +div.highlight .-Color[class*=-C226] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC226] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C227] { + color: #FFFF5F +} + +div.highlight .-Color[class*=-BGC227] { + background-color: #FFFF5F +} + +div.highlight .-Color[class*=-C228] { + color: #FFFF87 +} + +div.highlight .-Color[class*=-BGC228] { + background-color: #FFFF87 +} + +div.highlight .-Color[class*=-C229] { + color: #FFFFAF +} + +div.highlight .-Color[class*=-BGC229] { + background-color: #FFFFAF +} + +div.highlight .-Color[class*=-C230] { + color: #FFFFD7 +} + +div.highlight .-Color[class*=-BGC230] { + background-color: #FFFFD7 +} + +div.highlight .-Color[class*=-C231] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC231] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C232] { + color: #080808 +} + +div.highlight .-Color[class*=-BGC232] { + background-color: #080808 +} + +div.highlight .-Color[class*=-C233] { + color: #121212 +} + +div.highlight .-Color[class*=-BGC233] { + background-color: #121212 +} + +div.highlight .-Color[class*=-C234] { + color: #1C1C1C +} + +div.highlight .-Color[class*=-BGC234] { + background-color: #1C1C1C +} + +div.highlight .-Color[class*=-C235] { + color: #262626 +} + +div.highlight .-Color[class*=-BGC235] { + background-color: #262626 +} + +div.highlight .-Color[class*=-C236] { + color: #303030 +} + +div.highlight .-Color[class*=-BGC236] { + background-color: #303030 +} + +div.highlight .-Color[class*=-C237] { + color: #3A3A3A +} + +div.highlight .-Color[class*=-BGC237] { + background-color: #3A3A3A +} + +div.highlight .-Color[class*=-C238] { + color: #444444 +} + +div.highlight .-Color[class*=-BGC238] { + background-color: #444444 +} + +div.highlight .-Color[class*=-C239] { + color: #4E4E4E +} + +div.highlight .-Color[class*=-BGC239] { + background-color: #4E4E4E +} + +div.highlight .-Color[class*=-C240] { + color: #585858 +} + +div.highlight .-Color[class*=-BGC240] { + background-color: #585858 +} + +div.highlight .-Color[class*=-C241] { + color: #626262 +} + +div.highlight .-Color[class*=-BGC241] { + background-color: #626262 +} + +div.highlight .-Color[class*=-C242] { + color: #6C6C6C +} + +div.highlight .-Color[class*=-BGC242] { + background-color: #6C6C6C +} + +div.highlight .-Color[class*=-C243] { + color: #767676 +} + +div.highlight .-Color[class*=-BGC243] { + background-color: #767676 +} + +div.highlight .-Color[class*=-C244] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC244] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C245] { + color: #8A8A8A +} + +div.highlight .-Color[class*=-BGC245] { + background-color: #8A8A8A +} + +div.highlight .-Color[class*=-C246] { + color: #949494 +} + +div.highlight .-Color[class*=-BGC246] { + background-color: #949494 +} + +div.highlight .-Color[class*=-C247] { + color: #9E9E9E +} + +div.highlight .-Color[class*=-BGC247] { + background-color: #9E9E9E +} + +div.highlight .-Color[class*=-C248] { + color: #A8A8A8 +} + +div.highlight .-Color[class*=-BGC248] { + background-color: #A8A8A8 +} + +div.highlight .-Color[class*=-C249] { + color: #B2B2B2 +} + +div.highlight .-Color[class*=-BGC249] { + background-color: #B2B2B2 +} + +div.highlight .-Color[class*=-C250] { + color: #BCBCBC +} + +div.highlight .-Color[class*=-BGC250] { + background-color: #BCBCBC +} + +div.highlight .-Color[class*=-C251] { + color: #C6C6C6 +} + +div.highlight .-Color[class*=-BGC251] { + background-color: #C6C6C6 +} + +div.highlight .-Color[class*=-C252] { + color: #D0D0D0 +} + +div.highlight .-Color[class*=-BGC252] { + background-color: #D0D0D0 +} + +div.highlight .-Color[class*=-C253] { + color: #DADADA +} + +div.highlight .-Color[class*=-BGC253] { + background-color: #DADADA +} + +div.highlight .-Color[class*=-C254] { + color: #E4E4E4 +} + +div.highlight .-Color[class*=-BGC254] { + background-color: #E4E4E4 +} + +div.highlight .-Color[class*=-C255] { + color: #EEEEEE +} + +div.highlight .-Color[class*=-BGC255] { + background-color: #EEEEEE +} diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000..13e0e7f --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,159 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #F00 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #04D } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ +.highlight .no { color: #800 } /* Name.Constant */ +.highlight .nd { color: #A2F } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #00F } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #BBB } /* Text.Whitespace */ +.highlight .mb { color: #666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666 } /* Literal.Number.Float */ +.highlight .mh { color: #666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #00F } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666 } /* Literal.Number.Integer.Long */pre { line-height: 125%; } +td.linenos .normal { color: #3c4354; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: #3c4354; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #3c4354; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #3c4354; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.dark .highlight .hll { background-color: #6e7681 } +.dark .highlight { background: #1d2331; color: #D4D2C8 } +.dark .highlight .c { color: #7E8AA1 } /* Comment */ +.dark .highlight .err { color: #F88F7F } /* Error */ +.dark .highlight .esc { color: #D4D2C8 } /* Escape */ +.dark .highlight .g { color: #D4D2C8 } /* Generic */ +.dark .highlight .k { color: #FFAD66 } /* Keyword */ +.dark .highlight .l { color: #D5FF80 } /* Literal */ +.dark .highlight .n { color: #D4D2C8 } /* Name */ +.dark .highlight .o { color: #FFAD66 } /* Operator */ +.dark .highlight .x { color: #D4D2C8 } /* Other */ +.dark .highlight .p { color: #D4D2C8 } /* Punctuation */ +.dark .highlight .ch { color: #F88F7F; font-style: italic } /* Comment.Hashbang */ +.dark .highlight .cm { color: #7E8AA1 } /* Comment.Multiline */ +.dark .highlight .cp { color: #FFAD66; font-weight: bold } /* Comment.Preproc */ +.dark .highlight .cpf { color: #7E8AA1 } /* Comment.PreprocFile */ +.dark .highlight .c1 { color: #7E8AA1 } /* Comment.Single */ +.dark .highlight .cs { color: #7E8AA1; font-style: italic } /* Comment.Special */ +.dark .highlight .gd { color: #F88F7F; background-color: #3D1E20 } /* Generic.Deleted */ +.dark .highlight .ge { color: #D4D2C8; font-style: italic } /* Generic.Emph */ +.dark .highlight .ges { color: #D4D2C8 } /* Generic.EmphStrong */ +.dark .highlight .gr { color: #F88F7F } /* Generic.Error */ +.dark .highlight .gh { color: #D4D2C8 } /* Generic.Heading */ +.dark .highlight .gi { color: #6AD4AF; background-color: #19362C } /* Generic.Inserted */ +.dark .highlight .go { color: #7E8AA1 } /* Generic.Output */ +.dark .highlight .gp { color: #D4D2C8 } /* Generic.Prompt */ +.dark .highlight .gs { color: #D4D2C8; font-weight: bold } /* Generic.Strong */ +.dark .highlight .gu { color: #D4D2C8 } /* Generic.Subheading */ +.dark .highlight .gt { color: #F88F7F } /* Generic.Traceback */ +.dark .highlight .kc { color: #FFAD66 } /* Keyword.Constant */ +.dark .highlight .kd { color: #FFAD66 } /* Keyword.Declaration */ +.dark .highlight .kn { color: #FFAD66 } /* Keyword.Namespace */ +.dark .highlight .kp { color: #FFAD66 } /* Keyword.Pseudo */ +.dark .highlight .kr { color: #FFAD66 } /* Keyword.Reserved */ +.dark .highlight .kt { color: #73D0FF } /* Keyword.Type */ +.dark .highlight .ld { color: #D5FF80 } /* Literal.Date */ +.dark .highlight .m { color: #DFBFFF } /* Literal.Number */ +.dark .highlight .s { color: #D5FF80 } /* Literal.String */ +.dark .highlight .na { color: #FFD173 } /* Name.Attribute */ +.dark .highlight .nb { color: #FFD173 } /* Name.Builtin */ +.dark .highlight .nc { color: #73D0FF } /* Name.Class */ +.dark .highlight .no { color: #FFD173 } /* Name.Constant */ +.dark .highlight .nd { color: #7E8AA1; font-weight: bold; font-style: italic } /* Name.Decorator */ +.dark .highlight .ni { color: #95E6CB } /* Name.Entity */ +.dark .highlight .ne { color: #73D0FF } /* Name.Exception */ +.dark .highlight .nf { color: #FFD173 } /* Name.Function */ +.dark .highlight .nl { color: #D4D2C8 } /* Name.Label */ +.dark .highlight .nn { color: #D4D2C8 } /* Name.Namespace */ +.dark .highlight .nx { color: #D4D2C8 } /* Name.Other */ +.dark .highlight .py { color: #FFD173 } /* Name.Property */ +.dark .highlight .nt { color: #5CCFE6 } /* Name.Tag */ +.dark .highlight .nv { color: #D4D2C8 } /* Name.Variable */ +.dark .highlight .ow { color: #FFAD66 } /* Operator.Word */ +.dark .highlight .pm { color: #D4D2C8 } /* Punctuation.Marker */ +.dark .highlight .w { color: #D4D2C8 } /* Text.Whitespace */ +.dark .highlight .mb { color: #DFBFFF } /* Literal.Number.Bin */ +.dark .highlight .mf { color: #DFBFFF } /* Literal.Number.Float */ +.dark .highlight .mh { color: #DFBFFF } /* Literal.Number.Hex */ +.dark .highlight .mi { color: #DFBFFF } /* Literal.Number.Integer */ +.dark .highlight .mo { color: #DFBFFF } /* Literal.Number.Oct */ +.dark .highlight .sa { color: #F29E74 } /* Literal.String.Affix */ +.dark .highlight .sb { color: #D5FF80 } /* Literal.String.Backtick */ +.dark .highlight .sc { color: #D5FF80 } /* Literal.String.Char */ +.dark .highlight .dl { color: #D5FF80 } /* Literal.String.Delimiter */ +.dark .highlight .sd { color: #7E8AA1 } /* Literal.String.Doc */ +.dark .highlight .s2 { color: #D5FF80 } /* Literal.String.Double */ +.dark .highlight .se { color: #95E6CB } /* Literal.String.Escape */ +.dark .highlight .sh { color: #D5FF80 } /* Literal.String.Heredoc */ +.dark .highlight .si { color: #95E6CB } /* Literal.String.Interpol */ +.dark .highlight .sx { color: #95E6CB } /* Literal.String.Other */ +.dark .highlight .sr { color: #95E6CB } /* Literal.String.Regex */ +.dark .highlight .s1 { color: #D5FF80 } /* Literal.String.Single */ +.dark .highlight .ss { color: #DFBFFF } /* Literal.String.Symbol */ +.dark .highlight .bp { color: #5CCFE6 } /* Name.Builtin.Pseudo */ +.dark .highlight .fm { color: #FFD173 } /* Name.Function.Magic */ +.dark .highlight .vc { color: #D4D2C8 } /* Name.Variable.Class */ +.dark .highlight .vg { color: #D4D2C8 } /* Name.Variable.Global */ +.dark .highlight .vi { color: #D4D2C8 } /* Name.Variable.Instance */ +.dark .highlight .vm { color: #D4D2C8 } /* Name.Variable.Magic */ +.dark .highlight .il { color: #DFBFFF } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000..2c774d1 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,632 @@ +/* + * Sphinx JavaScript utilities for the full-text search. + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename, kind] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename, kind] = item; + + let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + SearchResultKind.title, + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + SearchResultKind.object, + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + SearchResultKind.text, + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/_static/theme.css b/_static/theme.css new file mode 100644 index 0000000..0cb8761 --- /dev/null +++ b/_static/theme.css @@ -0,0 +1,9 @@ +@font-face{font-display:swap;font-family:JetBrains Mono;font-style:italic;font-weight:400;src:url(76c1862325ea6f70eeff.woff2) format("woff2"),url(fd994e8d90d9cab651b0.woff) format("woff")} +@font-face{font-display:swap;font-family:JetBrains Mono;font-style:normal;font-weight:400;src:url(d04352f240062b100fba.woff2) format("woff2"),url(0fc70aa4dfe4d16d7073.woff) format("woff")} +@font-face{font-display:swap;font-family:JetBrains Mono;font-style:italic;font-weight:500;src:url(a63d39a1c104a2b3e87e.woff2) format("woff2"),url(83710c128240451d95af.woff) format("woff")} +@font-face{font-display:swap;font-family:JetBrains Mono;font-style:normal;font-weight:500;src:url(bb50084be2b43ba7b98c.woff2) format("woff2"),url(f1cdf5c21de970ee0592.woff) format("woff")} +@font-face{font-display:swap;font-family:JetBrains Mono;font-style:italic;font-weight:700;src:url(b659956119f91f2342bc.woff2) format("woff2"),url(583e3f428bf2362b546d.woff) format("woff")} +@font-face{font-display:swap;font-family:JetBrains Mono;font-style:normal;font-weight:700;src:url(ce1e40901d7a0d88d483.woff2) format("woff2"),url(5be6ec379613f10aea3f.woff) format("woff")} +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } + +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:JetBrains\ Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background:0 0% 100%;--foreground:222.2 47.4% 11.2%;--muted:210 40% 96.1%;--muted-foreground:215.4 16.3% 46.9%;--popover:0 0% 100%;--popover-foreground:222.2 47.4% 11.2%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--card:0 0% 100%;--card-foreground:222.2 47.4% 11.2%;--primary:222.2 47.4% 11.2%;--primary-foreground:210 40% 98%;--secondary:210 40% 96.1%;--secondary-foreground:222.2 47.4% 11.2%;--accent:210 40% 96.1%;--accent-foreground:222.2 47.4% 11.2%;--destructive:0 100% 50%;--destructive-foreground:210 40% 98%;--ring:215 20.2% 65.1%;--radius:0.5rem}.dark{--background:224 71% 4%;--foreground:213 31% 91%;--muted:223 47% 11%;--muted-foreground:215.4 16.3% 56.9%;--accent:216 34% 17%;--accent-foreground:210 40% 98%;--popover:224 71% 4%;--popover-foreground:215 20.2% 65.1%;--border:216 34% 17%;--input:216 34% 17%;--card:224 71% 4%;--card-foreground:213 31% 91%;--primary:210 40% 98%;--primary-foreground:222.2 47.4% 1.2%;--secondary:222.2 47.4% 11.2%;--secondary-foreground:210 40% 98%;--destructive:0 63% 31%;--destructive-foreground:210 40% 98%;--ring:216 34% 17%;--radius:0.5rem}.container{margin-left:auto;margin-right:auto;padding-left:2rem;padding-right:2rem;width:100%}@media (min-width:1400px){.container{max-width:1400px}}#content svg{display:inline}#content hr{border-color:hsl(var(--border));margin-bottom:1rem;margin-top:1rem}@media (min-width:768px){#content hr{margin-bottom:1.5rem;margin-top:1.5rem}}#content h1{font-size:2.25rem;font-weight:700;line-height:2.5rem;margin-bottom:.5rem}#content h2{border-bottom-width:1px;border-color:hsl(var(--border));font-size:1.875rem;font-weight:600;line-height:2.25rem;margin-top:3rem;padding-bottom:.5rem}#content h3{font-size:1.5rem;font-weight:600;line-height:2rem;margin-top:2rem}#content .rubric,#content h4{font-size:1.25rem;font-weight:600;line-height:1.75rem;margin-top:2rem}#content section{scroll-margin:5rem}#content section>p{line-height:1.75rem;margin-top:1.5rem}#content section>p:first-child{margin-top:0}#content section>p.lead{color:hsl(var(--muted-foreground));font-size:1.125rem;line-height:1.75rem}#content .centered{text-align:center}#content a.viewcode-back{color:hsl(var(--muted-foreground))!important;position:absolute;right:0}#content a:not(.toc-backref){color:hsl(var(--primary));font-weight:500;text-decoration-line:underline;text-decoration-thickness:from-font;text-underline-offset:4px}#content ul:not(.search){list-style-type:disc;margin-left:1.5rem;margin-top:1.5rem}#content ul:not(.search) p,#content ul:not(.search)>li{margin-top:1.5rem}#content ul:not(.search) ul{margin-top:0}#content ol{list-style-type:decimal;margin-left:1.5rem;margin-top:1.5rem}#content ol ::marker{font-weight:500}#content ol::marker{font-weight:500}#content ol p,#content ol>li{margin-top:1.5rem}#content ol ol{margin-top:0}#content dl{margin-top:1.5rem}#content dl dt:not(.sig){font-weight:500;margin-top:1.5rem}#content dl dt:not(.sig):first-child{margin-bottom:0;margin-top:0}#content dl dd{margin-left:1.5rem}#content dl p{margin-bottom:.5rem;margin-top:.5rem}#content .align-center{margin-left:auto;margin-right:auto;text-align:center}#content .align-right{margin-left:auto;text-align:right}#content img{margin-top:1.5rem}#content figure img{display:inline-block}#content figcaption{color:hsl(var(--muted-foreground));font-size:.875rem;line-height:1.25rem;margin-bottom:3rem}#content figcaption>*{margin-top:1rem}blockquote{border-left-width:2px;font-style:italic;margin-bottom:1.5rem;margin-top:1.5rem;padding-left:1.5rem}blockquote .attribution{font-style:normal;margin-top:.5rem}table{font-size:.875rem;line-height:1.25rem;margin-bottom:1.5rem;margin-top:1.5rem;width:100%}table caption{color:hsl(var(--muted-foreground));margin-bottom:1.5rem;text-align:left}table thead{border-bottom-width:1px;border-color:hsl(var(--border))}table th{font-weight:500;padding-bottom:.5rem;padding-left:.5rem;text-align:left}table th:first-child{padding-left:0}table th:is(.dark *){font-weight:600}table tbody tr{border-bottom-width:1px;border-color:hsl(var(--border))}table tbody td{padding:.5rem}table tbody td:first-child{padding-left:0}.footnote>.label{float:left;padding-right:.5rem}.footnote>:not(.label){margin-bottom:1.5rem;margin-left:2rem;margin-top:1.5rem}.footnote .footnote-reference,.footnote [role=doc-backlink]{text-decoration-line:none!important}.admonition{background-color:hsl(var(--background));border-color:hsl(var(--border));border-radius:var(--radius);border-width:1px;color:hsl(var(--foreground));font-size:.875rem;line-height:1.25rem;margin-bottom:1.5rem;margin-top:1.5rem;padding:1rem}.admonition p:not(.admonition-title){margin-top:.5rem}.admonition .admonition-title{margin-top:0!important}.admonition-title{font-weight:500}.admonition-title:is(.dark *){font-weight:600;letter-spacing:.025em}.note{--tw-border-opacity:1;border-color:rgb(2 132 199/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(240 249 255/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(12 74 110/var(--tw-text-opacity))}.note:is(.dark *){background-color:rgba(96,165,250,.15);--tw-text-opacity:1;color:rgb(224 242 254/var(--tw-text-opacity))}.hint,.tip{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity))}.hint:is(.dark *),.tip:is(.dark *){background-color:rgba(74,222,128,.15);--tw-text-opacity:1;color:rgb(220 252 231/var(--tw-text-opacity))}.danger,.error{--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity))}.danger:is(.dark *),.error:is(.dark *){background-color:hsla(0,91%,71%,.15);--tw-text-opacity:1;color:rgb(254 226 226/var(--tw-text-opacity))}.attention,.caution,.important,.warning{--tw-border-opacity:1;border-color:rgb(202 138 4/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(113 63 18/var(--tw-text-opacity))}.attention:is(.dark *),.caution:is(.dark *),.important:is(.dark *),.warning:is(.dark *){background-color:rgba(250,204,21,.15);--tw-text-opacity:1;color:rgb(254 249 195/var(--tw-text-opacity))}div.versionadded{border-left-width:3px;margin-top:1rem;--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem;padding:.25rem 1rem}div.versionadded p{margin-top:0!important}div.versionadded p:last-child{margin-bottom:0!important}div.versionadded .versionmodified{font-weight:500;--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity))}div.versionadded .versionmodified:is(.dark *){letter-spacing:.025em;--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}div.versionchanged{border-left-width:3px;margin-top:1rem;--tw-border-opacity:1;border-color:rgb(202 138 4/var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem;padding:.25rem 1rem}div.versionchanged p{margin-top:0!important}div.versionchanged p:last-child{margin-bottom:0!important}div.versionchanged .versionmodified{font-weight:500;--tw-text-opacity:1;color:rgb(113 63 18/var(--tw-text-opacity))}div.versionchanged .versionmodified:is(.dark *){letter-spacing:.025em;--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity))}div.deprecated{border-left-width:3px;margin-top:1rem;--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem;padding:.25rem 1rem}div.deprecated p{margin-top:0!important}div.deprecated p:last-child{margin-bottom:0!important}div.deprecated .versionmodified{font-weight:500;--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity))}div.deprecated .versionmodified:is(.dark *){letter-spacing:.025em;--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}.highlight{background-color:initial!important;position:relative}.highlight:hover .copy{opacity:1}.highlight .gp,.highlight-pycon .go,.highlight-python .go{-webkit-user-select:none;-moz-user-select:none;user-select:none}.literal-block-wrapper{border-color:hsl(var(--border));border-radius:var(--radius);border-width:1px;margin-left:0;margin-right:0;margin-top:1.5rem;max-width:none;padding-left:0;padding-right:0}.literal-block-wrapper pre{border-radius:0;border-style:none;margin-top:0}.literal-block-wrapper .code-block-caption{border-bottom-width:1px;border-color:hsl(var(--border));border-top-left-radius:var(--radius);border-top-right-radius:var(--radius);color:hsl(var(--muted-foreground));font-size:.875rem;letter-spacing:.025em;line-height:1.25rem;padding:.5rem 1rem}code{background-color:hsl(var(--muted));border-radius:.25rem;font-family:JetBrains\ Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem;padding:.2em .3em;position:relative;white-space:nowrap}code .ge,code em{color:hsl(var(--accent-foreground));font-weight:700;letter-spacing:.025em}:where(h1,h2,h3,h4,h5,h6) code{font-size:inherit}pre{border-color:hsl(var(--border));border-radius:var(--radius);border-width:1px;font-size:.875rem;line-height:1.25rem;margin-top:1.5rem;overflow-x:auto;padding-bottom:1rem;padding-top:1rem}pre[data-theme=dark]{background-color:hsl(var(--background))}pre[data-theme=light]{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}pre.literal-block{padding-left:1rem;padding-right:1rem}pre code{background-color:initial;padding:0;white-space:pre}pre code>[id^=line-]{display:block;padding-left:1rem;padding-right:1rem}pre code [id^=line-]:has(.gd),pre code [id^=line-]:has(.gi),pre code [id^=line-]:has(del),pre code [id^=line-]:has(ins),pre code [id^=line-]:has(mark){padding-left:0;padding-right:0}pre code [id^=line-] del,pre code [id^=line-] ins,pre code [id^=line-] mark{display:block;padding-left:1rem;padding-right:1rem;position:relative}pre code [id^=line-] mark{background-color:hsl(var(--muted));color:inherit;--tw-shadow:2px 0 currentColor inset;--tw-shadow-colored:inset 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}pre code [id^=line-] mark:is(.dark *){--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity));--tw-shadow:3px 0 currentColor inset;--tw-shadow-colored:inset 3px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}pre code [id^=line-] ins{background-color:rgba(34,197,94,.3);--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity));text-decoration-line:none}pre code [id^=line-] ins:before{left:2px;position:absolute;--tw-content:"\002b";content:var(--tw-content)}pre code [id^=line-] ins:is(.dark *){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(187 247 208/var(--tw-text-opacity))}pre code [id^=line-] del{background-color:rgba(239,68,68,.3);--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity));text-decoration-line:none}pre code [id^=line-] del:before{left:2px;position:absolute;--tw-content:"\2212";content:var(--tw-content)}pre code [id^=line-] del:is(.dark *){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(254 202 202/var(--tw-text-opacity))}pre span.linenos{background-color:initial!important;padding-left:0;padding-right:1rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}.highlight-diff .gi{background-color:rgba(34,197,94,.3);display:inline-block;padding-left:1rem;padding-right:1rem;width:100%;--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity))}.highlight-diff .gi:is(.dark *){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(187 247 208/var(--tw-text-opacity))}.highlight-diff .gd{background-color:rgba(239,68,68,.3);display:inline-block;padding-left:1rem;padding-right:1rem;width:100%;--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity))}.highlight-diff .gd:is(.dark *){--tw-bg-opacity:1;--tw-text-opacity:1;color:rgb(187 247 208/var(--tw-text-opacity))}.guilabel,.menuselection{border-color:hsl(var(--border));border-radius:calc(var(--radius) - 4px);border-width:1px;color:hsl(var(--accent-foreground));font-weight:500;padding:1px .5rem}#content kbd:not(.compound){background-color:hsl(var(--muted));border-radius:.25rem;border-width:1px;font-size:.875rem;font-weight:500;letter-spacing:.025em;line-height:1.25rem;padding:1px .25rem}.sig{border-color:hsl(var(--border));border-top-width:1px;font-family:JetBrains\ Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-weight:700;padding-top:1.5rem;scroll-margin:5rem}.sig-name{color:hsl(var(--accent-foreground))}em.property{color:hsl(var(--muted-foreground))}.option .sig-prename{font-style:italic}.viewcode-link{color:hsl(var(--muted-foreground));float:right}.option-list kbd{background-color:initial!important;border-style:none!important;font-size:1em!important;font-weight:700!important}dt .classifier{font-style:italic}dt .classifier:before{margin-right:.5rem;--tw-content:":";content:var(--tw-content)}.headerlink{align-items:center;display:inline-flex;margin-left:.25rem;position:relative;vertical-align:middle}.headerlink:after{z-index:1000000;-webkit-font-smoothing:subpixel-antialiased;letter-spacing:normal;text-shadow:none;text-transform:none;word-wrap:break-word;background-color:hsl(var(--muted));border-radius:calc(var(--radius) - 4px);content:attr(data-tooltip);display:none;pointer-events:none;position:absolute;white-space:pre;--tw-bg-opacity:0.75;color:hsl(var(--muted-foreground));font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:.75rem;font-weight:400;line-height:1rem;opacity:0;padding:.25rem;text-align:center;text-decoration-line:none}.headerlink:focus:after,.headerlink:focus:before,.headerlink:hover:after,.headerlink:hover:before{animation-delay:.2s;animation-duration:.4s;animation-fill-mode:forwards;animation-name:tooltip-appear;animation-timing-function:ease-in;display:inline-block;-webkit-text-decoration:none;text-decoration:none}.headerlink:after{margin-top:6px;right:50%;top:100%}.headerlink:before{border-bottom-color:#1a202c;bottom:-7px;margin-right:-6px;right:50%;top:auto}.headerlink:after{margin-right:-16px}.headerlink>*{visibility:hidden;fill:currentColor;color:hsl(var(--muted-foreground))}.headerlink:focus>*{visibility:visible}:is(h1,h2,h3,h4,table,.admonition-title,figure,dt,.code-block-caption):hover .headerlink{visibility:visible}:is(h1,h2,h3,h4,table,.admonition-title,figure,dt,.code-block-caption):hover .headerlink>*{visibility:visible}#left-sidebar .caption{border-radius:calc(var(--radius) - 2px);font-size:.875rem;font-weight:600;line-height:1.25rem;margin-bottom:.25rem;padding:1.5rem .5rem .25rem}#left-sidebar .caption:first-child{padding-top:0}#left-sidebar ul{display:grid;font-size:.875rem;grid-auto-flow:row;grid-auto-rows:max-content;line-height:1.25rem;overflow:hidden;transform:translate3d(var(--tw-translate-x),var(--tw-translate-y),0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-duration:.3s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (prefers-reduced-motion:reduce){#left-sidebar ul{transition-property:none}}#left-sidebar ul ul{margin-left:.75rem;opacity:1;padding:.5rem 0 .5rem .75rem;position:relative;transition-duration:.5s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}#left-sidebar ul ul:before{bottom:.25rem;left:0;position:absolute;top:.25rem;width:1px;--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}#left-sidebar ul ul:is(.dark *):before{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(38 38 38/var(--tw-bg-opacity))}#left-sidebar a{align-items:center;border-color:transparent;border-radius:calc(var(--radius) - 2px);border-width:1px;display:flex;padding:.375rem .5rem;width:100%}#left-sidebar a:hover{text-decoration-line:underline}#left-sidebar a:focus-visible{outline-offset:-1px}#left-sidebar a>button{border-radius:.25rem;color:hsl(var(--muted-foreground))}#left-sidebar a>button:hover{background-color:hsl(var(--primary)/.1)}#left-sidebar a>button>svg{transform:translate3d(var(--tw-translate-x),var(--tw-translate-y),0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transform-origin:center;transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}#left-sidebar a.current{background-color:hsl(var(--accent));border-color:hsl(var(--border));border-width:1px;color:hsl(var(--accent-foreground));font-weight:500}#left-sidebar a.expandable{justify-content:space-between}#left-sidebar a.expandable.expanded>button>svg{--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}#right-sidebar ul{margin:0}#right-sidebar ul li{margin-top:0;padding-top:.5rem}#right-sidebar ul li a{color:hsl(var(--muted-foreground));display:inline-block;text-decoration-line:none;transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}#right-sidebar ul li a:hover{color:hsl(var(--foreground))}#right-sidebar ul li a:focus-visible{outline-offset:-1px}#right-sidebar ul li a[data-current=true]{color:hsl(var(--foreground));font-weight:500}#right-sidebar ul li ul{padding-left:1rem}#right-sidebar ul:not(:last-child){padding-bottom:.5rem}.contents>:not([hidden])~:not([hidden]),.toctree-wrapper>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.contents,.toctree-wrapper{font-size:.875rem;line-height:1.25rem}.contents .caption,.contents .topic-title,.toctree-wrapper .caption,.toctree-wrapper .topic-title{font-weight:500;padding-top:1.5rem}.contents ul,.toctree-wrapper ul{list-style-type:none!important;margin:0!important}.contents ul li a.reference,.toctree-wrapper ul li a.reference{color:hsl(var(--muted-foreground))!important;display:inline-block;font-weight:400!important;text-decoration-line:none!important;transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.contents ul li a.reference:hover,.toctree-wrapper ul li a.reference:hover{color:hsl(var(--foreground))}.contents ul li ul,.toctree-wrapper ul li ul{padding-left:1rem}.contents ul:not(:last-child),.toctree-wrapper ul:not(:last-child){padding-bottom:.5rem}#search-results .search-summary{color:hsl(var(--muted-foreground));font-size:1.25rem;line-height:1.75rem;margin-top:1.5rem}#search-results ul.search,#search-results ul.search li{margin-top:1.5rem}#search-results ul.search .context{color:hsl(var(--muted-foreground));font-size:.875rem;line-height:1.25rem;margin-top:.5rem}.highlighted{background-color:hsl(var(--accent));text-decoration-line:underline;text-decoration-thickness:2px}.highlight-link{border-color:hsl(var(--border));border-radius:var(--radius);border-width:1px;font-size:.875rem;line-height:1.25rem;padding:.5rem 1rem;position:fixed;right:.5rem;top:4rem}.highlight-link:hover{background-color:hsl(var(--accent))}@media (min-width:1024px){.highlight-link{right:4rem}}.tooltipped{position:relative}.tooltipped:after{z-index:1000000;-webkit-font-smoothing:subpixel-antialiased;letter-spacing:normal;text-shadow:none;text-transform:none;word-wrap:break-word;background-color:hsl(var(--muted));border-radius:calc(var(--radius) - 4px);content:attr(data-tooltip);display:none;pointer-events:none;position:absolute;white-space:pre;--tw-bg-opacity:0.75;color:hsl(var(--muted-foreground));font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:.75rem;font-weight:400;line-height:1rem;opacity:0;padding:.25rem;text-align:center;text-decoration-line:none}@keyframes tooltip-appear{0%{opacity:0}to{opacity:1}}.tooltipped:focus:after,.tooltipped:focus:before,.tooltipped:hover:after,.tooltipped:hover:before{animation-delay:.2s;animation-duration:.4s;animation-fill-mode:forwards;animation-name:tooltip-appear;animation-timing-function:ease-in;display:inline-block;-webkit-text-decoration:none;text-decoration:none}.tooltipped-no-delay:focus:after,.tooltipped-no-delay:focus:before,.tooltipped-no-delay:hover:after,.tooltipped-no-delay:hover:before{animation-delay:0s}.tooltipped-multiline:focus:after,.tooltipped-multiline:hover:after{display:table-cell}.tooltipped-s:after,.tooltipped-se:after,.tooltipped-sw:after{margin-top:6px;right:50%;top:100%}.tooltipped-s:before,.tooltipped-se:before,.tooltipped-sw:before{border-bottom-color:#1a202c;bottom:-7px;margin-right:-6px;right:50%;top:auto}.tooltipped-se:after{left:50%;margin-left:-16px;right:auto}.tooltipped-sw:after{margin-right:-16px}.tooltipped-n:after,.tooltipped-ne:after,.tooltipped-nw:after{bottom:100%;margin-bottom:6px;right:50%}.tooltipped-n:before,.tooltipped-ne:before,.tooltipped-nw:before{border-top-color:#1a202c;bottom:auto;margin-right:-6px;right:50%;top:-7px}.tooltipped-ne:after{left:50%;margin-left:-16px;right:auto}.tooltipped-nw:after{margin-right:-16px}.tooltipped-n:after,.tooltipped-s:after{transform:translateX(50%)}.tooltipped-w:after{bottom:50%;margin-right:6px;right:100%;transform:translateY(50%)}.tooltipped-w:before{border-left-color:#1a202c;bottom:50%;left:-7px;margin-top:-6px;top:50%}.tooltipped-e:after{bottom:50%;left:100%;margin-left:6px;transform:translateY(50%)}.tooltipped-e:before{border-right-color:#1a202c;bottom:50%;margin-top:-6px;right:-7px;top:50%}.sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{bottom:0;top:0}.bottom-8{bottom:2rem}.left-0{left:0}.right-1\.5{right:.375rem}.right-4{right:1rem}.right-8{right:2rem}.top-0{top:0}.top-16{top:4rem}.top-2{top:.5rem}.top-4{top:1rem}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-bottom:1rem;margin-top:1rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-8{margin-bottom:2rem;margin-top:2rem}.-mt-10{margin-top:-2.5rem}.mb-4{margin-bottom:1rem}.mb-\[2px\]{margin-bottom:2px}.ml-0{margin-left:0}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-4{margin-right:1rem}.mr-6{margin-right:1.5rem}.mr-auto{margin-right:auto}.mt-12{margin-top:3rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.hidden{display:none}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-\[14px\]{height:14px}.h-\[calc\(100vh-8rem\)\]{height:calc(100vh - 8rem)}.h-full{height:100%}.max-h-\[calc\(100vh-5rem\)\]{max-height:calc(100vh - 5rem)}.max-h-\[calc\(var\(--vh\)-4rem\)\]{max-height:calc(var(--vh) - 4rem)}.min-h-screen{min-height:100vh}.w-4{width:1rem}.w-5\/6{width:83.333333%}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-\[14px\]{width:14px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-prose{max-width:65ch}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-x-full{--tw-translate-x:-100%}.-translate-x-full,.translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.rotate-0{--tw-rotate:0deg}.rotate-0,.rotate-90{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate:90deg}.scale-0{--tw-scale-x:0;--tw-scale-y:0}.scale-0,.scale-100{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.\!justify-start{justify-content:flex-start!important}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-4{gap:1rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1.5rem*var(--tw-space-x-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.scroll-smooth{scroll-behavior:smooth}.text-ellipsis{text-overflow:ellipsis}.text-clip{text-overflow:clip}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-\[0\.5rem\]{border-radius:.5rem}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-border{border-color:hsl(var(--border))}.border-input{border-color:hsl(var(--input))}.bg-background{background-color:hsl(var(--background))}.bg-background\/80{background-color:hsl(var(--background)/.8)}.bg-background\/95{background-color:hsl(var(--background)/.95)}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-muted{background-color:hsl(var(--muted))}.bg-transparent{background-color:initial}.fill-current{fill:currentColor}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-0{padding-left:0;padding-right:0}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.pr-6{padding-right:1.5rem}.pt-2{padding-top:.5rem}.pt-6{padding-top:1.5rem}.text-center{text-align:center}.font-mono{font-family:JetBrains\ Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.leading-loose{line-height:2}.text-foreground{color:hsl(var(--foreground))}.text-foreground\/60{color:hsl(var(--foreground)/.6)}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-offset-background{--tw-ring-offset-color:hsl(var(--background))}.backdrop-blur{--tw-backdrop-blur:blur(8px)}.backdrop-blur,.backdrop-blur-sm{backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-duration:.15s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-1000{transition-duration:1s}[x-cloak]{display:none!important}@media (max-width:640px){.container{padding-left:1rem;padding-right:1rem}}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-gray-950:hover{--tw-bg-opacity:1;background-color:rgb(3 7 18/var(--tw-bg-opacity))}.hover\:bg-muted:hover{background-color:hsl(var(--muted))}.hover\:bg-transparent:hover{background-color:initial}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:text-foreground\/80:hover{color:hsl(var(--foreground)/.8)}.hover\:placeholder-accent-foreground:hover::-moz-placeholder{color:hsl(var(--accent-foreground))}.hover\:placeholder-accent-foreground:hover::placeholder{color:hsl(var(--accent-foreground))}.hover\:opacity-100:hover{opacity:1}.focus\:translate-x-0:focus{--tw-translate-x:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:bg-gray-950:focus{--tw-bg-opacity:1;background-color:rgb(3 7 18/var(--tw-bg-opacity))}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:opacity-100:focus{opacity:1}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:outline-offset-\[-1px\]:focus-visible{outline-offset:-1px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:bg-accent{background-color:hsl(var(--accent))}.group:hover .group-hover\:text-accent-foreground{color:hsl(var(--accent-foreground))}.dark\:block:is(.dark *){display:block}.dark\:hidden:is(.dark *){display:none}.dark\:-rotate-90:is(.dark *){--tw-rotate:-90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.dark\:rotate-0:is(.dark *){--tw-rotate:0deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.dark\:scale-0:is(.dark *){--tw-scale-x:0;--tw-scale-y:0;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.dark\:scale-100:is(.dark *){--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.dark\:invert:is(.dark *){--tw-invert:invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width:640px){.sm\:inline-block{display:inline-block}.sm\:flex{display:flex}.sm\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.sm\:pr-12{padding-right:3rem}}@media (min-width:768px){.md\:sticky{position:sticky}.md\:top-14{top:3.5rem}.md\:z-30{z-index:30}.md\:my-0{margin-bottom:0;margin-top:0}.md\:-ml-2{margin-left:-.5rem}.md\:inline{display:inline}.md\:flex{display:flex}.md\:grid{display:grid}.md\:\!hidden{display:none!important}.md\:hidden{display:none}.md\:h-24{height:6rem}.md\:h-\[calc\(100vh-3\.5rem\)\]{height:calc(100vh - 3.5rem)}.md\:h-auto{height:auto}.md\:w-40{width:10rem}.md\:w-auto{width:auto}.md\:w-full{width:100%}.md\:flex-none{flex:none}.md\:translate-x-0{--tw-translate-x:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\:grid-cols-\[220px_minmax\(0\2c 1fr\)\]{grid-template-columns:220px minmax(0,1fr)}.md\:flex-row{flex-direction:row}.md\:justify-end{justify-content:flex-end}.md\:gap-2{gap:.5rem}.md\:gap-6{gap:1.5rem}.md\:overflow-auto{overflow:auto}.md\:bg-transparent{background-color:initial}.md\:p-0{padding:0}.md\:px-0{padding-left:0;padding-right:0}.md\:py-0{padding-bottom:0;padding-top:0}.md\:text-left{text-align:left}}@media (min-width:1024px){.lg\:my-8{margin-bottom:2rem;margin-top:2rem}.lg\:w-64{width:16rem}.lg\:grid-cols-\[240px_minmax\(0\2c 1fr\)\]{grid-template-columns:240px minmax(0,1fr)}.lg\:gap-10{gap:2.5rem}.lg\:py-8{padding-bottom:2rem;padding-top:2rem}}@media (min-width:1280px){.xl\:block{display:block}.xl\:grid{display:grid}.xl\:grid-cols-\[1fr_300px\]{grid-template-columns:1fr 300px}} diff --git a/_static/theme.js b/_static/theme.js new file mode 100644 index 0000000..830e8d8 --- /dev/null +++ b/_static/theme.js @@ -0,0 +1,2 @@ +/*! For license information please see theme.js.LICENSE.txt */ +(()=>{var e={122:function(e){var t;t=function(){return function(){var e={686:function(e,t,n){"use strict";n.d(t,{default:function(){return x}});var r=n(279),i=n.n(r),o=n(370),a=n.n(o),s=n(817),l=n.n(s);function c(e){try{return document.execCommand(e)}catch(e){return!1}}var u=function(e){var t=l()(e);return c("cut"),t},f=function(e,t){var n=function(e){var t="rtl"===document.documentElement.getAttribute("dir"),n=document.createElement("textarea");n.style.fontSize="12pt",n.style.border="0",n.style.padding="0",n.style.margin="0",n.style.position="absolute",n.style[t?"right":"left"]="-9999px";var r=window.pageYOffset||document.documentElement.scrollTop;return n.style.top="".concat(r,"px"),n.setAttribute("readonly",""),n.value=e,n}(e);t.container.appendChild(n);var r=l()(n);return c("copy"),n.remove(),r},d=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{container:document.body},n="";return"string"==typeof e?n=f(e,t):e instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(null==e?void 0:e.type)?n=f(e.value,t):(n=l()(e),c("copy")),n};function p(e){return p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p(e)}function _(e){return _="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_(e)}function h(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof e.action?e.action:this.defaultAction,this.target="function"==typeof e.target?e.target:this.defaultTarget,this.text="function"==typeof e.text?e.text:this.defaultText,this.container="object"===_(e.container)?e.container:document.body}},{key:"listenClick",value:function(e){var t=this;this.listener=a()(e,"click",(function(e){return t.onClick(e)}))}},{key:"onClick",value:function(e){var t=e.delegateTarget||e.currentTarget,n=this.action(t)||"copy",r=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.action,n=void 0===t?"copy":t,r=e.container,i=e.target,o=e.text;if("copy"!==n&&"cut"!==n)throw new Error('Invalid "action" value, use either "copy" or "cut"');if(void 0!==i){if(!i||"object"!==p(i)||1!==i.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===n&&i.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===n&&(i.hasAttribute("readonly")||i.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes')}return o?d(o,{container:r}):i?"cut"===n?u(i):d(i,{container:r}):void 0}({action:n,container:this.container,target:this.target(t),text:this.text(t)});this.emit(r?"success":"error",{action:n,text:r,trigger:t,clearSelection:function(){t&&t.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(e){return v("action",e)}},{key:"defaultTarget",value:function(e){var t=v("target",e);if(t)return document.querySelector(t)}},{key:"defaultText",value:function(e){return v("text",e)}},{key:"destroy",value:function(){this.listener.destroy()}}],r=[{key:"copy",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{container:document.body};return d(e,t)}},{key:"cut",value:function(e){return u(e)}},{key:"isSupported",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],t="string"==typeof e?[e]:e,n=!!document.queryCommandSupported;return t.forEach((function(e){n=n&&!!document.queryCommandSupported(e)})),n}}],n&&h(t.prototype,n),r&&h(t,r),l}(i()),x=g},828:function(e){if("undefined"!=typeof Element&&!Element.prototype.matches){var t=Element.prototype;t.matches=t.matchesSelector||t.mozMatchesSelector||t.msMatchesSelector||t.oMatchesSelector||t.webkitMatchesSelector}e.exports=function(e,t){for(;e&&9!==e.nodeType;){if("function"==typeof e.matches&&e.matches(t))return e;e=e.parentNode}}},438:function(e,t,n){var r=n(828);function i(e,t,n,r,i){var a=o.apply(this,arguments);return e.addEventListener(n,a,i),{destroy:function(){e.removeEventListener(n,a,i)}}}function o(e,t,n,i){return function(n){n.delegateTarget=r(n.target,t),n.delegateTarget&&i.call(e,n)}}e.exports=function(e,t,n,r,o){return"function"==typeof e.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof e&&(e=document.querySelectorAll(e)),Array.prototype.map.call(e,(function(e){return i(e,t,n,r,o)})))}},879:function(e,t){t.node=function(e){return void 0!==e&&e instanceof HTMLElement&&1===e.nodeType},t.nodeList=function(e){var n=Object.prototype.toString.call(e);return void 0!==e&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in e&&(0===e.length||t.node(e[0]))},t.string=function(e){return"string"==typeof e||e instanceof String},t.fn=function(e){return"[object Function]"===Object.prototype.toString.call(e)}},370:function(e,t,n){var r=n(879),i=n(438);e.exports=function(e,t,n){if(!e&&!t&&!n)throw new Error("Missing required arguments");if(!r.string(t))throw new TypeError("Second argument must be a String");if(!r.fn(n))throw new TypeError("Third argument must be a Function");if(r.node(e))return function(e,t,n){return e.addEventListener(t,n),{destroy:function(){e.removeEventListener(t,n)}}}(e,t,n);if(r.nodeList(e))return function(e,t,n){return Array.prototype.forEach.call(e,(function(e){e.addEventListener(t,n)})),{destroy:function(){Array.prototype.forEach.call(e,(function(e){e.removeEventListener(t,n)}))}}}(e,t,n);if(r.string(e))return function(e,t,n){return i(document.body,e,t,n)}(e,t,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}},817:function(e){e.exports=function(e){var t;if("SELECT"===e.nodeName)e.focus(),t=e.value;else if("INPUT"===e.nodeName||"TEXTAREA"===e.nodeName){var n=e.hasAttribute("readonly");n||e.setAttribute("readonly",""),e.select(),e.setSelectionRange(0,e.value.length),n||e.removeAttribute("readonly"),t=e.value}else{e.hasAttribute("contenteditable")&&e.focus();var r=window.getSelection(),i=document.createRange();i.selectNodeContents(e),r.removeAllRanges(),r.addRange(i),t=r.toString()}return t}},279:function(e){function t(){}t.prototype={on:function(e,t,n){var r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this},once:function(e,t,n){var r=this;function i(){r.off(e,i),t.apply(n,arguments)}return i._=t,this.on(e,i,n)},emit:function(e){for(var t=[].slice.call(arguments,1),n=((this.e||(this.e={}))[e]||[]).slice(),r=0,i=n.length;r{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";function e(e){if(e.includes("full"))return.99;if(e.includes("half"))return.5;if(!e.includes("threshold"))return 0;let t=e[e.indexOf("threshold")+1];return"100"===t?1:"0"===t?0:Number(`.${t}`)}function t(e){let t=e.match(/^(-?[0-9]+)(px|%)?$/);return t?t[1]+(t[2]||"px"):void 0}function r(e){const n="0px 0px 0px 0px",r=e.indexOf("margin");if(-1===r)return n;let i=[];for(let n=1;n<5;n++)i.push(t(e[r+n]||""));return i=i.filter((e=>void 0!==e)),i.length?i.join(" ").trim():n}var i,o,a,s,l=!1,c=!1,u=[],f=-1;function d(e){let t=u.indexOf(e);-1!==t&&t>f&&u.splice(t,1)}function p(){l=!1,c=!0;for(let e=0;e{let i=e();JSON.stringify(i),r?n=i:queueMicrotask((()=>{t(i,n),n=i})),r=!1}));return()=>a(i)}var y=[],v=[],g=[];function x(e,t){"function"==typeof t?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,v.push(t))}function b(e){y.push(e)}function w(e,t,n){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(n)}function E(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach((([n,r])=>{(void 0===t||t.includes(n))&&(r.forEach((e=>e())),delete e._x_attributeCleanups[n])}))}var S=new MutationObserver(L),A=!1;function O(){S.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),A=!0}function k(){!function(){let e=S.takeRecords();j.push((()=>e.length>0&&L(e)));let t=j.length;queueMicrotask((()=>{if(j.length===t)for(;j.length>0;)j.shift()()}))}(),S.disconnect(),A=!1}var j=[];function C(e){if(!A)return e();k();let t=e();return O(),t}var T=!1,$=[];function L(e){if(T)return void($=$.concat(e));let t=new Set,n=new Set,r=new Map,i=new Map;for(let o=0;o1===e.nodeType&&t.add(e))),e[o].removedNodes.forEach((e=>1===e.nodeType&&n.add(e)))),"attributes"===e[o].type)){let t=e[o].target,n=e[o].attributeName,a=e[o].oldValue,s=()=>{r.has(t)||r.set(t,[]),r.get(t).push({name:n,value:t.getAttribute(n)})},l=()=>{i.has(t)||i.set(t,[]),i.get(t).push(n)};t.hasAttribute(n)&&null===a?s():t.hasAttribute(n)?(l(),s()):l()}i.forEach(((e,t)=>{E(t,e)})),r.forEach(((e,t)=>{y.forEach((n=>n(t,e)))}));for(let e of n)t.has(e)||v.forEach((t=>t(e)));t.forEach((e=>{e._x_ignoreSelf=!0,e._x_ignore=!0}));for(let e of t)n.has(e)||e.isConnected&&(delete e._x_ignoreSelf,delete e._x_ignore,g.forEach((t=>t(e))),e._x_ignore=!0,e._x_ignoreSelf=!0);t.forEach((e=>{delete e._x_ignoreSelf,delete e._x_ignore})),t=null,n=null,r=null,i=null}function N(e){return R(P(e))}function M(e,t,n){return e._x_dataStack=[t,...P(n||e)],()=>{e._x_dataStack=e._x_dataStack.filter((e=>e!==t))}}function P(e){return e._x_dataStack?e._x_dataStack:"function"==typeof ShadowRoot&&e instanceof ShadowRoot?P(e.host):e.parentNode?P(e.parentNode):[]}function R(e){return new Proxy({objects:e},q)}var q={ownKeys:({objects:e})=>Array.from(new Set(e.flatMap((e=>Object.keys(e))))),has:({objects:e},t)=>t!=Symbol.unscopables&&e.some((e=>Object.prototype.hasOwnProperty.call(e,t)||Reflect.has(e,t))),get:({objects:e},t,n)=>"toJSON"==t?I:Reflect.get(e.find((e=>Reflect.has(e,t)))||{},t,n),set({objects:e},t,n,r){const i=e.find((e=>Object.prototype.hasOwnProperty.call(e,t)))||e[e.length-1],o=Object.getOwnPropertyDescriptor(i,t);return o?.set&&o?.get?o.set.call(r,n)||!0:Reflect.set(i,t,n)}};function I(){return Reflect.ownKeys(this).reduce(((e,t)=>(e[t]=Reflect.get(this,t),e)),{})}function z(e){let t=(n,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach((([i,{value:o,enumerable:a}])=>{if(!1===a||void 0===o)return;if("object"==typeof o&&null!==o&&o.__v_skip)return;let s=""===r?i:`${r}.${i}`;var l;"object"==typeof o&&null!==o&&o._x_interceptor?n[i]=o.initialize(e,s,i):"object"!=typeof(l=o)||Array.isArray(l)||null===l||o===n||o instanceof Element||t(o,s)}))};return t(e)}function D(e,t=()=>{}){let n={initialValue:void 0,_x_interceptor:!0,initialize(t,n,r){return e(this.initialValue,(()=>function(e,t){return t.split(".").reduce(((e,t)=>e[t]),e)}(t,n)),(e=>B(t,n,e)),n,r)}};return t(n),e=>{if("object"==typeof e&&null!==e&&e._x_interceptor){let t=n.initialize.bind(n);n.initialize=(r,i,o)=>{let a=e.initialize(r,i,o);return n.initialValue=a,t(r,i,o)}}else n.initialValue=e;return n}}function B(e,t,n){if("string"==typeof t&&(t=t.split(".")),1!==t.length){if(0===t.length)throw error;return e[t[0]]||(e[t[0]]={}),B(e[t[0]],t.slice(1),n)}e[t[0]]=n}var F={};function H(e,t){F[e]=t}function W(e,t){let n=function(e){let[t,n]=ue(e),r={interceptor:D,...t};return x(e,n),r}(t);return Object.entries(F).forEach((([r,i])=>{Object.defineProperty(e,`$${r}`,{get:()=>i(t,n),enumerable:!1})})),e}function V(e,t,n,...r){try{return n(...r)}catch(n){U(n,e,t)}}function U(e,t,n=void 0){e=Object.assign(e??{message:"No error message given."},{el:t,expression:n}),console.warn(`Alpine Expression Error: ${e.message}\n\n${n?'Expression: "'+n+'"\n\n':""}`,t),setTimeout((()=>{throw e}),0)}var K=!0;function Z(e){let t=K;K=!1;let n=e();return K=t,n}function J(e,t,n={}){let r;return X(e,t)((e=>r=e),n),r}function X(...e){return Y(...e)}var Y=G;function G(e,t){let n={};W(n,e);let r=[n,...P(e)],i="function"==typeof t?function(e,t){return(n=()=>{},{scope:r={},params:i=[]}={})=>{ee(n,t.apply(R([r,...e]),i))}}(r,t):function(e,t,n){let r=function(e,t){if(Q[e])return Q[e];let n=Object.getPrototypeOf((async function(){})).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e;let i=(()=>{try{let t=new n(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`);return Object.defineProperty(t,"name",{value:`[Alpine] ${e}`}),t}catch(n){return U(n,t,e),Promise.resolve()}})();return Q[e]=i,i}(t,n);return(i=()=>{},{scope:o={},params:a=[]}={})=>{r.result=void 0,r.finished=!1;let s=R([o,...e]);if("function"==typeof r){let e=r(r,s).catch((e=>U(e,n,t)));r.finished?(ee(i,r.result,s,a,n),r.result=void 0):e.then((e=>{ee(i,e,s,a,n)})).catch((e=>U(e,n,t))).finally((()=>r.result=void 0))}}}(r,t,e);return V.bind(null,e,t,i)}var Q={};function ee(e,t,n,r,i){if(K&&"function"==typeof t){let o=t.apply(n,r);o instanceof Promise?o.then((t=>ee(e,t,n,r))).catch((e=>U(e,i,t))):e(o)}else"object"==typeof t&&t instanceof Promise?t.then((t=>e(t))):e(t)}var te="x-";function ne(e=""){return te+e}var re={};function ie(e,t){return re[e]=t,{before(t){if(!re[t])return void console.warn(String.raw`Cannot find directive \`${t}\`. \`${e}\` will use the default order of execution`);const n=ve.indexOf(t);ve.splice(n>=0?n:ve.indexOf("DEFAULT"),0,e)}}}function oe(e,t,n){if(t=Array.from(t),e._x_virtualDirectives){let n=Object.entries(e._x_virtualDirectives).map((([e,t])=>({name:e,value:t}))),r=ae(n);n=n.map((e=>r.find((t=>t.name===e.name))?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e)),t=t.concat(n)}let r={},i=t.map(de(((e,t)=>r[e]=t))).filter(he).map(function(e,t){return({name:n,value:r})=>{let i=n.match(me()),o=n.match(/:([a-zA-Z0-9\-_:]+)/),a=n.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],s=t||e[n]||n;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:a.map((e=>e.replace(".",""))),expression:r,original:s}}}(r,n)).sort(ge);return i.map((t=>function(e,t){let n=re[t.type]||(()=>{}),[r,i]=ue(e);w(e,t.original,i);let o=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,r),n=n.bind(n,e,t,r),se?le.get(ce).push(n):n())};return o.runCleanups=i,o}(e,t)))}function ae(e){return Array.from(e).map(de()).filter((e=>!he(e)))}var se=!1,le=new Map,ce=Symbol();function ue(e){let t=[],[n,r]=function(e){let t=()=>{};return[n=>{let r=o(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach((e=>e()))}),e._x_effects.add(r),t=()=>{void 0!==r&&(e._x_effects.delete(r),a(r))},r},()=>{t()}]}(e);return t.push(r),[{Alpine:yt,effect:n,cleanup:e=>t.push(e),evaluateLater:X.bind(X,e),evaluate:J.bind(J,e)},()=>t.forEach((e=>e()))]}var fe=(e,t)=>({name:n,value:r})=>(n.startsWith(e)&&(n=n.replace(e,t)),{name:n,value:r});function de(e=()=>{}){return({name:t,value:n})=>{let{name:r,value:i}=pe.reduce(((e,t)=>t(e)),{name:t,value:n});return r!==t&&e(r,t),{name:r,value:i}}}var pe=[];function _e(e){pe.push(e)}function he({name:e}){return me().test(e)}var me=()=>new RegExp(`^${te}([^:^.]+)\\b`),ye="DEFAULT",ve=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",ye,"teleport"];function ge(e,t){let n=-1===ve.indexOf(e.type)?ye:e.type,r=-1===ve.indexOf(t.type)?ye:t.type;return ve.indexOf(n)-ve.indexOf(r)}function xe(e,t,n={}){e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0,cancelable:!0}))}function be(e,t){if("function"==typeof ShadowRoot&&e instanceof ShadowRoot)return void Array.from(e.children).forEach((e=>be(e,t)));let n=!1;if(t(e,(()=>n=!0)),n)return;let r=e.firstElementChild;for(;r;)be(r,t),r=r.nextElementSibling}function we(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var Ee=!1,Se=[],Ae=[];function Oe(){return Se.map((e=>e()))}function ke(){return Se.concat(Ae).map((e=>e()))}function je(e){Se.push(e)}function Ce(e){Ae.push(e)}function Te(e,t=!1){return $e(e,(e=>{if((t?ke():Oe()).some((t=>e.matches(t))))return!0}))}function $e(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),e.parentElement)return $e(e.parentElement,t)}}var Le=[];function Ne(e,t=be,n=()=>{}){!function(){se=!0;let r=Symbol();ce=r,le.set(r,[]);let i=()=>{for(;le.get(r).length;)le.get(r).shift()();le.delete(r)};t(e,((e,t)=>{n(e,t),Le.forEach((n=>n(e,t))),oe(e,e.attributes).forEach((e=>e())),e._x_ignore&&t()})),se=!1,i()}()}function Me(e,t=be){t(e,(e=>{!function(e){for(e._x_effects?.forEach(d);e._x_cleanups?.length;)e._x_cleanups.pop()()}(e),E(e)}))}var Pe=[],Re=!1;function qe(e=()=>{}){return queueMicrotask((()=>{Re||setTimeout((()=>{Ie()}))})),new Promise((t=>{Pe.push((()=>{e(),t()}))}))}function Ie(){for(Re=!1;Pe.length;)Pe.shift()()}function ze(e,t){return Array.isArray(t)?De(e,t.join(" ")):"object"==typeof t&&null!==t?function(e,t){let n=e=>e.split(" ").filter(Boolean),r=Object.entries(t).flatMap((([e,t])=>!!t&&n(e))).filter(Boolean),i=Object.entries(t).flatMap((([e,t])=>!t&&n(e))).filter(Boolean),o=[],a=[];return i.forEach((t=>{e.classList.contains(t)&&(e.classList.remove(t),a.push(t))})),r.forEach((t=>{e.classList.contains(t)||(e.classList.add(t),o.push(t))})),()=>{a.forEach((t=>e.classList.add(t))),o.forEach((t=>e.classList.remove(t)))}}(e,t):"function"==typeof t?ze(e,t()):De(e,t)}function De(e,t){return t=!0===t?t="":t||"",n=t.split(" ").filter((t=>!e.classList.contains(t))).filter(Boolean),e.classList.add(...n),()=>{e.classList.remove(...n)};var n}function Be(e,t){return"object"==typeof t&&null!==t?function(e,t){let n={};return Object.entries(t).forEach((([t,r])=>{n[t]=e.style[t],t.startsWith("--")||(t=t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()),e.style.setProperty(t,r)})),setTimeout((()=>{0===e.style.length&&e.removeAttribute("style")})),()=>{Be(e,n)}}(e,t):function(e,t){let n=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",n||"")}}(e,t)}function Fe(e,t=()=>{}){let n=!1;return function(){n?t.apply(this,arguments):(n=!0,e.apply(this,arguments))}}function He(e,t,n={}){e._x_transition||(e._x_transition={enter:{during:n,start:n,end:n},leave:{during:n,start:n,end:n},in(n=()=>{},r=()=>{}){Ve(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end},n,r)},out(n=()=>{},r=()=>{}){Ve(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end},n,r)}})}function We(e){let t=e.parentNode;if(t)return t._x_hidePromise?t:We(t)}function Ve(e,t,{during:n,start:r,end:i}={},o=()=>{},a=()=>{}){if(e._x_transitioning&&e._x_transitioning.cancel(),0===Object.keys(n).length&&0===Object.keys(r).length&&0===Object.keys(i).length)return o(),void a();let s,l,c;!function(e,t){let n,r,i,o=Fe((()=>{C((()=>{n=!0,r||t.before(),i||(t.end(),Ie()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning}))}));e._x_transitioning={beforeCancels:[],beforeCancel(e){this.beforeCancels.push(e)},cancel:Fe((function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();o()})),finish:o},C((()=>{t.start(),t.during()})),Re=!0,requestAnimationFrame((()=>{if(n)return;let o=1e3*Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s","")),a=1e3*Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""));0===o&&(o=1e3*Number(getComputedStyle(e).animationDuration.replace("s",""))),C((()=>{t.before()})),r=!0,requestAnimationFrame((()=>{n||(C((()=>{t.end()})),Ie(),setTimeout(e._x_transitioning.finish,o+a),i=!0)}))}))}(e,{start(){s=t(e,r)},during(){l=t(e,n)},before:o,end(){s(),c=t(e,i)},after:a,cleanup(){l(),c()}})}function Ue(e,t,n){if(-1===e.indexOf(t))return n;const r=e[e.indexOf(t)+1];if(!r)return n;if("scale"===t&&isNaN(r))return n;if("duration"===t||"delay"===t){let e=r.match(/([0-9]+)ms/);if(e)return e[1]}return"origin"===t&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[r,e[e.indexOf(t)+2]].join(" "):r}ie("transition",((e,{value:t,modifiers:n,expression:r},{evaluate:i})=>{"function"==typeof r&&(r=i(r)),!1!==r&&(r&&"boolean"!=typeof r?function(e,t,n){He(e,ze,""),{enter:t=>{e._x_transition.enter.during=t},"enter-start":t=>{e._x_transition.enter.start=t},"enter-end":t=>{e._x_transition.enter.end=t},leave:t=>{e._x_transition.leave.during=t},"leave-start":t=>{e._x_transition.leave.start=t},"leave-end":t=>{e._x_transition.leave.end=t}}[n](t)}(e,r,t):function(e,t,n){He(e,Be);let r=!t.includes("in")&&!t.includes("out")&&!n,i=r||t.includes("in")||["enter"].includes(n),o=r||t.includes("out")||["leave"].includes(n);t.includes("in")&&!r&&(t=t.filter(((e,n)=>nn>t.indexOf("out"))));let a=!t.includes("opacity")&&!t.includes("scale"),s=a||t.includes("opacity")?0:1,l=a||t.includes("scale")?Ue(t,"scale",95)/100:1,c=Ue(t,"delay",0)/1e3,u=Ue(t,"origin","center"),f="opacity, transform",d=Ue(t,"duration",150)/1e3,p=Ue(t,"duration",75)/1e3,_="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:u,transitionDelay:`${c}s`,transitionProperty:f,transitionDuration:`${d}s`,transitionTimingFunction:_},e._x_transition.enter.start={opacity:s,transform:`scale(${l})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"}),o&&(e._x_transition.leave.during={transformOrigin:u,transitionDelay:`${c}s`,transitionProperty:f,transitionDuration:`${p}s`,transitionTimingFunction:_},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:s,transform:`scale(${l})`})}(e,n,t))})),window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,n,r){const i="visible"===document.visibilityState?requestAnimationFrame:setTimeout;let o=()=>i(n);t?e._x_transition&&(e._x_transition.enter||e._x_transition.leave)?e._x_transition.enter&&(Object.entries(e._x_transition.enter.during).length||Object.entries(e._x_transition.enter.start).length||Object.entries(e._x_transition.enter.end).length)?e._x_transition.in(n):o():e._x_transition?e._x_transition.in(n):o():(e._x_hidePromise=e._x_transition?new Promise(((t,n)=>{e._x_transition.out((()=>{}),(()=>t(r))),e._x_transitioning&&e._x_transitioning.beforeCancel((()=>n({isFromCancelledTransition:!0})))})):Promise.resolve(r),queueMicrotask((()=>{let t=We(e);t?(t._x_hideChildren||(t._x_hideChildren=[]),t._x_hideChildren.push(e)):i((()=>{let t=e=>{let n=Promise.all([e._x_hidePromise,...(e._x_hideChildren||[]).map(t)]).then((([e])=>e?.()));return delete e._x_hidePromise,delete e._x_hideChildren,n};t(e).catch((e=>{if(!e.isFromCancelledTransition)throw e}))}))})))};var Ke=!1;function Ze(e,t=()=>{}){return(...n)=>Ke?t(...n):e(...n)}var Je=[];function Xe(e){Je.push(e)}var Ye=!1;function Ge(e){let t=o;h(((e,n)=>{let r=t(e);return a(r),()=>{}})),e(),h(t)}function Qe(e,t,n,r=[]){switch(e._x_bindings||(e._x_bindings=i({})),e._x_bindings[t]=n,t=r.includes("camel")?t.toLowerCase().replace(/-(\w)/g,((e,t)=>t.toUpperCase())):t){case"value":!function(e,t){if(st(e))void 0===e.attributes.value&&(e.value=t),window.fromModel&&(e.checked="boolean"==typeof t?nt(e.value)===t:tt(e.value,t));else if(at(e))Number.isInteger(t)?e.value=t:Array.isArray(t)||"boolean"==typeof t||[null,void 0].includes(t)?Array.isArray(t)?e.checked=t.some((t=>tt(t,e.value))):e.checked=!!t:e.value=String(t);else if("SELECT"===e.tagName)!function(e,t){const n=[].concat(t).map((e=>e+""));Array.from(e.options).forEach((e=>{e.selected=n.includes(e.value)}))}(e,t);else{if(e.value===t)return;e.value=void 0===t?"":t}}(e,n);break;case"style":!function(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles(),e._x_undoAddedStyles=Be(e,t)}(e,n);break;case"class":!function(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses(),e._x_undoAddedClasses=ze(e,t)}(e,n);break;case"selected":case"checked":!function(e,t,n){et(e,t,n),function(e,t,n){e[t]!==n&&(e[t]=n)}(e,t,n)}(e,t,n);break;default:et(e,t,n)}}function et(e,t,n){[null,void 0,!1].includes(n)&&function(e){return!["aria-pressed","aria-checked","aria-expanded","aria-selected"].includes(e)}(t)?e.removeAttribute(t):(it(t)&&(n=t),function(e,t,n){e.getAttribute(t)!=n&&e.setAttribute(t,n)}(e,t,n))}function tt(e,t){return e==t}function nt(e){return!![1,"1","true","on","yes",!0].includes(e)||![0,"0","false","off","no",!1].includes(e)&&(e?Boolean(e):null)}var rt=new Set(["allowfullscreen","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected","shadowrootclonable","shadowrootdelegatesfocus","shadowrootserializable"]);function it(e){return rt.has(e)}function ot(e,t,n){let r=e.getAttribute(t);return null===r?"function"==typeof n?n():n:""===r||(it(t)?!![t,"true"].includes(r):r)}function at(e){return"checkbox"===e.type||"ui-checkbox"===e.localName||"ui-switch"===e.localName}function st(e){return"radio"===e.type||"ui-radio"===e.localName}function lt(e,t){var n;return function(){var r=this,i=arguments;clearTimeout(n),n=setTimeout((function(){n=null,e.apply(r,i)}),t)}}function ct(e,t){let n;return function(){let r=arguments;n||(e.apply(this,r),n=!0,setTimeout((()=>n=!1),t))}}function ut({get:e,set:t},{get:n,set:r}){let i,s,l=!0,c=o((()=>{let o=e(),a=n();if(l)r(ft(o)),l=!1;else{let e=JSON.stringify(o),n=JSON.stringify(a);e!==i?r(ft(o)):e!==n&&t(ft(a))}i=JSON.stringify(e()),s=JSON.stringify(n())}));return()=>{a(c)}}function ft(e){return"object"==typeof e?JSON.parse(JSON.stringify(e)):e}var dt={},pt=!1,_t={};function ht(e,t,n){let r=[];for(;r.length;)r.pop()();let i=Object.entries(t).map((([e,t])=>({name:e,value:t}))),o=ae(i);return i=i.map((e=>o.find((t=>t.name===e.name))?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e)),oe(e,i,n).map((e=>{r.push(e.runCleanups),e()})),()=>{for(;r.length;)r.pop()()}}var mt={},yt={get reactive(){return i},get release(){return a},get effect(){return o},get raw(){return s},version:"3.14.3",flushAndStopDeferringMutations:function(){T=!1,L($),$=[]},dontAutoEvaluateFunctions:Z,disableEffectScheduling:function(e){_=!1,e(),_=!0},startObservingMutations:O,stopObservingMutations:k,setReactivityEngine:function(e){i=e.reactive,a=e.release,o=t=>e.effect(t,{scheduler:e=>{_?function(e){var t;t=e,u.includes(t)||u.push(t),c||l||(l=!0,queueMicrotask(p))}(e):e()}}),s=e.raw},onAttributeRemoved:w,onAttributesAdded:b,closestDataStack:P,skipDuringClone:Ze,onlyDuringClone:function(e){return(...t)=>Ke&&e(...t)},addRootSelector:je,addInitSelector:Ce,interceptClone:Xe,addScopeToNode:M,deferMutations:function(){T=!0},mapAttributes:_e,evaluateLater:X,interceptInit:function(e){Le.push(e)},setEvaluator:function(e){Y=e},mergeProxies:R,extractProp:function(e,t,n,r=!0){if(e._x_bindings&&void 0!==e._x_bindings[t])return e._x_bindings[t];if(e._x_inlineBindings&&void 0!==e._x_inlineBindings[t]){let n=e._x_inlineBindings[t];return n.extract=r,Z((()=>J(e,n.expression)))}return ot(e,t,n)},findClosest:$e,onElRemoved:x,closestRoot:Te,destroyTree:Me,interceptor:D,transition:Ve,setStyles:Be,mutateDom:C,directive:ie,entangle:ut,throttle:ct,debounce:lt,evaluate:J,initTree:Ne,nextTick:qe,prefixed:ne,prefix:function(e){te=e},plugin:function(e){(Array.isArray(e)?e:[e]).forEach((e=>e(yt)))},magic:H,store:function(e,t){if(pt||(dt=i(dt),pt=!0),void 0===t)return dt[e];dt[e]=t,z(dt[e]),"object"==typeof t&&null!==t&&t.hasOwnProperty("init")&&"function"==typeof t.init&&dt[e].init()},start:function(){var e;Ee&&we("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),Ee=!0,document.body||we("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+ +
+
+
+ + +
+ +

Index

+

C

+ + + + +
+ +

D

+ + + +
+ +

E

+ + + + +
+ +

F

+ + + + +
+ +

G

+ + + + +
+ +

I

+ + + + +
+ +

J

+ + + + +
+ +

M

+ + + + +
+ +

N

+ + + +
+ +

O

+ + + + +
+ +

Q

+ + + +
+ +

R

+ + + + +
+ +

S

+ + + + +
+ +

T

+ + + + +
+ +

W

+ + + +
+ + +
+ +
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..8730648 --- /dev/null +++ b/index.html @@ -0,0 +1,412 @@ + + + + + + + + + +Integrated Energy System Optimization | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+
+
+

Integrated Energy System Optimization

+ + + + + + + + + +
+

IESopt – an Integrated Energy System Optimization framework.

+

IESopt is developed and maintained at the Center for Energy at AIT Austrian Institute of Technology GmbH. The framework isdesigned to support the optimization of energy systems that are characterized by a high degree of integration between different energy carriers and sectors. It focuses on offering a modular and adaptable tool for modelers, that does not compromise on performance, while still being user-friendly. This is enabled by reducing energy system assets to abstract building blocks, that are supported by specialized implementation, and can be combined into complex systems without the need of a detailed understanding of mathematical modeling or proficiency in any coding-language.

+
+
+

Caution

+

The documentation is currently being put together based on cleaned parts of the internal docs. Until this is finished, this documentation may contains some placeholders.

+
+
+

Overview

+

This overview of iesopt’s documentation [1] will help you know where to find what you are looking for.

+
+

Getting started

+
    +
  1. The Installation section explains how to quickly install and set up iesopt.

  2. +
  3. If you are new, you can then work through A first model, which will guide you through all the basics you need +to now.

  4. +
+
+
+

Using this documentation

+

For anything beyond {Getting started}Getting started, the following provides a high-level overview of the remaining +documentation that can be helpful when creating your own models:

+
    +
  1. Tutorials will help you learn how to apply iesopt’s various main functionalities, to solve energy +system optimization models. Start here if you are new and have completed the A first model initial tutorial.

  2. +
  3. User guides provide various concise how-to guides, that help you accomplish a certain task, correctly +and safely. Consult these to remind yourself how to do X.

  4. +
  5. Reference contains technical reference for IESopt.jl core components, the YAML syntax, APIs, and more +internal details. It assumes that you already have a basic understanding of the core concepts of iesopt.

  6. +
  7. Developer documentation can be consulted for tips on how to improve iesopt, its +documentation, or other useful information related to developing iesopt. If you are only using iesopt to develop +your own tools / projects, this will not be necessary to check at all.

  8. +
+

If you are up- or downgrading iesopt, head over to the Releases Page +that provides you with information on what changed between versions.

+
+
+

Different projects

+

The following projects / repositories are part of “IESopt”:

+
    +
  • IESopt.jl, the Julia-based core model powering all of IESopt’s capabilities.

  • +
  • iesopt, the Python interface (which you are currently viewing), which +enables a fast and simple application of IESopt.jl, without the need to know any Julia, or how to set it up. It further +provides different quality-of-life features, and embeds the model into a more conventional object-oriented style, that +you may be more used to - compared to the way Julia works.

  • +
  • IESoptLib.jl, the library of various assets related to IESopt. You can +find examples, as well as pre-defined templates and addons here. The library is automatically loaded for you.

  • +
+
+
+
+

Installation

+
+

Setting up an environment

+
+

Note

+

Skip this step if you want to install iesopt into an existing environment and directly continue directly continue with +Installing iesopt.

+
+

This assumes that you have a working conda executable installed on your system, e.g., after installing Miniconda. +If you added the binary paths to your PATH environment variable, you should be able to execute the following steps in +every terminal (e.g., within VSCode), otherwise make sure to use a proper shell - most +likely you Anaconda Prompt.

+

First we create a new environment using (make sure to replace yourenvname by a fitting name)

+
conda create -n yourenvname python=3.12 -y
+conda activate yourenvname
+
+
+

Your terminal should now print the name of your environment in each new line, similar to

+
(yourenvname) user@PCNAME:~/your/current/path$
+
+
+

Next, we install Poetry by executing

+
pip install poetry
+
+
+

and use it to create a new basic environment by executing

+
poetry init -n
+
+
+

Now you should see a new pyproject.toml file inside your folder, and are ready to [install iesopt](Installing iesopt).

+
+

Learning more about managing dependencies with Poetry

+

Checkout the great tutorial “Dependency Management With Python Poetry” +to learn more about all of this, or consult the Basic usage section of +the Poetry documentation.

+
+
+
+

Installing iesopt

+

This assumes that you have a working environment, that has Poetry installed. It should however work similarly using +conda install or pip install instead.

+

You can install iesopt by executing

+
poetry add iesopt
+
+
+

And that’s it… you are done!

+
+

Precompiling

+

Julia, compared to Python as you are probably used to it, compiles code [2] just before it executes it. This, +coupled with the fact that we - until now - did not fully initialize our Julia environment, may lead to your first time +using iesopt taking a long (!) time.

+

To “prevent” this, we can do a lot of the heavy lifting right here and now, by starting Python. You can do this by just +executing python in the terminal that you used to set up everything, like so

+
(yourenvname) user@PCNAME:~/your/current/path$ python
+
+
+

which should result in an info message similar to this one:

+
Python 3.11.9 (main, Apr 19 2024, 16:48:06) [GCC 11.2.0] on linux
+Type "help", "copyright", "credits" or "license" for more information.
+>>>
+
+
+

Then just run

+
import iesopt
+
+
+

You will see some messages like INFO:iesopt:Setting up Julia ..., and most likely a lot of other output related to the +instantiation of a Julia environment. This may take a few minutes, but should end with lines that print

+
INFO:iesopt:Julia setup successful
+INFO:iesopt:Importing Julia module `IESoptLib`
+INFO:iesopt:Importing Julia module `IESopt`
+INFO:iesopt:Importing Julia module `JuMP`
+
+
+

and are followed by a welcome message that documents the current version of IESopt that you are using. After that, you +are ready to start using iesopt.

+
+

Reducing overhead

+

The next time that you launch iesopt by using import iesopt inside your current environment will be considerably +faster. Nonetheless, every new launch comes with certain compilation-related overheads. The best way to prevent this, is +making use of an interactive / REPL-based style of development.

+
+ +
+
+
+
+

Citing IESopt

+

If you find IESopt useful in your work, and are intend to publish or document your modeling, we kindly request that you +include the following citation:

+
    +
  • Style: APA7

    +
    +

    Strömer, S., Schwabeneder, D., & contributors. (2021-2024). IESopt: Integrated Energy System Optimization [Software]. AIT Austrian Institute of Technology GmbH. https://github.com/ait-energy/IESopt

    +
    +
  • +
  • Style: IEEE

    +
    +

    [1] S. Strömer, D. Schwabeneder, and contributors, “IESopt: Integrated Energy System Optimization,” AIT Austrian Institute of Technology GmbH, 2021-2024. [Online]. Available: https://github.com/ait-energy/IESopt

    +
    +
  • +
  • BibTeX:

    +
    @misc{iesopt,
    +    author = {Strömer, Stefan and Schwabeneder, Daniel and contributors},
    +    title = {{IES}opt: Integrated Energy System Optimization},
    +    organization = {AIT Austrian Institute of Technology GmbH},
    +    url = {https://github.com/ait-energy/iesopt},
    +    type = {Software},
    +    year = {2021-2024},
    +}
    +
    +
    +
  • +
+
+
+
+ +
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/notebooks/custom_results_1.html b/notebooks/custom_results_1.html new file mode 100644 index 0000000..7b4c716 --- /dev/null +++ b/notebooks/custom_results_1.html @@ -0,0 +1,882 @@ + + + + + + + + + +Extracting results: Part I | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Extracting results: Part I

+

This tutorial showcases how results can be extracted, including how user defined templates are able to create new result +calculations.

+
+
+
import iesopt
+
+
+
+
+
+
+
+
+
config_file = iesopt.make_example(
+    "48_custom_results", dst_dir="ex_custom_results", dst_name="config"
+)
+
+
+
+
+
INFO:iesopt:Data folder for examples already exists; NOT copying ANY contents
+INFO:iesopt:Creating example ('48_custom_results') at: 'ex_custom_results/config.iesopt.yaml'
+INFO:iesopt:Set write permissions for example ('ex_custom_results/config.iesopt.yaml'), and data folder ('ex_custom_results/files')
+
+
+
+
+
+
+
model = iesopt.run(config_file, verbosity=False)
+
+assert model.status == iesopt.ModelStatus.OPTIMAL
+
+
+
+
+
+

Accessing model results: Objectives

+

The objective of your model - after a successful solve - can be extracted using:

+
+
+
model.objective_value
+
+
+
+
+
981.1745152354571
+
+
+
+
+

It may however be the case, that you have registered multiple objective functions (which can be used for multi-objective +algorithms, or even be useful just for analysis purposes). You can get their value by their name. The default objective +is always called total_cost and is the only one guaranteed to always exist. We can check which objectives are registered:

+
+
+
model.results.objectives.keys()
+
+
+
+
+
dict_keys(['total_cost'])
+
+
+
+
+

And get the value of total_cost (which should match the one obtained from model.objective_value for this example):

+
+
+
model.results.objectives["total_cost"]
+
+
+
+
+
981.1745152354571
+
+
+
+
+
+
+

Accessing model results: Variables

+

Three different ways to access the results of our custom storage component storage:

+
+

Direct access

+
+
+
model.results.components["storage"].res.setpoint
+
+
+
+
+
array([-3.6565097 ,  3.47368421, -0.99722992,  0.94736842,  0.        ,
+        0.        ,  0.        , -5.6       ,  0.        , -0.1       ,
+       -1.50221607,  3.47368421,  2.63157895,  0.31578947,  0.42105263,
+       -3.46149584,  0.        ,  0.84210526, -2.4       , -4.        ,
+        3.57894737,  3.47368421,  0.94736842,  0.52631579])
+
+
+
+
+
+

Accessing dual information:

+
model.results.components["grid"].con.nodalbalance__dual
+
+
+
+
+
+

Access using get(...)

+
+
+
model.results.get("component", "storage", "res", "setpoint")
+
+
+
+
+
array([-3.6565097 ,  3.47368421, -0.99722992,  0.94736842,  0.        ,
+        0.        ,  0.        , -5.6       ,  0.        , -0.1       ,
+       -1.50221607,  3.47368421,  2.63157895,  0.31578947,  0.42105263,
+       -3.46149584,  0.        ,  0.84210526, -2.4       , -4.        ,
+        3.57894737,  3.47368421,  0.94736842,  0.52631579])
+
+
+
+
+
+

Accessing dual information:

+
model.results.get("component", "grid", "con", "nodalbalance", mode="dual")
+
+
+
+
+
+

Collective results

+
+

Using to_dict(...)

+
+
+
results = model.results.to_dict()
+
+results[("storage", "res", "setpoint")]
+
+
+
+
+
array([-3.6565097 ,  3.47368421, -0.99722992,  0.94736842,  0.        ,
+        0.        ,  0.        , -5.6       ,  0.        , -0.1       ,
+       -1.50221607,  3.47368421,  2.63157895,  0.31578947,  0.42105263,
+       -3.46149584,  0.        ,  0.84210526, -2.4       , -4.        ,
+        3.57894737,  3.47368421,  0.94736842,  0.52631579])
+
+
+
+
+
+

Accessing dual information:

+
results[("grid", "con", "nodalbalance__dual")]
+
+
+
+
+

Note: You can filter the results returned by to_dict(...) in exactly the same way as when using to_pandas(...) ( +see below). However, this is uncommon to be useful, since you most likely want to work with tabular data anyways when +using the filter function, which is why we skip it here.

+
+
+
+

Using to_pandas(...)

+
+
+
df = model.results.to_pandas()
+
+df.loc[
+    (
+        (df["component"] == "storage")
+        & (df["fieldtype"] == "res")
+        & (df["field"] == "setpoint")
+        & (df["mode"] == "primal")
+    ),
+    "value",
+].values
+
+
+
+
+
array([-3.6565097 ,  3.47368421, -0.99722992,  0.94736842,  0.        ,
+        0.        ,  0.        , -5.6       ,  0.        , -0.1       ,
+       -1.50221607,  3.47368421,  2.63157895,  0.31578947,  0.42105263,
+       -3.46149584,  0.        ,  0.84210526, -2.4       , -4.        ,
+        3.57894737,  3.47368421,  0.94736842,  0.52631579])
+
+
+
+
+
+
+
series = model.results.to_pandas(
+    lambda c, t, f: c == "storage" and t == "res" and f == "setpoint"
+)
+series.head()
+
+
+
+
+
t1   -3.656510
+t2    3.473684
+t3   -0.997230
+t4    0.947368
+t5    0.000000
+Name: (storage, res, setpoint), dtype: float64
+
+
+
+
+

We could actually only filter for the component (c == "storage"), since this is the only result that it creates.

+
+
+
series = model.results.to_pandas(lambda c, t, f: c == "storage")
+series.head()
+
+
+
+
+
t1   -3.656510
+t2    3.473684
+t3   -0.997230
+t4    0.947368
+t5    0.000000
+Name: (storage, res, setpoint), dtype: float64
+
+
+
+
+

This may however be dangerous, since a similar call

+
model.results.to_pandas(lambda c,t,f: c == "grid")
+
+
+

would then suddenly return a pd.DataFrame, since it contains two different results (try it out!) linked to the +component grid.

+
+

Accessing dual information (part I):

+
model.results.to_pandas(lambda c,t,f: c == "grid" and t == "con")
+
+
+

This works, since the model only contains a single result linked to constraints of the component grid. However, this +may again be dangerous, which is why you could instead make use of something like

+
df = model.results.to_pandas()
+df[df["mode"] == "dual"]
+
+
+

Note that this extracts ALL dual results, not only those for the component used above, but again to_pandas(...) is +mostly there to extract more than one result at the same time (we cover “Which way should I use below?”).

+
+
+

You may now wonder - since it all looks the same - what to_pandas(...) could be useful for. It’s main usage is +extracting more than one result in one call:

+
+
+
df = model.results.to_pandas(field_types="exp", orientation="wide")
+df.head(5)
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storage.storagedemandgridgenerator
expexpexpexp
injectionvalueinjectionout_electricity
t13.4736844.04.440892e-167.65651
t2-3.4736844.0-1.110223e-160.70000
t30.9473684.02.220446e-164.99723
t4-0.9473684.00.000000e+003.10000
t50.0000004.00.000000e+004.00000
+
+
+
+
+
+

Which result extraction should I use?

+

As a basic guide you can use the following logic to decide how to extract results. Are you:

+
    +
  1. Looking for a single result of a component? Extract it similar to model.results.components["storage"].res.setpoint.

  2. +
  3. Looking for multiple results of a component (e.g., all objective terms created by a single Unit), or similar results of multiple components (e.g., electricity generation of all generators)? Make use of to_pandas(...), applying a specific filter, and either using orientation = "long" (the default), or orientation = "wide".

  4. +
+
+

Advanced usage: Looking for a single result of a component at a time, but doing it repeatedly for a single run (= extracting a single result from component A, then one from component B, and so on)?

+

Then use the to_dict(...) function and then extract your results similar to model.to_dict()[("storage", "res", "setpoint")]. Compared to (1.) this has the advantage of caching results during the first call to to_dict(...), and being able to only extract specific results if correctly filtered. **Pay attention to why, when, and how you use this, since improper usage may be way slower than directly accessing your results as explained above.

+
+
+
+

Loading results from file

+

The example that we have used until now, does not write any results to a file. To load some, we therefore need to enable +this and then re-solve the model.

+

For that, edit the top-level config file, and change

+
config:
+  # ...
+  results:
+    enabled: true
+    memory_only: false
+
+
+

to

+
config:
+  # ...
+  results:
+    enabled: true
+    memory_only: true       # <-- change here!
+
+
+

Now run the model once more to create the result output file

+
+
+
model = iesopt.run("ex_custom_results/config.iesopt.yaml", verbosity=False)
+
+
+
+
+

This will create an IESopt result file SomeScenario.iesopt.result.jld2 inside the +ex_custom_results/out/CustomResults/ folder, which contains all results. This can be used to analyse results at a +later time. To prevent losing information it tries to extract all results - which may be time intensive, but +ensures that you do not forget to extract something, to only realise later that you miss it.

+

We can now load this file using

+
+
+
results = iesopt.Results(
+    file="ex_custom_results/out/CustomResults/SomeScenario.iesopt.result.jld2"
+)
+
+
+
+
+

Now, you can use exactly the same code that we have already walked through, c.f. Accessing model results: Variables, just by +replacing every access to model.results by results.

+
+

File results: Direct access

+
+
+
# instead of
+#   `model.results.components["storage"].res.setpoint`
+# we now use:
+
+results.components["storage"].res.setpoint
+
+
+
+
+
array([-3.6565097 ,  3.47368421, -0.99722992,  0.94736842,  0.        ,
+        0.        ,  0.        , -5.6       ,  0.        , -0.1       ,
+       -1.50221607,  3.47368421,  2.63157895,  0.31578947,  0.42105263,
+       -3.46149584,  0.        ,  0.84210526, -2.4       , -4.        ,
+        3.57894737,  3.47368421,  0.94736842,  0.52631579])
+
+
+
+
+
+
+

File results: Access using get(...)

+
+
+
results.get("component", "storage", "res", "setpoint")
+
+
+
+
+
array([-3.6565097 ,  3.47368421, -0.99722992,  0.94736842,  0.        ,
+        0.        ,  0.        , -5.6       ,  0.        , -0.1       ,
+       -1.50221607,  3.47368421,  2.63157895,  0.31578947,  0.42105263,
+       -3.46149584,  0.        ,  0.84210526, -2.4       , -4.        ,
+        3.57894737,  3.47368421,  0.94736842,  0.52631579])
+
+
+
+
+
+
+

File results: Collective results

+
+

File results: Using to_dict(...)

+
+
+
result_dict = results.to_dict()
+result_dict[("storage", "res", "setpoint")]
+
+
+
+
+
array([-3.6565097 ,  3.47368421, -0.99722992,  0.94736842,  0.        ,
+        0.        ,  0.        , -5.6       ,  0.        , -0.1       ,
+       -1.50221607,  3.47368421,  2.63157895,  0.31578947,  0.42105263,
+       -3.46149584,  0.        ,  0.84210526, -2.4       , -4.        ,
+        3.57894737,  3.47368421,  0.94736842,  0.52631579])
+
+
+
+
+
+
+

File results: Using to_pandas(...)

+
+
+
df = results.to_pandas(field_types="exp", orientation="wide")
+df.head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storage.storagedemandgridgenerator
expexpexpexp
injectionvalueinjectionout_electricity
t13.4736844.04.440892e-167.65651
t2-3.4736844.0-1.110223e-160.70000
t30.9473684.02.220446e-164.99723
t4-0.9473684.00.000000e+003.10000
t50.0000004.00.000000e+004.00000
+
+
+
+
+
+
+

Calling into Julia

+

You should probably never need the following, but you can also manually access the results data Struct inside the +Julia model to extract some results. For an optimized model (not for results loaded from a file!), this could be done +using

+
+
+
my_result = (
+    model.core.ext[iesopt.Symbol("iesopt")].results.components["storage"].res.setpoint
+)
+
+
+
+
+

Observe that

+
+
+
type(my_result)
+
+
+
+
+
juliacall.VectorValue
+
+
+
+
+

which shows that the other modes of result extraction take care of a proper Julia-to-Python conversion for you already. +Further, you can then

+
+
+
my_result[:5]
+
+
+
+
+
5-element view(::Vector{Float64}, 1:1:5) with eltype Float64:
+ -3.656509695290859
+  3.473684210526316
+ -0.997229916897507
+  0.9473684210526315
+  0.0
+
+
+
+
+

which, as you see, returns an actual view into the Vector{Float64}, indexed using the Julia range 1:1:5 (given by +the Python range :5). But

+
+
+
my_result[0:5]
+
+
+
+
+
5-element view(::Vector{Float64}, 1:1:5) with eltype Float64:
+ -3.656509695290859
+  3.473684210526316
+ -0.997229916897507
+  0.9473684210526315
+  0.0
+
+
+
+
+

makes it clear, that the wrapper we use automatically translates between 0-based (Python) and 1-based (Julia) indexing, +which may become confusing and error-prone when thinking about a Julia Vector but accessing the first entry using

+
+
+
my_result[0]
+
+
+
+
+
-3.656509695290859
+
+
+
+
+
+

Accessing dual information (part I):

+
model.core.ext[iesopt.Symbol("iesopt")].results.components["grid"].con.nodalbalance__dual
+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/notebooks/custom_results_2.html b/notebooks/custom_results_2.html new file mode 100644 index 0000000..fde3eef --- /dev/null +++ b/notebooks/custom_results_2.html @@ -0,0 +1,848 @@ + + + + + + + + + +Extracting results: Part II | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Extracting results: Part II

+

This tutorial showcases more ways to handle and analyse results. Make sure that you’ve read the first part!

+

We make use of the same example, and first just load and run it.

+
+
+
import iesopt
+
+config_file = iesopt.make_example(
+    "48_custom_results", dst_dir="ex_custom_results", dst_name="config"
+)
+model = iesopt.run(config_file, verbosity=False)
+
+
+
+
+
+
+
+

Remember that the most versatile way (for most tasks) is one that you already know: df = model.results.to_pandas(). This will give you all results as a single pandas.DataFrame that you can then filter, resample, analyse, etc. in any way you wish, with all functions that you are used to from pandas.

+
+
+

What should I look at?

+

If you are unsure which results are even available, you can make use of query_available_results(...) to find out:

+
+
+
model.results.query_available_results("storage.storage", mode="primal")
+
+
+
+
+
[('var', 'state'), ('exp', 'injection')]
+
+
+
+
+

This shows you that two results exist for the component storage.storage: var.state (the level of the state of this Node) and exp.injection (the expression holding the injection into the Node).

+
+

More results are available when using mode="dual", or mode="both" - try it out!

+
+

If you’d be interested in seeing results for the first, then you could (check part I of this tutorial for different ways to access this) do:

+
+
+
model.results.get("component", "storage.storage", "var", "state", mode="primal")
+
+
+
+
+
array([-0.        ,  4.42105263,  0.94736842,  0.94736842, -0.        ,
+       -0.        , -0.        , -0.        ,  3.19552632,  6.80552632,
+        6.90052632, 10.13052632,  6.65684211,  4.02526316,  3.70947368,
+        3.28842105,  3.28842105,  3.28842105,  2.44631579,  4.72631579,
+        8.52631579,  4.94736842,  1.47368421,  0.52631579])
+
+
+
+
+

Finally, one hint: query_available_results(...) treats its first parameter as regular expression, so you can use any regex you want to look up more than one component at the same time! regex.101 is a good place to test your regular expressions that you wrote using any LLM (they are quite okay at that!).

+
+
+

Looking at (a) specific component(s)

+

If you are now interested in seeing all results for a single component, the DataFrame returned by to_pandas(...) can get overwhelming quickly. That’s what overview(...) can be used for.

+
+

Temporal results

+

Observe the following:

+
+
+
model.results.overview("storage.*ing", temporal=True, mode="both").head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
storage.chargingstorage.discharging
convarconvar
flow_lbflowflow_lbflow
dualdualprimaldualdualprimal
t1-0.0000000.04.65374-1.0263160.0-0.000000
t2-1.0803320.0-0.00000-0.0000000.03.473684
t30.0000000.0-0.00000-1.0263160.0-0.000000
t4-1.0803320.0-0.00000-0.0000000.00.947368
t50.0000000.0-0.00000-1.0263160.0-0.000000
+
+
+

As you can see:

+
    +
  • The results are automatically given in wide format.

  • +
  • The regular expression "storage.*ing" matched all components that (1) start with “storage”, but also (2) end with “ing”. Try changing that to "storage." and see what other components get matched too.

  • +
  • Since we passed mode="both", it returns both primal and dual results. Try passing "primal" or "dual" instead.

  • +
+

Since we set temporal=True, we got results that are available for every Snapshot.

+
+
+

Non-temporal results

+

Let’s see what happens if we instead do:

+
+
+
model.results.overview(".*", temporal=False, mode="both")
+
+
+
+
+
storage.storage  con  last_state_lb  dual        0.000000
+                      last_state_ub  dual       -0.000000
+generator        obj  marginal_cost  primal    981.174515
+dtype: float64
+
+
+
+
+

Since all results are now non-temporal, we get back a pandas.Series instead.

+
+

Can you explain why this now contains the dual results of constraints constructed by storage.storage? The documentation of the core component Node may help… But - in any case, feel free to ask stuff like this (we are very happy to answer this).

+
+

But, lets look at an example that contains more interesting results for something like this. First we pull a different example and solve it

+
+
+
other_config = iesopt.make_example(
+    "08_basic_investment", dst_dir="ex_custom_results", dst_name="config"
+)
+other_model = iesopt.run(other_config, verbosity=False)
+
+
+
+
+
INFO:iesopt:Data folder for examples already exists; NOT copying ANY contents
+INFO:iesopt:Creating example ('08_basic_investment') at: 'ex_custom_results/config.iesopt.yaml'
+INFO:iesopt:Set write permissions for example ('ex_custom_results/config.iesopt.yaml'), and data folder ('ex_custom_results/files')
+
+
+
+
+

… and then we take a look at the primal, non-temporal results:

+
+
+
other_model.results.overview(".*", temporal=False, mode="primal")
+
+
+
+
+
build_pipeline  var  value          primal      0.750000
+                obj  value          primal    750.000000
+build_gas       var  value          primal      0.607143
+                obj  value          primal    303.571429
+plant_gas       obj  marginal_cost  primal    917.000000
+build_storage   var  value          primal      0.450714
+                obj  value          primal     45.071429
+dtype: float64
+
+
+
+
+

This shows us the resulting values of all investment decisions in the model (e.g., (build_pipeline, var, value)), there associated costs (e.g., (build_pipeline, obj, value)), as well as the objective contribution of the marginal costs induced by operating plant_gas.

+

To only see investment decisions, you could either take this series and further filter it, for example by doing

+
sr = other_model.results.overview(".*", temporal=False, mode="primal")
+
+sr[sr.index.get_level_values(1) == "var"]
+
+
+

which (un-)fortunately also hides the obj entries of the investment decisions.

+

Or you could stick to an “intelligent” naming convention of your components (for example like we did in the example, naming all investment decisions build_***) and make use of the regular expression support of overview(...) by instead doing:

+
+
+
other_model.results.overview("^build_.*$", temporal=False, mode="primal")
+
+
+
+
+
build_pipeline  var  value  primal      0.750000
+                obj  value  primal    750.000000
+build_gas       var  value  primal      0.607143
+                obj  value  primal    303.571429
+build_storage   var  value  primal      0.450714
+                obj  value  primal     45.071429
+dtype: float64
+
+
+
+
+
+

Check out regex101.com/r/GzgzG2/1, and read through the “Explanation” section, to understand what ^build_.*$ actually achieves. Note: Using the intuitive way, other_model.results.overview("build_", ...), would have worked (here) as well. The difference is minimal and subtle, but …

+
+
+
+

Filtering components

+

If you are not familiar with regular expressions, don’t worry. The most commonly used “filters” work as expected. Let’s switch back to temporal results and stick with the “new” example that we have just used.

+
+

Selecting a specific component

+
+
+
other_model.results.overview("pipeline", temporal=True, mode="primal").head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
pipeline
var
flow
primal
t1-0.0
t2-0.0
t3-0.0
t4-0.0
t5-0.0
+
+
+
+
+

Selecting all components containing “plant”

+
+
+
other_model.results.overview("plant", temporal=True, mode="primal").head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
plant_gasplant_solar
expvarexpvar
out_electricityconversionout_electricityconversion
primalprimalprimalprimal
t10.0000000.0000000.00-0.00
t20.0000000.0000000.00-0.00
t30.0342860.0342860.020.02
t40.0000000.0000000.070.07
t50.0000000.0000000.160.16
+
+
+
+
+

Selecting specific components

+

Using |, you can select multiple specific components.

+
+

Note: You can use that multiple times. Try out passing "h2_south|h2_north|demand"! Can you explain why h2_south and h2_north return different types of results? If not - go ahead, ask us!

+
+
+
+
other_model.results.overview("h2_south|elec", temporal=True, mode="primal").head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
elecelectrolysish2_south
expexpvarexp
injectionin_electricityout_h2conversioninjection
primalprimalprimalprimalprimal
t10.00.0000000.000000-0.0000000.0
t20.00.0000000.000000-0.0000000.0
t30.00.0542860.0271430.0271430.0
t40.00.0700000.0350000.0350000.0
t50.00.1600000.0800000.0800000.0
+
+
+

But wait … we did not want to get results for electrolysis. That’s the disadvantage of being able to search for components containing plant, as shown before: Since electrolysis contains elec it matches this too.

+

Let’s fix this. Remember that we used ^build_.*$ before, without it being clear what this achieves? Let’s see …

+
+
+
other_model.results.overview("h2_south|^elec$", temporal=True, mode="primal").head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
elech2_south
expexp
injectioninjection
primalprimal
t10.00.0
t20.00.0
t30.00.0
t40.00.0
t50.00.0
+
+
+

It works!

+

But why … ?

+

As regex101.com/r/GzgzG2/1 explains:

+
    +
  • ^ asserts position at start of a line

  • +
  • $ asserts position at the end of a line

  • +
+

That means, instead of looking for any component that contains elec, we are looking for one that does not contain ANY characters before elec and also NONE after elec. In other words: It matches exactly elec.

+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/notebooks/first_model.html b/notebooks/first_model.html new file mode 100644 index 0000000..24ed585 --- /dev/null +++ b/notebooks/first_model.html @@ -0,0 +1,455 @@ + + + + + + + + + +A first model | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

A first model

+

After you have successfully installed IESopt, you can start to build your first model. In this tutorial, we will show you how to create a simple model, solve it, and extract some basic results.

+

Let’s start by creating a Python file, e.g. main.py, to hold the necessary code, and add the following lines:

+
import iesopt
+
+# Load and solve a model.
+model = iesopt.run("my_first_model.iesopt.yaml")
+
+print("Objective value:", model.objective_value)
+
+
+

Next we’ll describe a simple model that we would like to solve and set various parameters. Create an empty file my_first_model.iesopt.yaml (later on multiple files can be combined to describe more complex models), that we can now use to actually describe the model.

+
+

Model configuration

+

The first part of each *.iesopt.yaml file describes general configuration parameters. Add the following lines:

+
config:
+  optimization:
+    problem_type: LP
+    snapshots:
+      count: 24
+
+
+

This tells IESopt to start building a model, while expecting all formulations to be representable as LP (so, adding binary +variables will cause an error). Further we specify how many time steps (called Snapshot) we’d like to use, here telling IESopt that we are looking to optimize a full day (by the default a Snapshot’s duration is one hour).

+
+

Energy carriers

+

Since IESopt is a general purpose energy system model, it does not restrict you to a set of predefined types of energy, but +rather expects you to first define those. For our first model, we’ll only care about electricity and gas, so we add +the following lines:

+
carriers:
+  electricity: {}
+  gas: {}
+
+
+

The {} represents an empty dictionary. Additional parameters related to the carrier could later be specified there.

+
+
+

Model components

+

Now that all general settings of the model are in place, we can actually start describing the model’s structure:

+
    +
  • A photovoltaic (a Profile) system is feeding in electricity (based on some external availability factor) into a local electricity grid (a Node called elec_grid)

  • +
  • A simple storage is connected (via a Connection) to this grid, able to shift energy between time steps

  • +
  • An endogenous demand (electricity) must be met at every time step

  • +
  • Any uncovered demand (by PV or storage) can be satisfied using a gasturbine (a Unit), that draws gas from a gas_grid, that +needs to buy all used gas from a gas_market

  • +
+

We now describe these seven components, starting with the electricity grid Node. Add the following lines to +my_first_model.iesopt.yaml - everything after a # is considered a comment by IESopt:

+
components:
+  elec_grid:                # the unique name of this component
+    type: Node              # the type of this component
+    carrier: electricity    # this (Node-specific) parameter fixes the carrier to be electricity
+
+
+
+

Head over to this section of the docs to read up on the different component types that are available.

+
+

We add the other two Nodes, making sure, that the storage is stateful (models a “state of charge”), and that the +gas_grid has the proper carrier. Make sure that you do get the correct indent, since all of the following lines still +belong to the overall components: definition:

+
  gas_grid:                 # the unique name of this component
+    type: Node              # the type of this component
+    carrier: gas            # this (Node-specific) parameter fixes the carrier to be electricity
+
+  storage:
+    type: Node
+    carrier: electricity
+    has_state: true         # this allows this Node to have an "internal state"
+    state_lb: 0             # the state can not drop below 0
+    state_ub: 50            # a maximum of 50 electricity can be stored
+
+
+

Two important things can be seen with the storage:

+
    +
  1. Values in IESopt do not carry an explicit unit (at the moment, this will be possible in the future). This means, that if +we consider electricity to be in kW/kWh in this model, we need to make sure that all settings are adjusted to match that.

  2. +
  3. We are implicitly using a default setting of a parameter that we did not specify: state_cyclic (check it out in the docs!) is set to eq per default, forcing the model to always end the optimization with as much “charge” in the storage as it started with in the first time step (however, how much that is, is left to the optimizer to decide).

  4. +
+

Now that we have all Nodes in place, we can insert the only Connection by adding:

+
  conn:
+    type: Connection
+    node_from: elec_grid    # energy flows from HERE
+    node_to: storage        # to THERE
+    capacity: 15            # with a maximum capacity of +-15 units of electricity
+
+
+

Notice that while we did specify the Nodes that are connected by this Connection, no energy +carrier was explicitly set. This is due to the fact that Connections infer the energy carrier and will automatically fail if they +connect two Nodes with a different type of energy. The specified capacity therefore refers to 15 units of electricity +and constructs symmetric bounds on the flow (again, read up in the docs for other options and asymmetric bounds).

+

Next, it’s time to add all three Profiles to the model. A Profile allows for the “creation” or “destruction” of energy: +Normally, all energy needs to move through the model (possibly being transformed), but can not enter/leave the model. This +is where Profiles help, representing for example the cost of buying gas (gas_market) or a fixed demand that needs to +be covered (demand).

+

We now add:

+
  demand:
+    type: Profile           # the type is now "Profile"
+    carrier: electricity
+    value: 5                # this models a fixed demand of 5 units of electricity during every Snapshot
+    node_from: elec_grid    # this tells MFC that this Profile draws energy from "elec_grid"
+
+  # We can also set the "value" of a Profile to a time series, as can be seen:
+  photovoltaic:
+    type: Profile
+    carrier: electricity
+    value: [0,0,0,0,0,1,2,3,4,5,8,12,12,12,8,5,4,3,2,1,0,0,0,0]
+    node_to: elec_grid      # now feeding INTO "elec_grid"
+
+  gas_market:
+    type: Profile
+    carrier: gas
+    mode: create            # this changes the mode from the default ("fixed") to "create"
+    cost: 100               # this specifies the "cost of gas"
+    node_to: gas_grid
+
+
+
+

Note on setting Profile values: While the value of a Profile can be set directly in the *.iesopt.yaml file, most of the time this will just result in a convoluted file. It’s therefore possible to load external data files (in CSV format) and directly link to them using a simple column@filename syntax, that can be seen in other examples.

+
+

While the first two Profiles should be mostly self-explanatory, the gas_market introduces a new concept: While +standard Profiles always consider a fixed value (a time series), some time series may not be exogenous, e.g. how +much gas is bought from the gas market. That’s where the mode: create setting helps by defining a Profile that can +freely choose (as long as the value is >= 0) how much gas is being bought, but associates every unit of gas with a cost +that has to be “paid” (this is therefore automatically inserted into the objective function).

+

The only thing missing from the model description is the Unit (gas_turbine). It takes gas as its only input and +transforms that into electricity. For this we first add the following component:

+
  gasturbine:
+    type: Unit
+    inputs: {gas: gas_grid}
+    outputs: {electricity: elec_grid}
+    conversion: 1 gas -> 0.40 electricity
+    capacity: 100 out:electricity
+
+
+

Let’s look at the Unit-specific settings in detail:

+
    +
  • inputs: {gas: gas_grid}: This tells IESopt that an input accepting gas (the carrier) is connected to gas_grid (= it is +consuming gas from there).

  • +
  • Similarly, outputs: {electricity: elec_grid} tells IESopt where the only output (with carrier electricity) feeds energy to.

  • +
  • The most important part is kept in the so-called “conversion expression” conversion: 1 gas -> 0.40 electricity: This +tells IESopt that the Unit will use 1 unit of gas (e.g. kWh, MJ, …) and convert it into 0.4 units of electricity +(e.g. kWh) at a fixed rate.

  • +
  • Finally, capacity: 100 out:electricity specifies the “capacity limitations” of this Unit: 100 units of electricity can +be produced. This implicitly limits the maximum amount of gas that can be used during a time step to 250 units of gas.

  • +
+

Units come with a lot of additional (and very specific) parameters (e.g. marginal_cost, availability, …) that are +explained in detail in the specific section of the docs.

+
+
+

Final config file

+

Following the above steps you should now have your my_first_model.iesopt.yaml config file setup like this:

+
config:
+  optimization:
+    problem_type: LP
+    snapshots:
+      count: 24
+
+carriers:
+  electricity: {}
+  gas: {}
+
+components:
+  elec_grid:                # the unique name of this component
+    type: Node              # the type of this component
+    carrier: electricity    # this (Node-specific) parameter fixes the carrier to be electricity
+
+  gas_grid:                 # the unique name of this component
+    type: Node              # the type of this component
+    carrier: gas            # this (Node-specific) parameter fixes the carrier to be electricity
+
+  storage:
+    type: Node
+    carrier: electricity
+    has_state: true         # this allows this Node to have an "internal state"
+    state_lb: 0             # the state can not drop below 0
+    state_ub: 50            # a maximum of 50 electricity can be stored    
+   
+  conn:
+    type: Connection
+    node_from: elec_grid    # energy flows from HERE
+    node_to: storage        # to THERE
+    capacity: 15            # with a maximum capacity of +-15 units of electricity
+
+  demand:
+    type: Profile           # the type is now "Profile"
+    carrier: electricity
+    value: 5                # this models a fixed demand of 5 units of electricity during every Snapshot
+    node_from: elec_grid    # this tells MFC that this Profile draws energy from "elec_grid"
+
+  # We can also set the "value" of a Profile to a time series, as can be seen:
+  photovoltaic:
+    type: Profile
+    carrier: electricity
+    value: [0,0,0,0,0,1,2,3,4,5,8,12,12,12,8,5,4,3,2,1,0,0,0,0]
+    node_to: elec_grid      # now feeding INTO "elec_grid"
+
+  gas_market:
+    type: Profile
+    carrier: gas
+    mode: create            # this changes the mode from the default ("fixed") to "create"
+    cost: 100               # this specifies the "cost of gas"
+    node_to: gas_grid
+
+  gasturbine:
+    type: Unit
+    inputs: {gas: gas_grid}
+    outputs: {electricity: elec_grid}
+    conversion: 1 gas -> 0.40 electricity
+    capacity: 100 out:electricity
+
+
+
+
+

Running the optimization

+

Assuming that both my_first_model.iesopt.yaml and main.py are located in the same folder, you can now execute the following +command there (make sure you are in the correct Python environment):

+
python ./main.py
+
+
+

The output should show a total objective value of 9500, resulting from 38 units of electricity missing after +accounting for PV production, amounting to a total need of 95 units of gas, at a price of 100.

+
+

Note on startup time: If you are running this for the first time, you might notice a considerable delay before the +output is shown. This is due to the fact that IESopt is automatically connecting to the internal “core” (which is written in Julia) and updating it. This can +be avoided by running the import iesopt command once and then just iterating on the generate/optimize part, in a “REPL-style” approach. Remember: Interactively executing a line or block of code in VSCode is usually bound to Shift + Enter.

+
+
+
+
+

Extracting model results

+

Now, head over to the extracting results tutorial, to get started with extracting actually results from your model.

+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000..c8b7627 Binary files /dev/null and b/objects.inv differ diff --git a/pages/dev/core.html b/pages/dev/core.html new file mode 100644 index 0000000..33dadd7 --- /dev/null +++ b/pages/dev/core.html @@ -0,0 +1,212 @@ + + + + + + + + + +IESopt.jl | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/dev/general.html b/pages/dev/general.html new file mode 100644 index 0000000..f6845b4 --- /dev/null +++ b/pages/dev/general.html @@ -0,0 +1,367 @@ + + + + + + + + + +Contributing | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Contributing

+

Thank you for your interest in contributing to iesopt! This guide provides instructions on setting up your development environment and contributing code.

+
+

Setting up the development environment

+
    +
  1. Fork the Repository: First, fork the main repository on GitHub to your account.

  2. +
  3. Clone the Repository: Clone the forked repository to your local machine, by running

    +
    git clone https://github.com/your-username/iesopt.git
    +cd iesopt
    +
    +
    +
  4. +
  5. Install uv: +Please follow the official uv install instructions

  6. +
  7. Create the virtual environment: To setup the development venv run

    +
    uv sync
    +
    +
    +
  8. +
+

The virtual environment is now located in the .venv folder (if you ever need to start over, you can just delete this folder)

+
    +
  1. Pre-commit Hooks: To maintain code quality, please install the pre-commit hooks:

    +
    uv run pre-commit install
    +
    +
    +
  2. +
+
+
+

Code formatting and linting

+

After installing pre-commit hooks before, all checks should be automatically run and applied when committing changes.

+ +

ruff (our linter & formatter) should fix most mistakes automatically. codespell (our spelling checker) however, will report on mistakes in the terminal, linking to their occurrence. Inspect them, and fix them accordingly. Consult their README if you (highly unlikely) encounter a word where it wrongfully triggers.

+
+
+

Running tests

+

To run the test suite in your dev environment do

+
uv run pytest
+
+
+

This will print a coverage report, which looks like

+
Name                           Stmts   Miss  Cover
+--------------------------------------------------
+src/iesopt/__init__.py            33      4    88%
+src/iesopt/general.py              0      0   100%
+...
+src/iesopt/util/logging.py         7      0   100%
+src/iesopt/util/util.py            5      0   100%
+--------------------------------------------------
+TOTAL                            417    172    59%
+
+
+

and tells you roughly how good each file is covered by automated tests. Further it creates a coverage.xml file in the project root folder, that you can use. In VSCode, the extension Coverage Gutters allows inspecting test coverage directly in your code editor. If you have it installed and loaded, simply hit Ctrl + Shift + 8 (if you are using default keybinds) to enable watching the coverage.

+
+

Running tox tests

+

To run the test suite against all compatible Python versions do

+
uv run tox
+
+
+
+
+
+

Making changes

+
    +
  1. Create a New Branch: Always create a new branch for your work:

    +
    git checkout -b new-fix-or-feature
    +
    +
    +
  2. +
  3. Make Your Changes: Edit files and make your desired changes.

  4. +
  5. Test Your Changes: Run tests frequently to ensure nothing is broken.

  6. +
  7. Commit Your Changes: Follow conventional commit messages for clarity:

    +
    git commit -m "feat: added new feature X"
    +
    +
    +
  8. +
  9. Push Your Changes: Push the changes to your fork:

    +
    git push origin new-fix-or-feature
    +
    +
    +
  10. +
+
+
+

Improving the documentation

+
+

Building locally

+
uv run sphinx-build --nitpicky --fail-on-warning --fresh-env --keep-going --builder html docs/ docs/_build/html
+
+
+

You can omit the --fresh-env option to not

+
--fresh-env, -E       don't use a saved environment, always read all files
+
+
+

and alternatively use

+
uv run make -C docs clean
+
+
+

to clean the docs before rebuilding them.

+
+

Note on --keep-going

+

A final build of updates to the documentation, that is built using the --fail-on-warning flag, should be done without +--keep-going, to prevent skipping warnings/errors: Each warning (or subsequent error) should be properly resolved!

+
+
+
+
+

Submitting a pull request (PR)

+

Once your changes are pushed to your fork, you can submit a pull request to the main repository:

+
    +
  1. Open a Pull Request: Go to the main repository and click “New Pull Request.”

  2. +
  3. Follow the Template: Provide a clear title and description of your changes.

  4. +
  5. Respond to Feedback: Engage with reviewers and make requested changes promptly.

  6. +
+
+
+

Code Style and Guidelines

+
    +
  • Follow PEP 8: Ensure your code adheres to PEP 8.

  • +
  • Document Your Code: Provide docstrings for all functions, classes, and modules.

  • +
  • Write Tests: Include tests for new features and bug fixes.

  • +
+
+
+

Reporting issues

+

If you find any bugs or have feature requests, please report them via the issue tracker.

+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/dev/updating.html b/pages/dev/updating.html new file mode 100644 index 0000000..acad826 --- /dev/null +++ b/pages/dev/updating.html @@ -0,0 +1,349 @@ + + + + + + + + + +Updating | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Updating

+

This page collects information about updating the project between specific versions.

+
+

1.x.y to 2.0.0

+

The 2.0.0 release follows the breaking change of IESopt.jl going to v2.0.0. This was mainly triggered by a proper rework of how addons work, including a new internal handling for more complex expressions (allowing more flexibility in configuring conversion, or cost settings), as well as better support around templates (e.g. the introduction of Virtuals). A few breaking changes that were planned for quite some time are part of this.

+
+

Changes to the top-level config

+

The structure of the config section in the top-level configuration file changed. Overall it’s highly similar, but:

+
    +
  • A general section was added that properly groups “general” settings, that were previous keyword arguments.

  • +
  • Small adjustments were made to account for that.

  • +
  • The results section changed slightly.

  • +
+

See the respective section(s) in the YAML/Top-level config documentation. The most important changes relate to the following (sub-)sections:

+
    +
  • name

  • +
  • version

  • +
  • high_performance

  • +
  • constraint_safety is now called soft_constraints and part of the optimization section.

  • +
+
+
+

Changes to keyword arguments

+

Previously we allowed passing arbitrary keyword arguments to public functions, like iesopt.Model.generate() or iesopt.run(). This can lead to problems with latency on previously unseen types of arguments, especially now that we support arbitrary dictionaries as model parameters. This now works differently:

+
    +
  • There are no arbitrary keyword arguments anymore.

  • +
  • Model parameters have to be passed as dictionary, using parameters = {foo: 1.0, bar: "austria"}.

  • +
  • There is a new and “powerful” config keyword argument, explained below.

  • +
+
+

config

+

Besides model parameters, specific keyword arguments existed, e.g., verbosity. These are gone and now first-class options in the model’s top-level config (see YAML/Top-level config). With that a way to change them programmatically was in need, which is what the config argument does. Take the following example:

+
+
Example usage of config keyword argument.
+
import iesopt
+
+iesopt.run("my_config.iesopt.yaml", config = {
+    "general.verbosity.core": "error",
+    "optimization.snapshots.count": 168,
+    "files.data_in": "scenario17/data.csv",
+})
+
+
+
+

The passed dictionary modifies the top-level configuration during the parsing step. That means you are able to overwrite / set each configuration option programmatically. The access pattern follows the structure of the config section in the top-level configuration file. For example, the verbosity in the YAML looks like:

+
+
Verbosity example in the top-level YAML.
+
config:
+  general:
+    verbosity:
+      core: info
+
+
+
+

To modify / set this, general.verbosity.core is used. As you can see that opens the possibility to modify, e.g., files that are loaded based on (for example) which scenario we are in by modifying the files section, using files.data_in. Here data_in is the “name” used in the model later on (using some_col@data_in).

+

Further, this means you no longer need to use “model parameters” for stuff that is actually part of the config section, e.g., the number of Snapshots in your model. Instead of coming up with a parameter, e.g., my_snapshot_count, using it as count: <my_snapshot_count>, and then passing it as iesopt.run("...", my_snapshot_count = 168), you can now just modify that as shown above by setting it using the accessor optimization.snapshots.count.

+
+
+

parameters

+

Any dictionary passed to this will be used in the same way as keyword arguments were used before: Parameters given there are replaced in the parameters section (which might be based on an external *.iesopt.param.yaml file).

+
+
+
+

Changes to result extraction

+

We saw increasing interest in actively “filtering” results of various models. This triggered the implementation of a new result backend (initially only JLD2) supported by DuckDB. This is (as of 2.0.0) not enabled as default, since it does not fully support the functionality that the Python wrapper iesopt provides. However, we plan on switching to this backend in the future.

+
+
+

Changes to addons

+

To be written: Link to addon page/docs as soon as they are done.

+
+
+

Changes to examples, and more

+

Everything that was previously part of IESoptLib.jl is now integrated into IESopt.jl. Functionality around this can be accessed using IESopt.Assets.

+
+
+

Other changes

+
+

Tags

+

Component “tags” are back (or accessible again, they were never gone completely). That means you can tag components to later simplify result handling or working with your model. A tag can be added when creating a component:

+
+
Adding a tag.
+
components:
+  my_unit:
+    type: Unit
+    tags: CustomUnit
+
+
+
+

The attribute tags supports single tags (e.g., CustomUnit) or lists of tags (e.g., [CustomUnit] or [CustomUnit, FindMe]).

+
+

Tip

+

Each core component automatically tags its own type, so each Unit will already be tagged with the tag "Unit".

+

Most importantly, that means you are also able to extract all Virtuals (non-existing components related to templates that you use), since each of these also tags its “actual type”.

+
+

Using this is possible with iesopt.Model.get_components():

+
+
Extracting tagged components.
+
import iesopt
+
+model = iesopt.run("tagged_model.iesopt.config")
+
+my_units = model.get_components("CustomUnit")
+
+
+
+

When passing a list of tags, only components having ALL of these tags are returned.

+
+

Tip

+

Most importantly, that (and the fact that components “auto-tag” their type) means you are also able to extract all Virtuals (non-existing components related to templates that you use), since each of these also tags its “actual type”. Consider a template Battery that you use to initialize a battery

+
components:
+  my_bat:
+    type: Battery
+
+
+

All batteries can then be found using model.get_components("Battery"). This can also be really helpful in addons, where (using Julia) you can find, e.g., all CHPs in your model by doing chps = IESopt.get_components(model; tagged="CHP").

+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/julia/assets.html b/pages/manual/julia/assets.html new file mode 100644 index 0000000..ae52458 --- /dev/null +++ b/pages/manual/julia/assets.html @@ -0,0 +1,268 @@ + + + + + + + + + +Assets | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Assets

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

Assets module for IESopt.jl, containing path handling (relocatable) for assets: addons, examples, templates.

+
+
+

API Reference

+
+

Types

+
+
+

Macros

+
+
+

Functions

+
+

get_path

+
get_path(asset_type::String)
+
+
+

Get the path to the asset type folder. Currently supports: “addons”, “examples”, “templates”.

+

Arguments

+
    +
  • asset_type::String: The asset type.

  • +
+

Returns

+
    +
  • RelocatableFolders.Path: The path to the asset folder, already using normpath. Path implements an automatic conversion to String.

  • +
+

Example

+
Assets.get_path("templates")
+
+
+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/julia/index.html b/pages/manual/julia/index.html new file mode 100644 index 0000000..7a9a814 --- /dev/null +++ b/pages/manual/julia/index.html @@ -0,0 +1,955 @@ + + + + + + + + + +Julia | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Julia

+

If you are interested in more details on how to use IESopt.jl directly, please refer to it’s source code.

+

This page contains the docstrings of all things that are part of the public API of IESopt, except:

+
    +
  • All core components. Their full documentation is available in the components section.

  • +
  • The docstrings related to sub-modules of IESopt, e.g., IESopt.Assets. These can be found in the sub-pages of the current section.

  • +
+
+

Notes

+
    +
  • You can use IESopt.IESU as abbreviation for IESopt.Utilities, which allows using, e.g., IESU.annuity(...) after doing using IESopt - which may be better to understand when reading the code instead of IESopt.Utilities.annuity(...) (when only importing), or Utilities.annuity(...) (which does not show any relation to IESopt).

  • +
+
+

Tip

+

The public API of the Python wrapper was, as far as possible, designed to be almost identical to the one of the Julia package, so things should look similar.

+

For example, the following Python code:

+
import iesopt
+
+model = iesopt.generate("config.iesopt.yaml")
+
+my_result = model.get_component("turbine").exp.out_water
+
+
+

can be translated to Julia like this:

+
import IESopt
+
+model = IESopt.generate!("config.iesopt.yaml")
+
+my_result = IESopt.get_component(model, "turbine").exp.out_water
+
+
+
+ +
+
+

API Reference

+
+

Types

+
+

Carrier

+
struct Carrier
+    name::String
+    unit::Union{String, Nothing}
+end
+
+
+

Represents a single (energy) carrier with a given name.

+

This is mostly used to represent various commodities that (easily) represent some form of energy (e.g. gas, water, …), +but also enables modelling commodities that are not (treated as) representing some type of energy (e.g. CO2). Specify +unit to bind that carrier to an (arbitrary) unit that allows easier plotting and result analysis.

+
+
+
+

CoreTemplate

+
CoreTemplate
+
+
+

A struct to represent an IESopt.jl “Core Template”.

+
+
+
+

Expression

+
Expression
+
+
+

A mutable struct representing a general expression in the optimization model.

+

Fields

+
    +
  • model::JuMP.Model: The IESopt model associated with the expression.

  • +
  • dirty::Bool: A flag indicating if the expression is dirty (modified but not updated). Defaults to false.

  • +
  • temporal::Bool: A flag indicating if the expression is temporal. Defaults to false.

  • +
  • empty::Bool: A flag indicating if the expression is empty. Defaults to false.

  • +
  • value::Union{Nothing, JuMP.VariableRef, JuMP.AffExpr, Vector{JuMP.AffExpr}, Float64, Vector{Float64}}: The value of the expression, which can be a JuMP variable reference, affine expression, vector of affine expressions, float, or vector of floats. Defaults to nothing.

  • +
  • internal::Union{Nothing, NamedTuple}: Internal data associated with the expression. Defaults to nothing.

  • +
+

Usage examples

+
if !my_exp.empty
+    # ... do something with `my_exp`, since it contains values ...
+end
+
+
+
# Get the Expression's value at Snapshot `t`.
+access(my_exp, t)
+
+# Get the Expression's value - could be vector-valued.
+access(my_exp)
+
+
+

Both ways to access the value can be used with a type assertion to get the value in a specific type:

+
# Get the Expression's value at Snapshot `t` as a Float64.
+access(my_exp, t, Float64)
+
+# Get the Expression's value as a Float64.
+access(my_exp, Float64)
+
+
+

If the value of my_exp is a vector of Float64, the first call will succeed, while the second will throw a type assertion error.

+
+
+
+

InternalData

+
InternalData
+
+
+

The internal data structure used by IESopt.jl to store all relevant information about the model, its components, and +results.

+
+
+
+

NonEmptyExpressionValue

+

This constant defines a union type NonEmptyExpressionValue which describes any value type that an Expression can hold, +guaranteeing that the Expression can not be empty.

+
+
+
+

NonEmptyNumericalExpressionValue

+

This constant defines a union type NonEmptyNumericalExpressionValue which describes any numerical value type that an +Expression can hold, guaranteeing that the Expression can not be empty. JuMP objects are not included, and it can be +either scalar or vector-valued.

+
+
+
+

NonEmptyScalarExpressionValue

+

This constant defines a union type NonEmptyScalarExpressionValue which describes any scalar-valued type that an +Expression can hold, guaranteeing that the Expression can not be empty.

+
+
+
+

OptionalScalarExpressionValue

+

This constant defines a union type OptionalScalarExpressionValue which describes any scalar-valued value type that an +Expression can hold. Due to Optional it also includes nothing.

+
+
+
+

Snapshot

+
struct Snapshot
+    name::_String
+    id::_ID
+    weight::_ScalarInput
+
+    is_representative::Bool
+    representative::_ID
+end
+
+
+

Represent a specific timestamp, that can be tied to timeseries values.

+

Each Snapshot expects a name, that can be used to hold a timestamp (as String; therefore supporting arbitrary +formats). The weight (default = 1.0) specifies the “probabilistic weight” of this Snapshot or the length of the +timeperiod that begins there (a weight of 2 can therefore represent a 2-hour-resolution; this also allows a +variable temporal resolution throughout the year/month/…).

+
+
+
+

Virtual

+

A Virtual (component) is a component that does not exist in the model, but one that a user might expect to exist. +These are “components” that refer to a template. If a user creates a component “my_storage_foo”, of type “Battery”, they +might expect (and want) to be able to interact with “my_storage_foo”. Since the template is flattened into explicit +CoreComponents in the back, “my_storage_foo” does not actually exist - a problem that these Virtuals solve.

+
+
+
+
+

Macros

+
+

@check

+
@check
+
+
+

Check whether the passed expression passes an ArgCheck.@check, especially helpful to validate a Template’s parameters.

+

Example

+

A validate section added to a Template can make use of the @check macro to validate the parameters passed to the +template. The macro will properly raise a descriptive error if the condition is not met.

+
parameters:
+  p: null
+
+functions:
+  validate: |
+    @check this.get("p") isa Number
+    @check this.get("p") > 0
+
+
+

See [“Template Validation”](@ref manual_templates_validation) in the documentation for more information.

+

!!! warning “Usage outside of Core Template validation” +This requires __component__ to be set to some String outside of calling the macro, since it accesses this to +construct a proper error message.

+
+
+
+

@config

+
@config(model, expr, type::Union{Symbol, Expr}=:Any)
+
+
+

Returns or sets a configuration value in the model’s configuration dictionary.

+

This macro is used to set and retrieve configuration values in the model’s configuration dictionary. This resolves the +access properly during compile time. A type can be optionally specified to assert the type of the value.

+

Example

+
# Setting a configuration value.
+@config(model, general.verbosity.core) = "info"
+
+# Getting a configuration value.
+verbosity = @config(model, general.verbosity.core, String)
+
+
+
+
+
+

@critical

+
@critical(msg, args...)
+
+
+

A macro that logs an error message and then throws an error with the same message. Use it in the same way you would use @error, or @info, etc.

+

Arguments

+
    +
  • msg: The main error message to be logged and thrown.

  • +
  • args...: Additional arguments to be included in the error log.

  • +
+
+
+
+

@profile

+
@profile(arg1, arg2=nothing)
+
+
+

This macro is used to profile the execution of a function. It captures the time, memory allocation, and number of calls +of the function. The profiling data is stored in the _profiling field of the _IESoptData structure. The identifier +passed to the macro is used to store the profiling data. If no identifier is provided, the function’s name is used as +the identifier.

+

Options to use this macro are:

+
    +
  • @profile model “identifier” foo()

  • +
  • @profile model foo()

  • +
  • @profile “identifier” foo(model)

  • +
  • @profile foo(model)

  • +
+
+
+
+
+

Functions

+
+

access

+
access(e::Expression)
+
+
+

Access the value of an Expression object.

+
+
+
+

add_term_to_objective!

+
add_term_to_objective!(model::JuMP.Model, objective::String, term::Union{JuMP.AffExpr, JuMP.VariableRef})
+
+
+

Add a term to an objective in the model, which can be used for dynamically creating objectives, e.g., in addons.

+

The default objective (that always exists) is called “total_cost”. Other objectives can be dynamically registered using +register_objective!, or they can be added based on the YAML configuration.

+

Arguments

+
    +
  • model::JuMP.Model: The model to add the term to.

  • +
  • objective::String: The name of the objective to add the term to.

  • +
  • term::Union{JuMP.AffExpr, JuMP.VariableRef}: The term to add to the objective.

  • +
+

Example

+
add_term_to_objective!(model, "my_obj", 2 * x)
+
+
+
+
+
+

build!

+
build!(model::JuMP.Model)
+
+
+

Builds and prepares the given IESopt model. This function performs the following steps:

+
    +
  1. Prepares the model by ensuring necessary conversions before performing consistency checks.

  2. +
  3. Checks the consistency of all parsed components in the model.

  4. +
  5. If any component fails the consistency check, an error is raised.

  6. +
  7. Builds the model if all components pass the consistency checks.

  8. +
  9. Logs profiling results after the build process, displaying the top 5 profiling results.

  10. +
+

Arguments

+
    +
  • model::JuMP.Model: The IESopt model to be built and prepared.

  • +
+

Errors

+
    +
  • Raises an error if any component does not pass the consistency check.

  • +
+
+
+
+

compute_IIS

+
function compute_IIS(model::JuMP.Model; filename::String = "")
+
+
+

Compute the IIS and print it. If filename is specified it will instead write all constraints to the given file. This +will fail if the solver does not support IIS computation.

+
+
+
+

extract_result

+
extract_result(model::JuMP.Model; path::String = "./out", write_to_file::Bool=true)
+
+
+

DEPRECATED

+
+
+
+

generate!

+
generate!(filename::String; @nospecialize(kwargs...))
+
+
+

Generate an IESopt model based on the top-level config in filename.

+

Arguments

+
    +
  • filename::String: The name of the file to load.

  • +
+

Keyword Arguments
+To be documented.

+

Returns

+
    +
  • model::JuMP.Model: The generated IESopt model.

  • +
+
+
+
+

get_T

+
get_T(model::JuMP.Model)
+
+
+

Retrieve the vector T from the IESopt model.

+

Arguments

+
    +
  • model::JuMP.Model: The IESopt model from which to extract the vector T.

  • +
+

Returns

+
    +
  • Vector{_ID}: The vector T.

  • +
+
+
+
+

get_component

+
function get_component(model::JuMP.Model, component_name::AbstractString)
+
+
+

Get the component component_name from model.

+
+
+
+

get_components

+
get_components(model::JuMP.Model; tagged::Union{Nothing, String, Vector{String}} = nothing)
+
+
+

Retrieve components from a given IESopt model.

+

Arguments

+
    +
  • model::JuMP.Model: The IESopt model from which to retrieve components.

  • +
  • tagged::Union{Nothing, String, Vector{String}}: Optional argument to specify tagged components to retrieve. +If nothing, all components are retrieved. If a String or Vector{String}, only components with the specified tags are retrieved.

  • +
+

Returns

+
    +
  • Vector{_CoreComponent}: A subset of components from the model.

  • +
+
+
+
+

get_global

+
get_global(type::String)
+
+
+

Get a global setting for IESopt.

+

Arguments

+
    +
  • type::String: The type of global setting. Currently supports: “parameters”, “config”, “addons”, “carriers”, “components”, “load_components”, “skip_validation”.

  • +
+

Returns

+
    +
  • The global setting.

  • +
+

Example

+
using IESopt
+
+get_global("skip_validation")
+
+
+
+
+
+

get_value_at

+
get_value_at(x::T, ::_ID) where {T <: Union{Nothing, Real, JuMP.VariableRef, JuMP.AffExpr}}
+
+
+

Returns the value of x at any Snapshot index t. Can be used to access variables without needing to handle whether +they are vector-valued or not.

+

Arguments

+
    +
  • x::T: The input value which can be of type Nothing, Real, JuMP.VariableRef, or JuMP.AffExpr.

  • +
  • ::_ID: Unused Snapshot index.

  • +
+

Returns

+
    +
  • The value of x.

  • +
+
+
+
+

get_version

+
get_version()
+
+
+

Get the current version of IESopt.jl.

+

Returns

+
    +
  • String: The current version of IESopt.jl.

  • +
+
+
+
+

internal

+
internal(model::JuMP.Model)
+
+
+

Retrieve the internal data structure from the JuMP model.

+
+
+
+

make_base_name

+
make_base_name(comp::_CoreComponent, str::String)
+
+
+

Create a JuMP object (e.g., a variable) for a given component based on the component’s name and a user-defined string.

+

Arguments

+
    +
  • comp: The core component for which to create the base name.

  • +
  • str: The user-defined string to append to the component’s name.

  • +
+

Returns

+
    +
  • A string containing the base name for the object.

  • +
+

Example

+
JuMP.@constraint(
+    model,
+    [t = get_T(model)],
+    profile.exp.value[t] <= 1,
+    base_name = make_base_name(profile, "con_custom"),
+    container = Array,
+)
+
+
+
+
+
+

optimize!

+
optimize!(model::JuMP.Model; kwargs...)
+
+
+

Optimize the given IESopt model with optional keyword arguments.

+

Arguments

+
    +
  • model::JuMP.Model: The IESopt model to be optimized.

  • +
  • kwargs...: Additional keyword arguments to be passed to the JuMP.optimize! function.

  • +
+

Description
+This function performs the following steps:

+
    +
  1. If there are constraint safety penalties, it relaxes the constraints based on these penalties.

  2. +
  3. Sets the verbosity of the solver output based on the model’s configuration.

  4. +
  5. Logs the solver output to a file if logging is enabled and supported by the solver.

  6. +
  7. Calls JuMP.optimize! to solve the model.

  8. +
  9. Checks the result count and termination status to log the optimization outcome.

  10. +
  11. Analyzes the constraint safety results if there were any constraint safety penalties.

  12. +
  13. Extracts and saves the results if the model is solved and feasible.

  14. +
  15. Profiles the results after optimization.

  16. +
+

Logging

+
    +
  • Logs messages about the relaxation of constraints, solver output, and optimization status.

  • +
  • Logs warnings if the safety constraint feature is triggered or if unexpected result counts are encountered.

  • +
  • Logs errors if the solver log file setup fails, if no results are returned, or if extracting results is not possible.

  • +
+

Returns

+
    +
  • nothing: This function does not return any value.

  • +
+
+
+
+

overview

+
overview(file::String)
+
+
+

Extracts the most important information from an IESopt model file, and returns it as a dictionary.

+
+
+
+

pack

+
pack(file::String; out::String="", method=:store)
+
+
+

Packs the IESopt model specified by the top-level config file file into single file.

+

The out argument specifies the output file name. If not specified, a temporary file is created. Returns the output +file name. The method argument specifies the compression method to use. The default is :store, which means no +compression is used. The other option is :deflate, which uses the DEFLATE compression method. The default (:auto) +applies :store to all files below 1 MB, :deflate otherwise.

+
+
+
+

parse!

+
parse!(model::JuMP.Model, filename::AbstractString; kwargs...)
+
+
+

Parse the model configuration from a specified file and update the given JuMP.Model object.

+

Arguments

+
    +
  • model::JuMP.Model: The JuMP model to be updated.

  • +
  • filename::AbstractString: The path to the configuration file. The file must have a .iesopt.yaml extension.

  • +
+

Keyword Arguments
+To be documented.

+

Returns

+
    +
  • Bool: Returns true if the model was successfully parsed.

  • +
+

Errors

+
    +
  • Logs a critical error if the file does not have the .iesopt.yaml extension or if there is an error while parsing the model.

  • +
+
+
+
+

register_objective!

+
register_objective!(model::JuMP.Model, objective::String)
+
+
+

Register a new objective in the model, which can for dynamically creating objectives, e.g., in addons.

+

Arguments

+
    +
  • model::JuMP.Model: The model to register the objective in.

  • +
  • objective::String: The name of the objective to register.

  • +
+

Example

+
register_objective!(model, "my_obj")
+
+
+

which is equivalent to:

+
config:
+  optimization:
+    objectives:
+      my_obj: []
+
+
+
+
+
+

run

+
run(filename::String; kwargs...)
+
+
+

Build, optimize, and return a model.

+

Arguments

+
    +
  • filename::String: The path to the top-level configuration file.

  • +
+

Keyword Arguments

+

Keyword arguments are passed to the generate!(...) function.

+
+
+
+

safe_close_filelogger

+
safe_close_filelogger(model::JuMP.Model)
+
+
+

Safely closes the file logger’s iostream if it is open. This function checks if the logger associated with the given model is a LoggingExtras.TeeLogger and if it contains a IESopt._FileLogger as one of its loggers. If the file logger’s stream is open, it will be closed.

+

Arguments

+
    +
  • model::JuMP.Model: The IESopt model which contains the logger to be closed.

  • +
+

Returns

+
    +
  • nothing: This function does not return any value.

  • +
+

Notes

+
    +
  • The function includes a try-catch block to handle any potential errors during the closing process. Currently, the catch block does not perform any actions.

  • +
+
+
+
+

set_global!

+
set_global!(type::String, key::String, @nospecialize(value))
+
+
+

Set a global setting for IESopt. These will be used as defaults for every subsequent function call (that supports +these). User passed settings will override these defaults.

+

Arguments

+
    +
  • type::String: The type of global setting. Currently supports: “parameters”, “config”, “addons”, “carriers”, “components”, “load_components”.

  • +
  • key::String: The key of the global setting.

  • +
  • value: The value of the global setting.

  • +
+

Example

+
using IESopt
+
+set_global!("config", "general.verbosity.core", "error")
+
+
+
+
+
+

to_table

+
function to_table(model::JuMP.Model; path::String = "./out", write_to_file::Bool=true)
+
+
+

Turn model into a set of CSV files containing all core components that represent the model.

+

This can be useful by running

+
IESopt.parse!(model, filename)
+IESopt.to_table(model)
+
+
+

which will parse the model given by filename, without actually building it (which saves a lot of time), and will +output a complete “description” in core components (that are the resolved version of all non-core components).

+

If write_to_file is false it will instead return a dictionary of all DataFrames.

+
+
+
+

unpack

+
unpack(file::String; out::String="", force_overwrite::Bool=false)
+
+
+

Unpacks the IESopt model specified by file.

+

The out argument specifies the output directory. If not specified, a temporary directory is created. Returns the +path to the top-level config file. The force_overwrite argument specifies whether to overwrite existing files.

+
+
+
+

validate

+
validate(toplevel_config_file::String)
+
+
+

Validate the model description bsaed on the given top-level configuration file. This function checks the top-level +configuration file and all referenced files for validity. The function returns true if the model description is valid, +and false otherwise. If the model description is invalid, the function will print an error message.

+

Arguments

+
    +
  • toplevel_config_file::String: The path to the top-level configuration file.

  • +
+

Returns

+
    +
  • valid::Bool: Whether the model description is valid (true), or not (false).

  • +
+
+
+
+

write_to_file

+
write_to_file(model::JuMP.Model, filename::String; format::JuMP.MOI.FileFormats.FileFormat = JuMP.MOI.FileFormats.FORMAT_AUTOMATIC, kwargs...)
+
+
+

Write the given IESopt model to a file with the specified filename and format.

+

Be aware, that this function will overwrite any existing file with the same name!

+

Arguments

+
    +
  • model::JuMP.Model: The IESopt model to be written to a file.

  • +
  • filename::String: The name of the file to which the model should be written. Note that if the format is set to +FORMAT_AUTOMATIC, the the file extension will be forced to lower case to allow detection.

  • +
  • format::JuMP.MOI.FileFormats.FileFormat (optional): The format in which the model should be written. The default is +JuMP.MOI.FileFormats.FORMAT_AUTOMATIC; if left as that, it will try to automatically determine the format based on +the file extension.

  • +
+

All additional keyword arguments are passed to the JuMP.write_to_file function.

+

Returns

+
    +
  • String: The absolute path to the file to which the model was written.

  • +
+

Example

+
import IESopt
+
+model = IESopt.generate!("config.iesopt.yaml")
+IESopt.write_to_file(model, "model.lp")
+
+
+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/julia/resultsduckdb.html b/pages/manual/julia/resultsduckdb.html new file mode 100644 index 0000000..3c68084 --- /dev/null +++ b/pages/manual/julia/resultsduckdb.html @@ -0,0 +1,245 @@ + + + + + + + + + +ResultsDuckDB | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

ResultsDuckDB

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

The ResultsDuckDB module provides functions to extract results from a JuMP model and store them in a DuckDB database.

+
+
+

API Reference

+
+

Types

+
+
+

Macros

+
+
+

Functions

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/julia/resultsjld2.html b/pages/manual/julia/resultsjld2.html new file mode 100644 index 0000000..736e911 --- /dev/null +++ b/pages/manual/julia/resultsjld2.html @@ -0,0 +1,264 @@ + + + + + + + + + +ResultsJLD2 | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

ResultsJLD2

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

This module provides functions to save and load results to and from JLD2 files.

+
+
+

API Reference

+
+

Types

+
+
+

Macros

+
+
+

Functions

+
+

load_results

+
load_results(filename::String)
+
+
+

Load results from a JLD2 file.

+

Arguments

+
    +
  • filename::String: The path to the JLD2 file.

  • +
+

Returns

+
    +
  • results: The IESopt result object.

  • +
+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/julia/utilities.html b/pages/manual/julia/utilities.html new file mode 100644 index 0000000..ab9a8a5 --- /dev/null +++ b/pages/manual/julia/utilities.html @@ -0,0 +1,311 @@ + + + + + + + + + +Utilities | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Utilities

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

This module contains utility functions for the IESopt package, that can be helpful in preparing or analysing components.

+
+
+

API Reference

+
+

Types

+
+
+

Macros

+
+
+

Functions

+
+

annuity

+
annuity(total::Real; lifetime::Real, rate::Float64, fraction::Float64)
+
+
+

Calculate the annuity of a total amount over a lifetime with a given interest rate.

+

Arguments

+
    +
  • total::Real: The total amount to be annuitized.

  • +
+

Keyword Arguments

+
    +
  • lifetime::Real: The lifetime over which the total amount is to be annuitized.

  • +
  • rate::Float64: The interest rate at which the total amount is to be annuitized.

  • +
  • fraction::Float64: The fraction of a year that the annuity is to be calculated for (default: 1.0).

  • +
+

Returns

+

Float64: The annuity of the total amount over the lifetime with the given interest rate.

+

Example

+

Calculating a simple annuity, for a total amount of € 1000,- over a lifetime of 10 years with an interest rate of 5%:

+
# Set a parameter inside a template.
+set("capex", IESU.annuity(1000.0; lifetime=10, rate=0.05))
+
+
+

Calculating a simple annuity, for a total amount of € 1000,- over a lifetime of 10 years with an interest rate of 5%, +for a fraction of a year (given by MODEL.yearspan, which is the total timespan of the model in years):

+
# Set a parameter inside a template.
+set("capex", IESU.annuity(1000.0; lifetime=10, rate=0.05, fraction=MODEL.yearspan))
+
+
+
+
+
+

timespan

+
timespan(model::JuMP.Model)::Float64
+
+
+

Calculate the total timespan (duration, time period, …) of a model in hours.

+

Arguments

+
    +
  • model::JuMP.Model: The model for which the timespan is to be calculated.

  • +
+

Returns

+

Float64: The total timespan of the model in hours.

+
+
+
+

yearspan

+
yearspan(model::JuMP.Model)::Float64
+
+
+

Calculate the total timespan (duration, time period, …) of a model in years. This assumes that a full year has a total +of 8760 hours.

+

Arguments

+
    +
  • model::JuMP.Model: The model for which the timespan is to be calculated.

  • +
+

Returns

+

Float64: The total timespan of the model in years.

+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/python/configuration.html b/pages/manual/python/configuration.html new file mode 100644 index 0000000..3ea70cc --- /dev/null +++ b/pages/manual/python/configuration.html @@ -0,0 +1,332 @@ + + + + + + + + + +Configuration | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Configuration

+

Various configuration options are handled using environment variables. These can easily be configured using .env files. You can check out python-dotenv for more information on how to use .env files with Python.

+

How to? For the basics, create a file called .env in the root of your project and add the following:

+
+
A simple .env file.
+
IESOPT_CORE = 2.0.0
+
+
+
+
+

Tip

+

For many applications it is perfectly fine not to use any configuration at all. The defaults are chosen to work well in most cases. Each release of the Python wrapper iesopt is bound to a specific version of the Julia package IESopt.jl. Changing this is only necessary if you want to use a different version of the Julia package. Further, we install a default version of HiGHS as solver - so unless you need to use (and have access to) a commercial solver, you should be fine with the default settings.

+
+
+

Julia packages

+
+
+

IESopt.jl

+

To install a specific version of the Julia core, IESopt.jl, use the following:

+
+
Setting the IESopt.jl version.
+
IESOPT_CORE = 2.0.0
+
+
+
+
+
+

Solvers

+

Installing solver packages can be done using the following:

+
+
Setting the solver versions.
+
IESOPT_SOLVER_HIGHS = 1.12.0
+
+
+
+

Other examples may be setting IESOPT_SOLVER_CPLEX or IESOPT_SOLVER_GUROBI.

+
+

Forcing a solver executable version

+

For example when installing Gurobi, it might be that the Julia wrapper pulls a version newer than the one you are able +to use according to your license. This can be fixed by explicitly adding the corresponding _jll (“binary wrapper”) +as dependency:

+
+
Fixing a solver executable version in .env.
+
IESOPT_PKG_Gurobi_jll = 11.0.3
+IESOPT_SOLVER_GUROBI = 1.6.0
+
+
+
+

This will use the latest Gurobi_jll version related to Gurobi 11.

+
+
+
+

Important versions

+

The following other entries are potentially used in some projects:

+
+
Fine grained version control.
+
IESOPT_JULIA = 1.11.1
+IESOPT_JUMP = 1.23.3
+
+
+
+
+
+

Arbitrary packages

+

To install packages that are not part of or related to IESopt, you can use the following:

+
+
Arbitrary packages.
+
IESOPT_PKG_ModelingToolkit = 1.0.3
+
+
+
+

Make sure the proper casing of the package name is used. The package name is case-sensitive. Even though environment variables are commonly upper-case only, the package name has to reflect the Julia package’s wording.

+
+
+

Non-registered packages

+

To install non-registered packages, you can use the following syntax:

+
+
Installing packages from GitHub.
+
IESOPT_CORE = https://github.com/ait-energy/IESopt.jl#super-important-feature
+
+
+
+

When such a repository is updated, we might not be able to automatically update the package. In this case, you can use the following

+
+
Updating packages from GitHub.
+
import iesopt
+iesopt.julia.seval('import Pkg; Pkg.update("IESopt")')
+
+
+
+

to forcefully update a Julia package.

+
+
+

Options

+

Currently the following options are available:

+
+
Options:
+

+
IESOPT_MULTITHREADED:
+

yes or no (default). Talk to us before using this.

+
+
IESOPT_OPTIMIZATION:
+

rapid, latency (default), normal, or performance. Consider using latency for small models, or repeatedly executing your code, since it may be faster for these kind of work loads. Set it to performance for large models where initial up-front costs are not relevant. normal refers to the default settings chosen by “just launching Julia”. For iterative development on a single small model, rapid might be the best choice - however, it compromises actual performance, even in subsequent runs, so it is not recommended for production code.

+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/python/index.html b/pages/manual/python/index.html new file mode 100644 index 0000000..4c8cbdb --- /dev/null +++ b/pages/manual/python/index.html @@ -0,0 +1,285 @@ + + + + + + + + + +Python | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Python

+
+
+iesopt.IESopt = Julia: IESopt
+

IESopt.jl module from Julia.

+
+
+
+iesopt.examples() list[str][source]
+

Return a list of all available examples.

+
+
Returns:
+

List of available examples. This contains the names of the examples, not the full filenames (so, e.g., +“some_example” instead of “some_example.iesopt.yaml”).

+
+
+
+
+
+iesopt.make_example(example: str, dst_dir: str | Path = './', dst_name: str | None = None) Path[source]
+

Generate a local copy of a specific example.

+

A list of examples, and their exact names, can be obtained using iesopt.examples().

+
+
Parameters:
+
    +
  • example – str +Name of the example to generate.

  • +
  • dst_dir – Optional[str] +Directory to generate the example in, defaults to “./”.

  • +
  • dst_name – Optional[str] +Name of the generated example file (without the “.iesopt.yaml” extension), e.g., +“config”, will create dst_dir/config.iesopt.yaml. Will default to the original name of the example.

  • +
+
+
Returns:
+

Path to the generated example file.

+
+
+
+
+
+iesopt.run(filename: str | Path, **kwargs) Model[source]
+

Generate and optimize an IESopt model.

+

Results can be accessed (after a successful optimization) using model.results.

+
+
Parameters:
+

filename – str +Path to the IESopt model file to load.

+
+
Keyword Arguments:
+

**kwargs – Additional keyword arguments to pass to the Model constructor.

+
+
Returns:
+

The generated and optimized IESopt model.

+
+
+

Example

+
+
Run an IESopt model
+
import iesopt
+iesopt.run("opt/config.iesopt.yaml")
+
+
+
+
+ +
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/python/jump.html b/pages/manual/python/jump.html new file mode 100644 index 0000000..bc683e8 --- /dev/null +++ b/pages/manual/python/jump.html @@ -0,0 +1,243 @@ + + + + + + + + + +JuMP | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

JuMP

+
+
+iesopt.JuMP = Julia: JuMP
+

JuMP.jl module from Julia.

+
+
+
+iesopt.jump_value(*args, **kwargs)
+
+
+
+iesopt.jump_dual(*args, **kwargs)
+
+
+
+iesopt.jump_reduced_cost(*args, **kwargs)
+
+
+
+iesopt.jump_shadow_price(*args, **kwargs)
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/python/model.html b/pages/manual/python/model.html new file mode 100644 index 0000000..7ed4c5d --- /dev/null +++ b/pages/manual/python/model.html @@ -0,0 +1,443 @@ + + + + + + + + + +Model | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Model

+
+
+class iesopt.Model(filename: str | Path, **kwargs)[source]
+

An IESopt model, based on an IESopt.jl core model.

+
+
+compute_iis(filename=None) None[source]
+

Compute and print the Irreducible Infeasible Set (IIS) of the model, or optionally write it to a file.

+

Note that this requires a solver that supports IIS computation.

+
+
Parameters:
+

filename (Optional[str | Path]) – The filename to write the IIS to. If None, the IIS is only printed to the +console.

+
+
+

Examples

+
+
Computing the IIS of a model.
+
import iesopt
+
+model = iesopt.run("infeasible_model.iesopt.yaml")
+model.compute_iis()
+
+# or (arbitrary filename/extension):
+model.compute_iis(filename="my_problem.txt")
+
+
+
+
+
+
+property core
+

Access the core JuMP model that is used internally.

+
+
+
+property data
+

Access the IESopt data object of the model.

+

This is deprecated; use model.internal instead (similar to the Julia usage IESopt.internal(model)).

+
+
+
+extract_result(component: str, field: str, mode: str = 'value')[source]
+

Manually extract a specific result from the model.

+
+
+
+generate() None[source]
+

Generate a IESopt model from the attached top-level YAML config.

+
+
+
+get_component(component: str)[source]
+

Get a core component based on its full name.

+
+
+
+get_components(tagged=None)[source]
+

Get all components of the model, possibly filtered by (a) tag(s).

+
+
Parameters:
+

tagged (str or list of str) – The tag(s) to filter the components by, can be None (default) to get all components.

+
+
+
+
+
+get_constraint(component: str, constraint: str)[source]
+

Get a specific constraint from a core component.

+
+
+
+get_variable(component: str, variable: str)[source]
+

Get a specific variable from a core component.

+
+
+
+property internal
+

Access the IESopt data object of the model.

+
+
+
+nvar(var: str)[source]
+

Extract a named variable, from model.

+

If your variable is called :myvar, and you would access it in Julia using model[:myvar], you can call +model.nvar(“myvar”).

+
+
+
+property objective_value: float
+

Get the objective value of the model. Only available if the model was solved beforehand.

+
+
+
+optimize() None[source]
+

Optimize the model.

+
+
+
+property results: Results
+

Get the results of the model.

+
+
+
+property status: ModelStatus
+

Get the current status of this model. See ModelStatus for possible values.

+
+
+
+write_to_file(filename=None, *, format: str = 'automatic') str[source]
+

Write the model to a file.

+

Consult the Julia version of this function, [IESopt.write_to_file](https://ait-energy.github.io/iesopt/pages/manual/julia/index.html#write-to-file) +for more information. This will automatically invoke generate if the model has not been generated yet.

+
+
Parameters:
+

filename (Optional[str]) – The filename to write to. If None, the path and name are automatically +determined by IESopt.

+
+
Keyword Arguments:
+

format (str) – The format to write the file in. If automatic, the format is determined based on the +extension of the filename. Otherwise, it used as input to [JuMP.write_to_file](https://jump.dev/JuMP.jl/stable/api/JuMP/#write_to_file), +by converting to uppercase and prefixing it with MOI.FileFormats.FORMAT_. Writing to, e.g., +an LP file can be done by setting format=”lp” or format=”LP”.

+
+
Returns:
+

The filename (including path) of the written file.

+
+
Return type:
+

str

+
+
+

Examples

+
+
Writing a model to a problem file.
+
import iesopt
+
+cfg = iesopt.make_example("01_basic_single_node", dst_dir="opt")
+
+# Model will be automatically generated when calling `write_to_file`:
+model = iesopt.Model(cfg)
+model.write_to_file()
+
+# It also works with already optimized models:
+model = iesopt.run(cfg)
+model.write_to_file("opt/out/my_problem.LP")
+
+# And supports different formats:
+target = model.write_to_file("opt/out/my_problem.foo", format="mof")
+print(target)
+
+
+
+
+
+
+
+class iesopt.model.ModelStatus[source]
+

Status of an iesopt.Model.

+
+
+EMPTY = 'empty'
+
+
+
+FAILED_GENERATE = 'failed_generate'
+
+
+
+FAILED_OPTIMIZE = 'failed_optimize'
+
+
+
+GENERATED = 'generated'
+
+
+
+INFEASIBLE = 'infeasible'
+
+
+
+INFEASIBLE_OR_UNBOUNDED = 'infeasible_unbounded'
+
+
+
+OPTIMAL = 'optimal'
+
+
+
+OPTIMAL_LOCALLY = 'local_optimum'
+
+
+
+OTHER = 'other'
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/python/results.html b/pages/manual/python/results.html new file mode 100644 index 0000000..0f602e7 --- /dev/null +++ b/pages/manual/python/results.html @@ -0,0 +1,299 @@ + + + + + + + + + +Results | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Results

+
+
+class iesopt.Results(*, file: str = None, model=None)[source]
+
+
+entries(field: str = None)[source]
+

Get all available entries for a specific field, or all fields if field is None.

+
+
Parameters:
+

field – Optional[str] +Field to get entries for, by default None which returns all fields

+
+
+
+
+
+query_available_results(component: str, mode: str = 'both')[source]
+

Query the available results for a specific component, optionally filtered by mode.

+
+
Parameters:
+
    +
  • component – str +Component to query results for

  • +
  • mode – Optional[str] +Mode to query results for, either “both”, “primal”, or “dual”, by default “both”

  • +
+
+
+
+
+
+to_dict(filter=None, field_types=None, build_cache: bool = True) dict[source]
+

Extract results from the Results object and return them as a dictionary.

+
+
Parameters:
+
    +
  • filter – Optional[Callable] +Filter function to apply to the results, by default None, must take three arguments: (c, t, f) where c is +the component’s name, t is the field type (“var”, “exp”, “con”, or “obj”), and f is the field name.

  • +
  • field_types – Optional[list[str]] +Field types to extract, by default None, which extracts all field types.

  • +
  • build_cache – Optional[bool] +Whether to build a cache of the results, by default True.

  • +
+
+
Returns:
+

Dictionary of results, with keys as tuples of (component, fieldtype, field) and values as the result.

+
+
+
+
+
+to_pandas(filter=None, field_types=None, orientation: str = 'long', build_cache: bool = True)[source]
+

Extract results from the Results object and return them as pandas.DataFrame or pandas.Series (depending on the +shape of the included results).

+
+
Parameters:
+
    +
  • filter – Optional[Callable] +Filter function to apply to the results, by default None, must take three arguments: (c, t, f) where c is +the component’s name, t is the field type (“var”, “exp”, “con”, or “obj”), and f is the field name

  • +
  • field_types – Optional[list[str]] +Field types to extract, by default None, which extracts all field types

  • +
  • orientation – Optional[str] +Orientation of the resulting DataFrame, either “wide” or “long”, by default “long”

  • +
  • build_cache – Optional[bool] +Whether to build a cache of the results, by default True.

  • +
+
+
Returns:
+

DataFrame or Series of results, depending on the orientation and shape of the results.

+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/python/util.html b/pages/manual/python/util.html new file mode 100644 index 0000000..f484d4a --- /dev/null +++ b/pages/manual/python/util.html @@ -0,0 +1,259 @@ + + + + + + + + + +Utilities | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Utilities

+
+
+iesopt.julia -> juliacall.Main
+

The Julia object, if setup was successful.

+
+
Usage
+
import iesopt
+iesopt.julia.println("Hello, from Julia's world!")
+
+
+
+
+
+
+iesopt.Symbol(string: str)
+

Create a Julia Symbol from string.

+

This function should be called as iesopt.Symbol, but can also be called as iesopt.jl_symbol.

+
+
Parameters:
+

string (str) – The string to convert to a Julia Symbol.

+
+
Returns:
+

The Julia Symbol object.

+
+
+

Examples

+
+
Directly import and use the Symbol alias
+
from iesopt import Symbol
+Symbol(":iesopt")               # => `Julia: :iesopt`
+
+
+
+
+
+
+iesopt.get_jl_docstr(obj: str, module: str = 'IESopt')
+

Get the documentation string of a Julia object inside the IESopt module.

+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/core/connection.html b/pages/manual/yaml/core/connection.html new file mode 100644 index 0000000..eb7ca31 --- /dev/null +++ b/pages/manual/yaml/core/connection.html @@ -0,0 +1,603 @@ + + + + + + + + + +Connection | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Connection

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, …

+
+
+

Parameters

+
+

node_from

+

This Connection models a flow from node_from to node_to (both are Nodes).

+
    +
  • mandatory: yes

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

node_to

+

This Connection models a flow from node_from to node_to (both are Nodes).

+
    +
  • mandatory: yes

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

carrier

+

Carrier of this Connection. If not given, automatically picks the carrier of the Nodes it connects. This parameter is not necessary, and only exists to allow for a more explicit definition.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

capacity

+

The symmetric bound on this Connection’s flow. Results in lb = -capacity and ub = capacity. Must not be specified if lb, ub, or both are explicitly stated.

+
    +
  • mandatory: no

  • +
  • default: \(+\infty\)

  • +
  • values: numeric, col@file, decision:value

  • +
  • unit: power

  • +
+
+
+

lb

+

Lower bound of this Connection’s flow.

+
    +
  • mandatory: no

  • +
  • default: \(-\infty\)

  • +
  • values: numeric, col@file, decision:value

  • +
  • unit: power

  • +
+
+
+

ub

+

Upper bound of this Connection’s flow.

+
    +
  • mandatory: no

  • +
  • default: \(+\infty\)

  • +
  • values: numeric, col@file, decision:value

  • +
  • unit: power

  • +
+
+
+

cost

+

Cost of every unit of energy flow over this connection that is added to the model’s objective function. Keep in mind that negative flows will induce negative costs, which can be used to model revenues. Further, a bidirectional Connection (if lb < 0, which is the default, or if capacity is used) with a positive cost will lead to negative costs for the reverse flow. If you do not want this, split the Connection into two separate ones, each being unidirectional (with lb: 0). Remember, that these can share the same “capacity” (which is then set asub), even when using decision:value or col@file as value.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: numeric

  • +
  • unit: monetary (per energy)

  • +
+
+
+

loss

+

Fractional loss when transferring energy. Per default, this loss occurs “at the destination”, which means that for a loss of 5%, set as loss: 0.05, and considering a Snapshot where the Connection has a flow value of 100, it will “extract” 100 from node_from and “inject” 95 into node_to. Since the flow variable is given as power, this would, e.g., translate to consuming 200 units of energy at node_from and injecting 190 units at node_to, if the Snapshot duration is 2 hours. Refer to loss_mode for more information on how to modify this behaviour.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: \in [0, 1]

  • +
  • unit: -

  • +
+
+
+

loss_mode

+

Configures where the fractional loss (set by loss) occurs. to means that the loss occurs at the destination, from means that the loss occurs at the source, and split means that the loss is split between source and destination. Not that for most cases to is the correct choice, as it is the most common behaviour - the other options, e.g., lead to the possibility of drawing at a higher power from the node_from than the capacity of the Connection “allows”.

+
    +
  • mandatory: no

  • +
  • default: \(to\)

  • +
  • values: to, from, split

  • +
  • unit: -

  • +
+
+
+

delay

+

Delay between withdrawing energy from node_from and injecting it into node_to. This can be used to model, e.g., river flows, where the water takes some time to travel from one point to another (e.g., in a cascade of reservoirs). Note that this delay is always “cyclic” meaning if it extends over the modeling period, it will be “wrapped around”, starting again at the beginning of the period. Further, the flow is always withdrawn at Snapshot t and injected at t + delay.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: \in [0, \infty]

  • +
  • unit: hours

  • +
+
+
+

build_priority

+

Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+
+

Detailed reference

+
+

Expressions

+
+

in

+ +

Full implementation and all details: connection/exp_in @ IESopt.jl

+
+

The construction of a Connection’s in/out expressions is directly done in _connection_var_flow!(...), the function that constructs the variable flow.

+
+
+
+

out

+ +

Full implementation and all details: connection/exp_out @ IESopt.jl

+
+

The construction of a Connection’s in/out expressions is directly done in _connection_var_flow!(...), the function that constructs the variable flow.

+
+
+
+

pf_flow

+ +

Full implementation and all details: connection/exp_pf_flow @ IESopt.jl

+
+

Construct the JuMP.AffExpr holding the PTDF based flow of this Connection.

+

 

+

This needs the global addon Powerflow with proper settings for mode, as well as properly configured power flow parameters for this Connection (pf_V, pf_I, pf_X, …).

+
+
+
+
+

Variables

+
+

flow

+ +

Full implementation and all details: connection/var_flow @ IESopt.jl

+
+

Add the variable representing the flow of this connection to the model. This can be accessed via connection.var.flow[t].

+

 

+

Additionally, the flow gets “injected” at the Nodes that the connection is connecting, resulting in

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{connection.node}_{from}\text{.injection}_t = \text{connection.node}_{from}\text{.injection}_t - \text{flow}_t, \qquad \forall t \in T \\ +& \text{connection.node}_{to}\text{.injection}_t = \text{connection.node}_{to}\text{.injection}_t + \text{flow}_t, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+

For “PF controlled” Connections (ones that define the necessary power flow parameters), the flow variable may not be constructed (depending on specific power flow being used). The automatic result extraction will detect this and return the correct values either way. Accessing it manually can be done using connection.exp.pf_flow[t].

+
+
+
+
+

Constraints

+
+

flow_bounds

+ +

Full implementation and all details: connection/con_flow_bounds @ IESopt.jl

+
+

Add the constraint defining the bounds of the flow (related to connection) to the model.

+

 

+

Specifying capacity will lead to symmetric bounds (\(\text{lb} := -capacity\) and \(\text{ub} := capacity\)), while asymmetric bounds can be set by explicitly specifying lb and ub.

+

 

+
+
+

Note

+

Usage of etdf is currently not fully tested, and not documented.

+
+
+

Upper and lower bounds can be “infinite” (by not setting them) resulting in the respective constraints not being added, and the flow variable therefore being (partially) unconstrained. Depending on the configuration the flow is calculated differently:

+

 

+
    +
  • if connection.etdf is set, it is based on an ETDF sum flow,

  • +
  • if connection.exp.pf_flow is available, it equals this

  • +
  • else it equal connection.var.flow

  • +
+

 

+

This flow is then constrained:

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{flow}_t \geq \text{lb}, \qquad \forall t \in T \\ +& \text{flow}_t \leq \text{ub}, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+

 

+
+
+

Constraint safety

+

The lower and upper bound constraint are subject to penalized slacks.

+
+
+
+
+

Objectives

+
+

cost

+ +

Full implementation and all details: connection/obj_cost @ IESopt.jl

+
+

Add the (potential) cost of this connection to the global objective function.

+

 

+

The connection.cost setting introduces a fixed cost of “transportation” to the flow of this Connection. It is based on the directed flow. This means that flows in the “opposite” direction will lead to negative costs:

+

 

+
+
+\[ +\sum_{t \in T} \text{flow}_t \cdot \text{cost}_t \cdot \omega_t +\]
+
+

 

+

Here \(\omega_t\) is the weight of Snapshot t.

+

 

+
+
+

Costs for flows in both directions

+

If you need to apply a cost term to the absolute value of the flow, consider splitting the Connection into two different ones, in opposing directions, and including lb = 0.

+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/core/decision.html b/pages/manual/yaml/core/decision.html new file mode 100644 index 0000000..647795c --- /dev/null +++ b/pages/manual/yaml/core/decision.html @@ -0,0 +1,607 @@ + + + + + + + + + +Decision | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Decision

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

A Decision represents a basic decision variable in the model that can be used as input for various other core component’s settings, as well as have associated costs.

+
+
+

Parameters

+
+

lb

+

Minimum size of the decision value (considered for each “unit” if count allows multiple “units”).

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+

ub

+

Maximum size of the decision value (considered for each “unit” if count allows multiple “units”).

+
    +
  • mandatory: no

  • +
  • default: \(+\infty\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+

cost

+

Cost that the decision value induces, given as cost \cdot value.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: monetary (per value)

  • +
+
+
+

fixed_value

+

If mode: fixed, this value is used as the fixed value of the decision. This can be useful if this Decision was used in a previous optimization and its value should be fixed to that value in the next optimization (applying it where ever it is used, instead of needing to find all usages). Furthermore, this allows extracting the dual value of the constraint that fixes the value, assisting in approaches like Benders decomposition. Note that this does not change the induced cost in any way.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+

fixed_cost

+

This setting activates a “fixed cost” component for this decision variable, which requires that the model’s problem type allows for binary variables (e.g., MILP). This can be used to model fixed costs that are only incurred if the decision variable is active (e.g., a fixed cost for an investment that is only incurred if the investment is made). If the decision is 0, no fixed costs have to be paid; however, if the decision is greater than 0, the fixed cost is incurred. Note that after deciding to activate the decision, the overall value is still determined in the usual (continuous) way, incuring the (variable) cost as well. More complex cost functions can be modelled by switching to mode sos1 or sos2 and using the sos parameter.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: -

  • +
  • unit: monetary

  • +
+
+
+

mode

+

Type of the decision variable that is constructed. linear results in a continuous decision, integer results in a integer variable, binary constrains it to be either 0 or 1. sos1 and sos2 can be used to activate SOS1 or SOS2 mode (used for piecewise linear costs). See fixed_value if setting this to fixed.

+
    +
  • mandatory: no

  • +
  • default: \(linear\)

  • +
  • values: linear, binary, integer, sos1, sos2, fixed

  • +
  • unit: -

  • +
+
+
+

sos

+

TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: list

  • +
  • unit: -

  • +
+
+
+

build_priority

+

Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others.

+
    +
  • mandatory: no

  • +
  • default: \(1000\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+
+

Detailed reference

+
+

Expressions

+
+
+

Variables

+
+

fixed

+ +

Full implementation and all details: decision/var_fixed @ IESopt.jl

+
+

to be added

+
+
+
+

sos

+ +

Full implementation and all details: decision/var_sos @ IESopt.jl

+
+

to be added

+
+
+
+

value

+ +

Full implementation and all details: decision/var_value @ IESopt.jl

+
+

Add the variable describing the value of this decision to the model. If lower and upper bounds (decision.lb and decision.ub) are the same, the variable will immediately be fixed to that value. This can be accessed via decision.var.value.

+
+
+
+
+

Constraints

+
+

fixed

+ +

Full implementation and all details: decision/con_fixed @ IESopt.jl

+
+

to be added

+
+
+
+

sos1

+ +

Full implementation and all details: decision/con_sos1 @ IESopt.jl

+
+

to be added

+
+
+
+

sos2

+ +

Full implementation and all details: decision/con_sos2 @ IESopt.jl

+
+

to be added

+
+
+
+

sos_value

+ +

Full implementation and all details: decision/con_sos_value @ IESopt.jl

+
+

to be added

+
+
+
+
+

Objectives

+
+

fixed

+ +

Full implementation and all details: decision/obj_fixed @ IESopt.jl

+
+

to be added ```

+
+
+
+

sos

+ +

Full implementation and all details: decision/obj_sos @ IESopt.jl

+
+

Add the cost defined by the SOS-based value of this Decision to the model.

+
+
+
+

value

+ +

Full implementation and all details: decision/obj_value @ IESopt.jl

+
+

Add the cost defined by the value of this Decision to the model:

+

 

+
+
+\[ +\text{value} \cdot \text{cost} +\]
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/core/node.html b/pages/manual/yaml/core/node.html new file mode 100644 index 0000000..a041d4d --- /dev/null +++ b/pages/manual/yaml/core/node.html @@ -0,0 +1,639 @@ + + + + + + + + + +Node | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Node

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

A Node represents a basic intersection/hub for energy flows. This can for example be some sort of bus (for electrical systems). It enforces a nodal balance equation (= “energy that flows into it must flow out”) for every Snapshot. Enabling the internal state of the Node allows it to act as energy storage, modifying the nodal balance equation. This allows using Nodes for various storage tasks (like batteries, hydro reservoirs, heat storages, …).

+ +
+
+

Parameters

+
+

carrier

+

Carrier of this Node. All connecting components need to respect that.

+
    +
  • mandatory: yes

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

has_state

+

If true, the Node is considered to have an internal state (“stateful Node”). This allows it to act as energy storage. Connect Connections or Units to it, acting as charger/discharger.

+
    +
  • mandatory: no

  • +
  • default: \(false\)

  • +
  • values: true, false

  • +
  • unit: -

  • +
+
+
+

state_lb

+

Lower bound of the internal state, requires has_state = true.

+
    +
  • mandatory: no

  • +
  • default: \(-\infty\)

  • +
  • values: numeric, col@file, decision:value

  • +
  • unit: energy

  • +
+
+
+

state_ub

+

Upper bound of the internal state, requires has_state = true.

+
    +
  • mandatory: no

  • +
  • default: \(+\infty\)

  • +
  • values: numeric, col@file, decision:value

  • +
  • unit: energy

  • +
+
+
+

state_cyclic

+

Controls how the state considers the boundary between last and first Snapshot. disabled disables cyclic behaviour of the state (see also state_initial), eq leads to the state at the end of the year being the initial state at the beginning of the year, while geq does the same while allowing the end-of-year state to be higher (= “allowing to destroy energy at the end of the year”).

+
    +
  • mandatory: no

  • +
  • default: \(eq\)

  • +
  • values: eq, geq, or disabled

  • +
  • unit: -

  • +
+
+
+

state_initial

+

Sets the initial state. Must be used in combination with state_cyclic = disabled.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: numeric

  • +
  • unit: energy

  • +
+
+
+

state_final

+

Sets the final state. Must be used in combination with state_cyclic = disabled.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: numeric

  • +
  • unit: energy

  • +
+
+
+

state_percentage_loss

+

Per Snapshot percentage loss of state (losing 1% should be set as 0.01).

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: \in [0, 1]

  • +
  • unit: -

  • +
+
+
+

nodal_balance

+

Can only be used for has_state = false. enforce forces total injections to always be zero (similar to Kirchhoff’s current law), create allows “supply < demand”, destroy allows “supply > demand”, at this Node.

+
    +
  • mandatory: no

  • +
  • default: \(enforce\)

  • +
  • values: enforce, destroy, or create

  • +
  • unit: -

  • +
+
+
+

sum_window_size

+

TODO.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: integer

  • +
  • unit: -

  • +
+
+
+

sum_window_step

+

TODO.

+
    +
  • mandatory: no

  • +
  • default: \(1\)

  • +
  • values: integer

  • +
  • unit: -

  • +
+
+
+

build_priority

+

Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+
+

Detailed reference

+
+

Expressions

+
+

injection

+ +

Full implementation and all details: node/exp_injection @ IESopt.jl

+
+

Add an empty (JuMP.AffExpr(0)) expression to the node that keeps track of feed-in and withdrawal of energy.

+

 

+

This constructs the expression \(\text{injection}_t, \forall t \in T\) that is utilized in node.con.nodalbalance. Core components (Connections, Profiles, and Units) that feed energy into this node add to it, all others subtract from it. A stateless node forces this nodal balance to always equal 0 which essentially describes “generation = demand”.

+
+
+
+
+

Variables

+
+

pf_theta

+ +

Full implementation and all details: node/var_pf_theta @ IESopt.jl

+
+

Construct the auxiliary phase angle variable for the linear_angle power flow algorithm.

+

 

+

This needs the global Powerflow addon, configured with mode: linear_angle, and constructs a variable var_pf_theta for each Snapshot. If the pf_slack property of this Node is set to true, it does not add a variable but sets var_pf_theta[t] = 0 for each Snapshot. ```

+
+
+
+

state

+ +

Full implementation and all details: node/var_state @ IESopt.jl

+
+

Add the variable representing the state of this node to the model, if node.has_state == true. This can be accessed via node.var.state[t].

+

 

+

Additionally, if the state’s initial value is specified via state_initial the following gets added:

+

 

+
+
+\[ +\text{state}_1 = \text{state}_{initial} +\]
+
+
+
+

Constraints

+
+

last_state

+ +

Full implementation and all details: node/con_last_state @ IESopt.jl

+
+

Add the constraint defining the bounds of the node’s state during the last Snapshot to the model, if node.has_state == true.

+

 

+

This is necessary since it could otherwise happen, that the state following the last Snapshot is actually not feasible (e.g. we could charge a storage by more than it’s state allows for). The equations are based on the construction of the overall state variable.

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{state}_{end} \cdot \text{factor}^\omega_t + \text{injection}_{end} \cdot \omega_t \geq \text{state}_{lb} \\ +& \text{state}_{end} \cdot \text{factor}^\omega_t + \text{injection}_{end} \cdot \omega_t \leq \text{state}_{ub} +\end{aligned} +\end{split}\]
+
+

 

+

Here \(\omega_t\) is the weight of Snapshot t, and \(\text{factor}\) is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise.

+

 

+
+
+

Constraint safety

+

The lower and upper bound constraint are subject to penalized slacks.

+
+
+
+

nodalbalance

+ +

Full implementation and all details: node/con_nodalbalance @ IESopt.jl

+
+

Add the constraint describing the nodal balance to the model.

+

 

+

Depending on whether the node is stateful or not, this constructs different representations:

+

 

+

if node.has_state == true

+
+
+\[\begin{split} +\begin{aligned} +& \text{state}_t = \text{state}_{t-1} \cdot \text{factor}^\omega_{t-1} + \text{injection}_{t-1} \cdot \omega_{t-1}, \qquad \forall t \in T \setminus \{1\} \\ +\\ +& \text{state}_1 = \text{state}_{end} \cdot \text{factor}^\omega_{end} + \text{injection}_{end} \cdot \omega_{end} +\end{aligned} +\end{split}\]
+
+

 

+

 

+

Here \(\omega_t\) is the weight of Snapshot t, and \(\text{factor}\) is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise. \(\text{injection}_{t}\) describes the overall injection (all feed-ins minus all withdrawals). \(end\) indicates the last snapshot in \(T\). Depending on the setting of state_cyclic the second constraint is written as \(=\) ("eq") or \(\leq\) ("leq"). The latter allows the destruction of excess energy at the end of the total time period to help with feasibility.

+

 

+

if node.has_state == false

+
+
+\[\begin{split} +\begin{aligned} +& \text{injection}_{t} = 0, \qquad \forall t \in T \\ +\end{aligned} +\end{split}\]
+
+

 

+

 

+

This equation can further be configured using the nodal_balance parameter, which accepts enforce (resulting in \(=\)), create (resulting in \(\leq\); allowing the creation of energy - or “negative injections”), and destroy ( resulting in \(\geq\); allowing the destruction of energy - or “positive injections”). This can be used to model some form of energy that can either be sold (using a destroy Profile connected to this Node), or “wasted into the air” using the destroy setting of this Node.

+
+
+
+

state_bounds

+ +

Full implementation and all details: node/con_state_bounds @ IESopt.jl

+
+

Add the constraint defining the bounds of the node’s state to the model, if node.has_state == true.

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{state}_t \geq \text{state}_{lb}, \qquad \forall t \in T \\ +& \text{state}_t \leq \text{state}_{ub}, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+
+
+

Constraint safety

+

The lower and upper bound constraint are subject to penalized slacks.

+
+
+
+
+

Objectives

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/core/profile.html b/pages/manual/yaml/core/profile.html new file mode 100644 index 0000000..deb5b9b --- /dev/null +++ b/pages/manual/yaml/core/profile.html @@ -0,0 +1,564 @@ + + + + + + + + + +Profile | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Profile

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

A Profile allows representing “model boundaries” - parts of initial problem that are not endogenously modelled - with a support for time series data. Examples are hydro reservoir inflows, electricity demand, importing gas, and so on. Besides modelling fixed profiles, they also allow different ways to modify the value endogenously.

+ +
+
+

Parameters

+
+

carrier

+

Carrier of this Profile. Must match the Carrier of the Node that this connects to.

+
    +
  • mandatory: yes

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

value

+

The concrete value of this Profile - either static or as time series. Only applicable if mode: fixed.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: numeric, col@file

  • +
  • unit: power

  • +
+
+
+

node_from

+

Name of the Node that this Profile draws energy from. Exactly one of node_from and node_to must be set.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

node_to

+

Name of the Node that this Profile feeds energy to. Exactly one of node_from and node_to must be set.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

mode

+

The mode of operation of this Profile. fixed uses the supplied value, ranged allows ranging between lb and ub, while create (must specify node_to) and destroy (must specify node_from) handle arbitrary energy flows that are bounded from below by 0. Use fixed if you want to fix the value of the Profile to a specific value, e.g., a given energy demand. Use create to “import” energy into the model, e.g., from a not explicitly modelled gas market, indcucing a certain cost for buying that energy. Use destroy to “export” energy from the model, e.g., to handle CO2 going into the atmosphere (which may be taxed, etc., by the cost of this Profile). Use ranged if you need more fine grained control over the value of the Profile, than what create and destroy allow (e.g., a grid limited energy supplier).

+
    +
  • mandatory: no

  • +
  • default: \(fixed\)

  • +
  • values: -

  • +
  • unit: -

  • +
+
+
+

lb

+

The lower bound of the range of this Profile (must be used together with mode: ranged).

+
    +
  • mandatory: no

  • +
  • default: \(-\infty\)

  • +
  • values: numeric

  • +
  • unit: power

  • +
+
+
+

ub

+

The upper bound of the range of this Profile (must be used together with mode: ranged).

+
    +
  • mandatory: no

  • +
  • default: \(+\infty\)

  • +
  • values: numeric

  • +
  • unit: power

  • +
+
+
+

cost

+

Cost per unit of energy that this Profile injects or withdraws from a Node. Refer to the basic examples to see how this can be combined with mode for different use cases.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: monetary per energy

  • +
+
+
+

build_priority

+

Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+
+

Detailed reference

+
+

Expressions

+
+

value

+ +

Full implementation and all details: profile/exp_value @ IESopt.jl

+
+

Construct the JuMP.AffExpr that keeps the total value of this Profile for each Snapshot.

+

 

+

This is skipped if the value of this Profile is handled by an Expression. Otherwise it is initialized based on profile.value.

+
+
+
+
+

Variables

+
+

aux_value

+ +

Full implementation and all details: profile/var_aux_value @ IESopt.jl

+
+

Add the variable that is used in this Profiles value to the model.

+

 

+

The variable var_value[t] is constructed and is linked to the correct Nodes. There are different ways, IESopt interprets this, based on the setting of profile.mode:

+

 

+
    +
  1. fixed: The value is already handled by the constant term of profile.exp.value and NO variable is constructed.

  2. +
  3. create, destroy, or ranged: This models the creation or destruction of energy - used mainly to represent model boundaries, and energy that comes into the model or leaves the model’s scope. It is however important that create should mostly be used feeding into a Node (profile.node_from = nothing) and destroy withdrawing from a Node (profile.node_to = nothing). If lb and ub are defined, ranged can be used that allows a more detailed control over the Profile, specifying upper and lower bounds for every Snapshot. See _profile_con_value_bounds!(profile::Profile) for details on the specific bounds for each case.

  4. +
+

 

+

This variable is added to the profile.exp.value. Additionally, the energy (that profile.exp.value represents) gets “injected” at the Nodes that the profile is connected to, resulting in

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{profile.node}_{from}\text{.injection}_t = \text{profile.node}_{from}\text{.injection}_t - \text{value}_t, \qquad \forall t \in T \\ +& \text{profile.node}_{to}\text{.injection}_t = \text{profile.node}_{to}\text{.injection}_t + \text{value}_t, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+
+
+

Constraints

+
+

value_bounds

+ +

Full implementation and all details: profile/con_value_bounds @ IESopt.jl

+
+

Add the constraint defining the bounds of this profile to the model.

+

 

+

This heavily depends on the mode setting, as it does nothing if the mode is set to fixed, or the value is actually controlled by an Expression. The variable can be accessed via profile.var.aux_value[t], but using the normal result extraction is recommended, since that properly handles the profile.exp.value instead.

+

 

+

Otherwise:

+

 

+

if profile.mode === :create or profile.mode === :destroy

+
+
+\[ +\begin{aligned} +& \text{aux_value}_t \geq 0, \qquad \forall t \in T +\end{aligned} +\]
+
+

 

+

 

+

if profile.mode === :ranged

+
+
+\[\begin{split} +\begin{aligned} +& \text{value}_t \geq \text{lb}_t, \qquad \forall t \in T \\ +& \text{value}_t \leq \text{ub}_t, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+

 

+

Here, lb and ub can be left empty, which drops the respective constraint.

+
+
+
+
+

Objectives

+
+

cost

+ +

Full implementation and all details: profile/obj_cost @ IESopt.jl

+
+

Add the (potential) cost of this Profile to the global objective function.

+

 

+

The profile.cost setting specifies a potential cost for the creation (“resource costs”, i.e. importing gas into the model) or destruction (“penalties”, i.e. costs linked to the emission of CO2). It can have a unique value for every Snapshot, i.e. allowing to model a time-varying gas price throughout the year.

+

 

+

The contribution to the global objective function is as follows:

+

 

+
+
+\[ +\sum_{t\in T} \text{value}_t \cdot \text{profile.cost}_t \cdot \omega_t +\]
+
+

 

+

Here \(\omega_t\) is the weight of Snapshot t, and \(\text{value}_t\) actually refers to the value of profile.exp.value[t] (and not only on the maybe non-existing variable).

+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/core/unit.html b/pages/manual/yaml/core/unit.html new file mode 100644 index 0000000..7581f92 --- /dev/null +++ b/pages/manual/yaml/core/unit.html @@ -0,0 +1,1043 @@ + + + + + + + + + +Unit | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Unit

+
+

Note

+

This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).

+

If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.

+
+
+

Overview

+

A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.

+ +
+
+

Parameters

+
+

conversion

+

The conversion expression describing how this Unit transforms energy. Specified in the form of “\(\alpha \cdot carrier_1 + \beta \cdot carrier_2\) -> \(\gamma \cdot carrier_3 + \delta \cdot carrier_4\)”. Coefficients allow simple numerical calculations, but are not allowed to include spaces (so e.g. (1.0/9.0) is valid). Coefficients are allowed to be NumericalInputs, resulting in column@data_file being a valid coefficient (this can be used e.g. for time-varying COPs of heatpumps).

+
    +
  • mandatory: yes

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

capacity

+

Maximum capacity of this Unit, to be given in the format X in/out:carrier where X is the amount, in or out (followed by :) specifies whether the limit is to be placed on the in- our output of this Unit, and carrier specifies the respective Carrier. Example: 100 in:electricity (to limit the “input rating”).

+
    +
  • mandatory: yes

  • +
  • default: \(-\)

  • +
  • values: value dir:carrier

  • +
  • unit: -

  • +
+
+
+

outputs

+

Dictionary specifying the output “ports” of this Unit. Refer to the basic examples for the general syntax.

+
    +
  • mandatory: yes

  • +
  • default: \(-\)

  • +
  • values: dict

  • +
  • unit: -

  • +
+
+
+

inputs

+

Dictionary specifying the input “ports” of this Unit. If not specified (= no explicit input), the conversion has to follow the form of conversion: ~ -> ..., indicating an “open” input. This may, e.g., be used for renewable energy sources, where the primary energy input (e.g., solar) is not explicitly modeled.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: dict

  • +
  • unit: -

  • +
+
+
+

availability

+

Time series (or fixed value) that limits the available capacity. If, e.g., capacity: 100 out:electricity and availability: 70, the available capacity will only be 70 electricity. Can be used to model non-availability of power plants, e.g., due to maintenance. For time-varying availability of intermittent generators (e.g., wind), it’s recommended (most of the time) to use availability_factor instead.

+
    +
  • mandatory: no

  • +
  • default: \(+\infty\)

  • +
  • values: numeric

  • +
  • unit: power

  • +
+
+
+

availability_factor

+

Similar to availability, but given as factor of capacity instead. If, e.g., capacity: 100 out:electricity and availability_factor: 0.7, the available capacity will only be 70 electricity. This is especially useful for intermittent generators, where the availability is not a fixed value, but depends on the weather, and can be passed, e.g., by setting availability_factor: wind@input_data_file.

+
    +
  • mandatory: no

  • +
  • default: \(1\)

  • +
  • values: \in [0, 1]

  • +
  • unit: -

  • +
+
+
+

adapt_min_to_availability

+

If true, the minimal partial load will be influenced by the availability. Example: Consider a Unit with capacity: 100 out:electricity, a min_conversion of 0.4, and an availability_factor of 0.5. This entails having 50 electricity available, while the minimal partial load is 40 electricity. This results in the Unit at best operating only closely above the minimal partial load. Furthermore, an availability_factor below 0.4 would result in no feasible generation, besides shutting the Unit off. While this might be the intended mode of operation in many use cases, adapt_min_to_availability can change this: If set to true, this dynamically changes the minimal partial load. In the previous example, that means (100 * 0.5) * 0.4 = 20 electricity (the 50% minimum load are now based on the available 40), changing the overall behaviour (including efficiencies) as well as leading to feasible generations even when the availability_factor is below 0.4.

+
    +
  • mandatory: no

  • +
  • default: \(false\)

  • +
  • values: true, false

  • +
  • unit: -

  • +
+
+
+

marginal_cost

+

Marginal cost of the consumption/generation of one unit of energy of the specified carrier. Has to be given in the format value per dir:carrier, e.g. 3.5 per out:electricity for a marginal cost of 3.5 monetary units per unit of electricity generated.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: value per dir:carrier

  • +
  • unit: monetary per energy

  • +
+
+
+

enable_ramp_up

+

Enables calculation of upward ramps. Ramping is based on the carrier specified in capacity.

+
    +
  • mandatory: no

  • +
  • default: \(false\)

  • +
  • values: true, false

  • +
  • unit: -

  • +
+
+
+

enable_ramp_down

+

Enables calculation of downward ramps. Ramping is based on the carrier specified in capacity.

+
    +
  • mandatory: no

  • +
  • default: \(false\)

  • +
  • values: true, false

  • +
  • unit: -

  • +
+
+
+

ramp_up_cost

+

Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: monetary per power

  • +
+
+
+

ramp_down_cost

+

Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: monetary per power

  • +
+
+
+

ramp_up_limit

+

Limits the allowed ramping up based on this factor of the total capacity. If capacity: 100 in:electricity with ramp_up_limit: 0.2, this limits the total increase of usage of electricity (on the input) to 20 units (power) per hour. For example, starting at an input of 35, after one hour the input has to be lesser than or equal to 55. If a Snapshot’s duration is set to, e.g., two hours, this would allow a total increase of 40 units.

+
    +
  • mandatory: no

  • +
  • default: \(1\)

  • +
  • values: \in [0, 1]

  • +
  • unit: -

  • +
+
+
+

ramp_down_limit

+

Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.

+
    +
  • mandatory: no

  • +
  • default: \(1\)

  • +
  • values: \in [0, 1]

  • +
  • unit: -

  • +
+
+
+

min_on_time

+

Minimum on-time of the Unit. If set, the Unit has to be on for at least this amount of time, after turning on. It is highly recommended to only use this with unit_commitment: binary, unless you know why it’s fine to use with another mode.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: hours

  • +
+
+
+

min_off_time

+

Minimum off-time of the Unit. If set, the Unit has to be off for at least this amount of time, after turning off. It is highly recommended to only use this with unit_commitment: binary, unless you know why it’s fine to use with another mode.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: hours

  • +
+
+
+

on_time_before

+

Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: hours

  • +
+
+
+

off_time_before

+

Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: hours

  • +
+
+
+

is_on_before

+

Number of Units that should be considered to have been running before the optimization starts. Can be used in combination with on_time_before, especially for unit_count greater than 1.

+
    +
  • mandatory: no

  • +
  • default: \(1\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+

unit_commitment

+

Controls how the unit commitment of this Unit is handled. linear results in the ability to startup parts of the unit (so 0.314159 is a feasible amount of “turned on unit”), while binary restricts the Unit to either be on (converting the conversion_at_min + possible additional conversion above that minimum) or off (converting nothing); integer is needed to consider binary unit commitment for Units with more than 1 “grouped unit” (see unit_count).

+
    +
  • mandatory: no

  • +
  • default: \(off\)

  • +
  • values: off, linear, binary, integer

  • +
  • unit: -

  • +
+
+
+

unit_count

+

Number of units aggregated in this Unit. Besides interacting with the mode of unit_commitment, this mainly is responsible for scaling the output (e.g. grouping 47 of the same wind turbine, …).

+
    +
  • mandatory: no

  • +
  • default: \(1\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+

min_conversion

+

If unit_commitment is not set to off, this specifies the percentage that is considered to be the minimal feasible partial load this Unit can operate at. Operating below that setpoint is not allowed, at that point the conversion_at_min coefficients are used, and above that they are scaled to result in conversion when running at full capacity.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: \in [0, 1]

  • +
  • unit: -

  • +
+
+
+

conversion_at_min

+

The conversion expression while running on the minimal partial load. Only applicable if unit_commitment is not off and min_conversion is explicitly set. Follows the same form as conversion.

+
    +
  • mandatory: no

  • +
  • default: \(-\)

  • +
  • values: string

  • +
  • unit: -

  • +
+
+
+

startup_cost

+

Costs per startup (also applicable if startups are not binary or integer). This is necessary to allow conversion_at_min to have (at least partially) the effect that one expects, if unit_commitment: linear.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: monetary per start

  • +
+
+
+

build_priority

+

Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others.

+
    +
  • mandatory: no

  • +
  • default: \(0\)

  • +
  • values: numeric

  • +
  • unit: -

  • +
+
+
+
+

Detailed reference

+
+

Expressions

+
+
+

Variables

+
+

conversion

+ +

Full implementation and all details: unit/var_conversion @ IESopt.jl

+
+

Add the variable describing the unit’s conversion to the model.

+

 

+

This can be accessed via unit.var.conversion[t]; this does not describe the full output of the Unit since that maybe also include fixed generation based on the ison variable.

+

 

+
+
+
+

ison

+ +

Full implementation and all details: unit/var_ison @ IESopt.jl

+
+

Add the variable describing the current “online” state of the unit to the model.

+

 

+

The variable can be further parameterized using the unit.unit_commitment setting (“linear”, “binary”, “integer”). It will automatically enforce the constraints \(0 \leq \text{ison} \leq \text{unitcount}\), with \(\text{unitcount}\) describing the number of units that are aggregated in this unit (set by unit.unit_count). This can be accessed via unit.var.ison[t].

+
+
+
+

ramp

+ +

Full implementation and all details: unit/var_ramp @ IESopt.jl

+
+

Add the variable describing the per-snapshot ramping to the model.

+

 

+

This adds two variables per snapshot to the model (if the respective setting unit.enable_ramp_up or unit.enable_ramp_down is activated). Both are preconstructed with a fixed lower bound of 0. This describes the amount of change in conversion that occurs during the current snapshot. These can be accessed via unit.var.ramp_up[t] and unit.var.ramp_down[t].

+

 

+

These variables are only used for ramping costs. The limits are enforced directly on the conversion, which means this variable only exists if costs are specified!

+
+
+
+

startup

+ +

Full implementation and all details: unit/var_startup @ IESopt.jl

+
+

Add the variable describing the per-snapshot startup to the model.

+

 

+

This adds a variable per snapshot to the model (if the respective setting unit.unit_commitment is activated). The variable can be further parameterized using the unit.unit_commitment setting (“linear”, “binary”, “integer”). It will automatically enforce the constraints \(0 \leq \text{startup} \leq \text{unitcount}\), with \(\text{unitcount}\) describing the number of units that are aggregated in this unit (set by unit.unit_count). This describes the startup that happens during the current snapshot and can be accessed via unit.var.startup.

+
+
+
+
+

Constraints

+
+

conversion_bounds

+ +

Full implementation and all details: unit/con_conversion_bounds @ IESopt.jl

+
+

Add the constraint defining the unit’s conversion bounds to the model.

+

 

+

This makes use of the current min_capacity (describing the lower limit of conversion; either 0 if no minimum load applies or the respective value of the minimum load) as well as the online_capacity (that can either be the full capacity if unit commitment is disabled, or the amount that is currently active).

+

 

+

Depending on how the “availability” of this unit is handled it constructs the following constraints:

+

 

+

if !isnothing(unit.availability)

+
+
+\[\begin{split} +\begin{aligned} +& \text{conversion}_t \geq \text{capacity}_{\text{min}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{capacity}_{\text{online}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{availability}_t, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+

 

+

This effectively results in \(\text{conversion}_t \leq \min(\text{capacity}_{\text{online}, t}, \text{availability}_t)\).

+

 

+

if !isnothing(unit.availability_factor)

+
+
+\[\begin{split} +\begin{aligned} +& \text{conversion}_t \geq \text{capacity}_{\text{min}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{capacity}_{\text{online}, t} \cdot \text{availability}_{\text{factor}, t}, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+

 

+
+
+

If no kind of availability limiting takes place, the following bounds are enforced:

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{conversion}_t \geq \text{capacity}_{\text{min}, t}, \qquad \forall t \in T \\ +& \text{conversion}_t \leq \text{capacity}_{\text{online}, t}, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+
+

ison

+ +

Full implementation and all details: unit/con_ison @ IESopt.jl

+
+

Construct the upper bound for var_ison, based on unit.unit_count, if it is handled by an external Decision.

+
+
+
+

min_onoff_time

+ +

Full implementation and all details: unit/con_min_onoff_time @ IESopt.jl

+
+

Add the constraints modeling min on- or off-time of a Unit to the model.

+

 

+

This constructs the constraints

+

 

+
+
+\[\begin{split} +\begin{align} +& \sum_{t' = t}^{t + \text{min\_on\_time}} ison_{t'} >= \text{min\_on\_time} \cdot (ison_t - ison_{t-1}) \qquad \forall t \in T \\ +& \sum_{t' = t}^{t + \text{min\_off\_time}} (1 - ison_{t'}) >= \text{min\_off\_time} \cdot (ison_{t-1} - ison_t) \qquad \forall t \in T +\end{align} +\end{split}\]
+
+

 

+

respecting on_time_before and off_time_before, and is_on_before. See the code for more details.

+

 

+
+
+

Aggregated units

+

This is currently not fully adapted to account for Units with unit_count > 1.

+
+
+
+

ramp

+ +

Full implementation and all details: unit/con_ramp @ IESopt.jl

+
+

Add the auxiliary constraint that enables calculation of per snapshot ramping to the model.

+

 

+

Depending on whether ramps are enabled, none, one, or both of the following constraints are constructed:

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{ramp}_{\text{up}, t} \geq \text{conversion}_{t} - \text{conversion}_{t-1}, \qquad \forall t \in T \\ +& \text{ramp}_{\text{down}, t} \geq \text{conversion}_{t-1} - \text{conversion}_{t}, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+

This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if:

+

 

+
    +
  • out[5] = 100 and out[4] = 50, then ramp_up[5] = 50 and ramp_down[5] = 0

  • +
  • ramp_up[1] = ramp_down[1] = 0

  • +
+

 

+
+
+
+

ramp_limit

+ +

Full implementation and all details: unit/con_ramp_limit @ IESopt.jl

+
+

Add the constraint describing the ramping limits of this unit to the model.

+

 

+

This makes use of the maximum capacity of the unit, which is just the total installed capacity. Both, up- and downwards ramps can be enabled separately (via unit.ramp_up_limit and unit.ramp_down_limit), resulting in either or both of:

+

 

+
+
+\[\begin{split} +\begin{aligned} +& \text{ramp}_{\text{up}, t} \leq \text{ramplimit}_\text{up} \cdot \text{capacity}_\text{max} \cdot \omega_t, \qquad \forall t \in T \\ +& \text{ramp}_{\text{down}, t} \leq \text{ramplimit}_\text{down} \cdot \text{capacity}_\text{max} \cdot \omega_t, \qquad \forall t \in T +\end{aligned} +\end{split}\]
+
+

 

+

This does not make use of the ramping variable (that is only used for costs - if there are costs).

+

 

+

This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if:

+

 

+
    +
  • out[5] = 100 and out[4] = 50, then ramp_up[5] = 50 and ramp_down[5] = 0

  • +
  • ramp_up[1] = ramp_down[1] = 0

  • +
+
+
+
+

startup

+ +

Full implementation and all details: unit/con_startup @ IESopt.jl

+
+

Add the auxiliary constraint that enables calculation of per snapshot startup to the model.

+

 

+

Depending on whether startup handling is enabled, the following constraint is constructed:

+

 

+
+
+\[ +\begin{aligned} +& \text{startup}_{\text{up}, t} \geq \text{ison}_{t} - \text{ison}_{t-1}, \qquad \forall t \in T +\end{aligned} +\]
+
+

 

+

This calculates the startup that happens from the PREVIOUS snapshot to this one. That means that if:

+

 

+
    +
  • ison[5] = 1 and ison[4] = 0, then startup[5] = 1

  • +
+
+
+
+
+

Objectives

+
+

marginal_cost

+ +

Full implementation and all details: unit/obj_marginal_cost @ IESopt.jl

+
+

Add the (potential) cost of this unit’s conversion (unit.marginal_cost) to the global objective function.

+

 

+
+
+\[ +\sum_{t \in T} \text{conversion}_t \cdot \text{marginalcost}_t \cdot \omega_t +\]
+
+
+

ramp_cost

+ +

Full implementation and all details: unit/obj_ramp_cost @ IESopt.jl

+
+

Add the (potential) cost of this unit’s ramping to the global objective function.

+

 

+

To allow for finer control, costs of up- and downwards ramping can be specified separately (using unit.ramp_up_cost and unit.ramp_down_cost):

+

 

+
+
+\[ +\sum_{t \in T} \text{ramp}_{\text{up}, t} \cdot \text{rampcost}_{\text{up}} + \text{ramp}_{\text{down}, t} \cdot \text{rampcost}_{\text{down}} +\]
+
+
+

startup_cost

+ +

Full implementation and all details: unit/obj_startup_cost @ IESopt.jl

+
+

Add the (potential) cost of this unit’s startup behaviour (configured by unit.startup_cost if unit.unit_commitment != :off).

+

 

+
+
+\[ +\sum_{t \in T} \text{startup}_t \cdot \text{startupcost} +\]
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/core_components.html b/pages/manual/yaml/core_components.html new file mode 100644 index 0000000..8cac6d0 --- /dev/null +++ b/pages/manual/yaml/core_components.html @@ -0,0 +1,227 @@ + + + + + + + + + +Components | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Components

+
    +
  • A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, …

  • +
  • A Decision represents a basic decision variable in the model that can be used as input for various other core component’s settings, as well as have associated costs.

  • +
  • A Node represents a basic intersection/hub for energy flows. This can for example be some sort of bus (for electrical systems). It enforces a nodal balance equation (= “energy that flows into it must flow out”) for every Snapshot. Enabling the internal state of the Node allows it to act as energy storage, modifying the nodal balance equation. This allows using Nodes for various storage tasks (like batteries, hydro reservoirs, heat storages, …).

  • +
  • A Profile allows representing “model boundaries” - parts of initial problem that are not endogenously modelled - with a support for time series data. Examples are hydro reservoir inflows, electricity demand, importing gas, and so on. Besides modelling fixed Profiless, they also allow different ways to modify the value endogenously.

  • +
  • A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.

  • +
+
+

Tip

+

There exists a further type, called Virtual, that works almost exactly the same as any other core component - but is only a virtual placeholder for non-existing components of your model. These are ones that a user might expect to exist, but are removed due to flattening the model topology: Each template that gets instantiated is dropped from the model and replaced by the components that it actually constructs. To allow accessing these components, Virtual components are used.

+

Their usage, and various internal details, are rather advanced and currently not fully documented.

+
+ +
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/index.html b/pages/manual/yaml/index.html new file mode 100644 index 0000000..75605b9 --- /dev/null +++ b/pages/manual/yaml/index.html @@ -0,0 +1,294 @@ + + + + + + + + + +YAML | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

YAML

+

Most configuration settings for the IESopt optimization model are stored in YAML files. This section provides a reference for the YAML syntax and the specific settings used in IESopt.

+
+

Yet another markup language?

+

YAML - which actually stands for YAML Ain’t Markup Language - is a human-readable data serialization language. It is often used for configuration files and data exchange between languages. YAML is a superset of JSON, meaning that any valid JSON file is also a valid YAML file.

+

You can find more information at:

+ +
+
+

Good to know

+

The following points offer hints, notes, or recommendations for working with YAML files in IESopt:

+
    +
  • Indentation: YAML files make use of indentation: The recommended indentation is two spaces.

  • +
  • Comments: Comments in YAML files are preceded by a # character.

  • +
  • Dictionaries: YAML files are often used to define dictionaries. In Python (and Julia), dictionaries are/were unordered[1], and the order of (dictionary) entries are therefore not considered to have any specific order. However, the order given in the documentation is recommended for readability.

  • +
+
+

Danger

+

Since we are using dictionaries to store most configurations inside the YAML files, be aware that keys have to be unique. No key can exist twice in the same dictionary - this means, e.g., that you cannot use two components with the same name. However, depending on the implementation of the YAML parser, duplicate keys might not raise an error: After loading a YAML file IESopt will only be able to see the last value of a duplicate key, without any way to detect that ambiguity.

+

This is a common source of errors, so be careful!

+
+
+
+

Contents

+

See the documentation of the built-in YAML here:

+ +
+
+
+ +
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/manual/yaml/top_level.html b/pages/manual/yaml/top_level.html new file mode 100644 index 0000000..476a58e --- /dev/null +++ b/pages/manual/yaml/top_level.html @@ -0,0 +1,872 @@ + + + + + + + + + +Top-level config | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Top-level config

+

The top-level YAML configuration file, often named config.iesopt.yaml, is the main configuration file for the IESopt optimization model. It may contain the following main sections:

+
    +
  1. parameters (optional): General global parameters.

  2. +
  3. config: General configuration settings for the optimization model.

  4. +
  5. addons (optional): Additional settings for specific addons.

  6. +
  7. carriers: Definition of all energy carriers.

  8. +
  9. components (and/or load_components): Definitions of core components.

  10. +
+
+

Note

+

As mentioned in the intro section of the YAML reference, dictionaries are considered to not guarantee order. Therefore, the order of the sections in the YAML file is not important - however, the order given above is recommended for readability.

+
+

This means a basic top-level configuration file could look like this:

+
+
Rough outline of a simple config.iesopt.yaml.
+
config:
+  optimization:
+    problem_type: LP
+    snapshots:
+      count: 24
+
+carriers:
+  electricity: {}
+
+components:
+  main_grid_node:
+    type: Node
+    carrier: electricity
+
+
+
+
+

parameters

+

General global parameters, that the whole model can access. Can be set from outside when, e.g., when calling iesopt.run().

+
+

Note

+

This section is optional and only needed if you want to global model parameters.

+
+

The base approach is passing a dictionary, with all global parameters and their default values:

+
+
Example for a dict-based parameters section.
+
parameters:
+  ng_emission_factor: 0.202
+  co2_cost: 125
+  elec_cost: null
+
+
+
+

Here elec_cost defaults to null - which is the equivalent of None (Python) or nothing (Julia). Either it is used internally for some setting that accepts null as a valid value, or it is meant to be set from outside.

+

However, with a growing number of parameters, it might be more convenient to store them in a separate file. This can be done by passing the path to the file:

+
+
Example for file-based parameters section.
+
parameters: global_params_scenarioHIGH.iesopt.param.yaml
+
+
+
+

The file global_params_scenarioHIGH.iesopt.param.yaml could then look like this:

+
+
Example content of global_params_scenarioHIGH.iesopt.param.yaml.
+
ng_emission_factor: 0.202  # t CO2 / MWh_ng
+co2_cost: 125              # EUR / t CO2
+elec_cost: null            # EUR / MWh_el
+
+
+
+
+

Hint

+

Make sure to use comments to better explain the parameters and their units, and structure the file in a way that makes it easy to read and understand. Note: The same is true for the dictionary approach - use comments! - but the file approach is more likely to be used with a large number of parameters.

+
+
+
+

config

+

General configuration settings for the optimization model.

+
+
Example for the config section in the top-level YAML configuration file.
+
config:
+  general:
+    version:
+      core: 1.1.0
+      python: 1.4.7
+    name:
+      model: FarayOptIndustry
+      scenario: Base_2022_LOW
+  optimization:
+    problem_type: LP
+    snapshots:
+      count: 168
+  files:
+    data: inputs_2022.csv
+  results:
+    enabled: true
+  paths:
+    files: data/
+    templates: templates/
+    components: model/
+    results: out/
+
+
+
+

The following subsections explain each part of the config section in more detail.

+
+

general

+

This contains general settings.

+
+

version

+

This section allows specifying various IESopt versions, which is important to ensure reproducibility and compatibility: Executing a model based on a config.iesopt.yaml file with a different version of IESopt might lead to unexpected results or errors.

+
+
Example for the version section.
+
config:
+  general:
+    version:
+      core: 1.1.0
+      python: 1.4.7
+
+
+
+

We try to stick to semantic versioning, which means that changes to the minor or patch version should not contain breaking changes, but:

+
    +
  1. A bug fix to IESopt.jl might lead to different results of your model, meaning every bug fix might be a breaking change “for you”.

  2. +
  3. We might mess up and introduce a breaking change in a minor version, without intending it.

  4. +
+

Therefore: Make sure you KNOW what changes between versions, and TEST your models when upgrading.

+
+
Parameters:
+

+
core:
+

The version of the core IESopt framework (IESopt.jl).

+
+
python:
+

The version of the Python interface (iesopt).

+
+
+
+

Tip

+

You may add arbitrary versions to this section, e.g., for personal use in specific addons or other dependencies.

+
+
+
+

name

+

This section does not directly affect the model but can be used to store the name of the model and the scenario. This can be useful for logging and debugging purposes, as well as for the results output.

+
+

Note

+

This section is optional and only needed if you want to global model parameters.

+
+
+
Parameters:
+

+
model:
+

Name of the model, e.g., a high-level description, or the name of the project.

+
+
scenario:
+

A specific scenario or case, e.g., the year, a specific set of parameters, or a version of input files.

+
+
+
+

Tip

+

The scenario setting supports dynamic placeholders. Currently, you can use $TIME$ which will automatically be replaced by the timestamp of the model being built. This way, each result will have a unique scenario name, which prevents overwriting results.

+
+

Consider the following example for the name section:

+
+
Example for the name section.
+
config:
+  general:
+    name:
+      model: FarayOptIndustry
+      scenario: Base_2022_LOW_T-$TIME$
+
+
+
+

When running the model containing this configuration, the results will be stored in a folder structure that looks like this:

+
+
Example folder structure for the results of the model run.
+
.
+├── data/
+│   └── ...
+├── out/
+│   ├── FarayOptIndustry/
+│   │   ├── Base_2022_LOW_T-2024_09_11_09520837.iesopt.result.jld2
+│   │   ├── Base_2022_LOW_T-2024_09_11_09520837.iesopt.log
+│   │   ├── Base_2022_LOW_T-2024_09_11_09520837.highs.log
+│   │   └── ...
+│   └── FarayOptIndustry_Baseline/
+│       └── ...
+└── config.iesopt.yaml
+
+
+
+

In this example, the $TIME$ placeholder was replaced by the current timestamp, which is 2024_09_11_09520837 in this case. You can see the top-level config file (config.iesopt.yaml), a data folder (data/; see the files and paths sections), the results folder (out/; see the results and paths sections). Inside the results folder, IESopt creates a folder for each “model name”. Each executed run creates its result files inside that, using the “scenario name” as base filename.

+
+
+

verbosity

+

Controls the verbosity of various parts of a model run.

+
+
Parameters:
+

+
core (str, default = info):
+

Verbosity of the (Julia) core, IESopt.jl. Supports: debug, info (default), warning, error.

+
+
progress (str):
+

Whether to show progress bars (on or off), defaults to on unless core is set to error - in that case it defaults to off.

+
+
python (str):
+

Verbosity of the Python wrapper, iesopt. Supports: debug, info, warning, error. Defaults to the verbosity set in core.

+
+
solver (str):
+

Whether to silence solver prints/outputs (on or off), defaults to on unless core is set to error - in that case it defaults to off.

+
+
+
+
+

performance

+

Controls various “performance” related settings of the model.

+
+
Parameters:
+

+
string_names (bool, default = true):
+

Activates (or deactivates) “string names” for expressions, variables, and constraints. These make the model readable for humans, but cost (a lot of) performance. Refer to the JuMP.jl documentation and its set_string_names_on_creation function, or read upon how this upstream functionality originated from a discussion during the early stages of IESopt development (discourse.julialang, this issue, and some time later this PR).

+
+
logfile (bool, default = true):
+

Controls whether or not to write an IESopt logfile (which captures everything even if verbosity prevents displaying it). While this does save a bit of time it also prevents tracking down errors, so disable it wisely. This is most important to disable in iterative solves of small models, where logging accounts for a larger portion of overall time.

+
+
force_addon_reload (bool, default = true):
+

If set to false, addons that have already been loaded once will not be reloaded. This can help with repeated solves of the same model. Be aware, that this might interfere with reloading changes to an addon’s code that you have made, so unless you need it (and understand it) it is best left as true.

+
+
+
+
Example for the performance section.
+
config:
+  general:
+    performance:
+      string_names: false
+      logfile: true
+
+
+
+
+
+
+

optimization

+
+

problem_type

+
+
Options:
+

+
LP:
+

Restricted to (continuous) linear programming formulations.

+
+
MILP:
+

Enables mixed-integer linear programming formulations.

+
+
MO:
+

Enables multi-objective optimization formulations.

+
+
MGA:
+

Enables modelling-to-generate-alternatives formulations.

+
+
SDDP:
+

Enables stochastic dual dynamic programming formulations.

+
+
+

Options can be combined using + (where applicable), e.g., LP+MO.

+
+
Example for the problem_type section.
+
config:
+  optimization:
+    problem_type: LP
+
+
+
+
+

Note

+

The options MO, MGA, and SDDP represent very advanced functionality. These are not fully documented, or may be partially broken in any given release. If you are interested in these features, please reach out to the developers.

+
+
+
+

solver

+
+
Parameters:
+

+
name (str, default = highs):
+

The solver to use for the optimization problem. If not set, the default solver (currently HiGHS) is used.

+
+
log (bool, default = true):
+

Whether to log the solver output to a file.

+
+
attributes (dict, optional):
+

Additional attributes to pass to the solver.

+
+
+
+
Example for the solver section.
+
config:
+  optimization:
+    solver:
+      name: highs
+      log: false
+      attributes:
+        solver: ipm
+
+
+
+
+
+

snapshots

+

Here we define the Snapshots (IESopt’s representation of time steps, including auxiliary information).

+
+
Parameters:
+

+
count (int):
+

The number of Snapshots to use in the optimization.

+
+
names (str, optional):
+

Linked column of a loaded file (using the usual col@file syntax) that contains the names of the Snapshots. These are purely for aesthetic purposes (e.g., result data frames) and are not used in the optimization.

+
+
weights (float, default = 1):
+

The duration of each Snapshot in hours. Values below 1 are interpreted as fractions of an hour, e.g., 0.25 for 15 minutes.

+
+
+
+
Example for the snapshots section.
+
config:
+  optimization:
+    snapshots:
+      count: 24
+      weights: 4
+
+
+
+
+
+

objectives

+
+

Caution

+

This setting is part of advanced functionality. It is not fully documented, or may be partially broken in any given release. If you are interested in this feature, please reach out to the developers.

+
+

This allows defining custom objective expressions for the optimization problem. This can be useful if you want to optimize for a specific objective that is not directly supported by IESopt. Per default, IESopt is optimizing the only base objective expression, which is the model’s total cost (called total_cost).

+
+
Example for the objectives section.
+
config:
+  optimization:
+    objectives:
+      emissions: [co2_emissions.exp.value]
+
+
+
+

The above constructs a new objective expression that only consists of co2_emissions.exp.value. It may also be initialized empty ([]), in which case you can add terms later on in the model definition.

+
+
+

multiobjective

+
+

Caution

+

This setting is part of advanced functionality. It is not fully documented, or may be partially broken in any given release. If you are interested in this feature, please reach out to the developers.

+
+
+
Parameters:
+

+
mode (str):
+

The mode to use for multi-objective optimization, currently supported modes are: EpsilonConstraint, Lexicographic, Hierarchical.

+
+
terms (list[str]):
+

A list of objectives to optimize.

+
+
settings (dict):
+

Settings for the multi-objective optimization, refer to the documentation of MultiObjectiveAlgorithms.jl for more information.

+
+
+
+
Example for the multiobjective section.
+
config:
+  optimization:
+    multiobjective:
+      mode: EpsilonConstraint
+      terms: [total_cost, emissions]
+      settings:
+        MOA.SolutionLimit: 5
+
+
+
+
+

Note

+

Refer to the related example models for more information on how to use multi-objective optimization in IESopt.

+
+
+
+

soft_constraints

+

This was called constraint_safety in all versions previous to v2.0.0. It controls if and how certain constraints of the model are relaxed to allow penalized violation of said constraints. This can be helpful, e.g., if your model is infeasible for certain external settings and you might want to ensure getting an approximate solution.

+
+
Example for the soft_constraints section.
+
config:
+  optimization:
+    soft_constraints:
+      active: true
+      penalty: 1e6
+
+
+
+
+
Parameters:
+

+
active (bool, default = false):
+

Activate the feature.

+
+
penalty (float):
+

Penalty that is used to penalize constraint violations.

+
+
+
+
+
+

files

+

This can be used to define input files that are later referenced in the configuration file. Each filename (possibly including a path) is linked to a name that can be used to reference the file later on.

+
+
Example for the files section.
+
config:
+  files:
+    data: inputs_2023_base.csv
+
+
+
+

In the above example, the file inputs_2023_base.csv is linked to the name data. This means that the file can be referenced in the configuration file using data, accessing a column - e.g., “pv_generation” - using pv_generation@data.

+
+

Note

+

Assuming that, in the above example, we call data the file’s “descriptor” and inputs_2023_base.csv the file’s “name”, then make sure that the descriptor does not start with an underscore, and further only uses alphanumeric characters and underscores (so no !, ~, or other “unexpected” special characters).

+
+
+

_csv_config

+
+

Caution

+

This feature is experimental and can change, break, or be discontinued at any time.

+
+

You can configure the behavior of the CSV reader by adding a _csv_config section to the files section.

+
+
Parameters:
+

+
comment (string, default = null):
+

If empty/not-given, no “comments” are recognized. If set to any string, all lines starting with this string are considered comments and ignored. Common options are # or ;. Make sure to properly escape the string if needed, e.g., comment: "#".

+
+
delim (char, default = ,):
+

The delimiter used in the CSV file. Common options are , (EN locale), ; (DE locale), or \t (tab-separated).

+
+
decimal (char, default = .):
+

The decimal separator used in the CSV file. Common options are . (EN locale) or , (DE locale). Make sure this is set correctly in conjunction with the delim setting.

+
+
+
+
Ignore comment rows in input CSVs.
+
config:
+  files:
+    _csv_config:
+      comment: "#"
+
+
+
+
+
Switch to a format often used on German systems.
+
config:
+  files:
+    _csv_config:
+      delim: ";"
+      decimal: ","
+
+
+
+
+
+
+

results

+

Settings for the results output. Details to be added.

+
+
Parameters:
+

+
enabled (bool, default = true):
+

Whether to enable the automatic extraction of results. If this is set to false, no results will read from the solver after optimizing the model - you can however still access the solver results directly, see for example iesopt.jump_value().

+
+
memory_only (bool, default = true):
+

Whether to store the results in memory only, without writing them to disk. This can be useful if you plan to access and further process the results directly in Python or Julia, or only want to store specific results.

+
+
compress (bool, default = false):
+

Whether to compress the results when writing them to disk. This can save disk space but might increase the time needed to write and read the results. Refer to JLD2.jl for more information about compression.

+
+
include (str, default = none or nput+log):
+

A list of result extraction modes to activate, see below for more details. The default depends on the setting of memory_only: If memory_only is true, the default is none, otherwise it is input+log. You can use all to activate all possible settings.

+
+
backend (str, default = jld2):
+

Backend to use in the result extraction. Currently jld2 is the main/working one, while we are trying to improve the duckdb backend.

+
+
+
+
Example for the results section.
+
config:
+  results:
+    enabled: true
+    memory_only: true
+
+
+
+
+

Details: Result extraction modes

+

Currently the following options exist for the include setting:

+
+
input:
+

Add information about the model’s input data to the results.

+
+
git:
+

Add information about the status of any git setup that exists in the model’s folder.

+
+
log:
+

Add IESopt’s full log of the optimization run to the results.

+
+
+

These modes can be combined using +. For example, input+git will include both the input data and the git information in the results. If include is set to all, all modes are included. If include is set to none, no modes are included.

+
+
+

Note

+

Refer to the name section for information on how the model and scenario names are used in the results output.

+
+
+
+

paths

+

This section as a whole, or the individual paths, are optional - defaults will be used for those not set.

+
+
Parameters:
+

+
files (str, default = ./files/):
+

The path to the directory where input files are stored.

+
+
templates (str, default = ./templates/):
+

The path to the directory where templates are stored.

+
+
components (str, default = ./model/):
+

The path to the directory where model topology files are stored. These allow loading a large number of components at once. Refer to the components section for more information.

+
+
addons (str, default = ./addons/):
+

The path to the directory where addons (*.jl files) are stored.

+
+
results (str, default = ./out/):
+

The path to the directory where results should be stored.

+
+
+
+
Example for the paths section.
+
config:
+  paths:
+    files: data/
+    templates: templates/
+    components: model/
+    results: out/
+
+
+
+
+

Note

+

The paths are all relative to the location of the top-level configuration file.

+
+
+
+
+

addons

+

Additional settings for specific addons.

+
+
Example for the addons section in the top-level YAML configuration file.
+
addons:
+  ModifyHeatStorages:
+    alpha: 0.93
+    temperature: 60.0
+
+
+
+
+

Note

+

This section is optional and only needed if you want to use addons. The settings for the addons are specific to the addon.

+
+
+
+

carriers

+

Definition of all energy carriers.

+
+
Example for the carriers section in the top-level YAML configuration file.
+
carriers:
+  electricity: {}
+  co2: {}
+
+
+
+
+
+

components

+

This is the main section where all model components are defined (or loaded). There are two possibilities to define components: components and load_components. The former is used to define components directly in the top-level configuration file, while the latter is used to load components from a file. At least one of these sections must be present in the top-level configuration file - both can be used at the same time.

+
+
Basic example for the components section.
+
components:
+  main_grid_node:
+    type: Node
+    carrier: electricity
+
+
+
+

When using load_components, the easiest way is to pass a list of filenames. These files are then loaded from the components path (see the paths section) and the components are added to the model. Note that using components is not necessary when using load_components, and is only done here for demonstration purposes.

+
+
Advanced example, utilizing load_components.
+
load_components:
+  - units.csv
+  - nodes.csv
+  - profiles.csv
+
+components:
+  co2_emissions:
+    type: Profile
+    carrier: co2
+    mode: destroy
+    node_from: total_co2
+    cost: 100
+
+
+
+
+

Note

+

The load_components section allows more complex ways to read components from files: You can pass .csv as sole list entry to load all CSV files in the components path, or you can pass one, or multiple, regex patterns as list entries to load only specific files. For example, using

+
load_components:
+  - ^09[/\\]((?!snapshots\.csv$).)*\.csv$
+
+
+

will load all CSV files in the components path that are in a subfolder 09 except for snapshots.csv.

+

Note: It is recommended to use [/\\] as path separators, like in the regex above, so the model can work on both UNIX and Windows systems.

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/references/projects.html b/pages/references/projects.html new file mode 100644 index 0000000..5b232fd --- /dev/null +++ b/pages/references/projects.html @@ -0,0 +1,263 @@ + + + + + + + + + +Projects | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Projects

+
+

Overview

+

This maintains a list of projects where IESopt was applied as part of the modeling +approach. Entries are in alphabetical order. If you want to contribute a new project, please follow the +instructions in the section on contributing below.

+
+
+

List of references

+

To be added.

+
+
+

Contributing

+

To contribute a new reference, either

+
    +
  • fork the iesopt repository, and directly add to the above list, or

  • +
  • open an issue with the reference details.

  • +
+

See the template below for the structure of a reference.

+
+

Template

+

To be added.

+
+
+

Creating citation badges

+

You can use shields.io to create badges, or use standardized ones that you already have +(e.g., from Zenodo), otherwise stick to the ones provided below.

+

Pure: (publications.ait.ac.at)

+
+
[![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-none?style=social)](ADDYOURLINKHERE)
+
+
+
+

DOI:

+
+
[![CITATION](https://img.shields.io/badge/DOI-10.XXXX%2Fname.YYYY.ZZZZZZ-none?style=social)](https://doi.org/10.XXXX/name.YYYY.ZZZZZZ)
+
+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/references/publications.html b/pages/references/publications.html new file mode 100644 index 0000000..6dc18bb --- /dev/null +++ b/pages/references/publications.html @@ -0,0 +1,313 @@ + + + + + + + + + +Publications | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Publications

+
+

Overview

+

This maintains a list of publications (journals, conferences, …) where IESopt was applied as part of the modeling +approach. Entries are in alphabetical order. If you want to contribute a new publication or project, please follow the +instructions in the section on contributing below.

+
+
+

List of references

+
+

Heat Highway - Heat Transmission Network Design Optimization and Robustness Analysis for a Case Study in Tyrol - Methodology

+

CITATION

+
+

Abstract – The majority of district heating (DH) networks today are fueled by combustion processes based on fossil or biogenic fuels. For the decarbonization of DH networks various uncertainties regarding the future development of key factors, such as energy prices, need to be considered. Within the project “HeatHighway” a hypothetical inter-regional heat transfer network (HTN) in the region of the Inn valley in Tyrol, Austria was investigated.

+
+
+

Keywords – Future district heating, Waste heat sources, 4th generation DH, Heat transmission networks, Deterministic optimization, Monte Carlo simulation

+
+ +
+
+
+

Optimizing the Domestic Production and Infrastructure for Green Hydrogen in Austria for 2030

+

CITATION

+
+

Abstract – The decarbonisation of the Austrian energy system is expected to be facilitated by the uptake of hydrogen-based technologies, which requires the establishment of a hydrogen infrastructure to meet the rising demand. While large quantities of hydrogen are expected to be imported in the future, current developments in the energy market suggest that domestic production of hydrogen should not be ignored to ensure the security of supply. As domestic production ramps up, locating electrolysers to ensure optimal system integration is still an open question. To address this challenge, the “HyTechonomy” project developed an optimisation model that identifies the most promising domestic locations for green hydrogen production and optimal means of hydrogen transport for the year 2030.

+
+
+

Keywords – Hydrogen infrastructure, Energy system modelling, centralised electrolysis, decentralised electrolysis

+
+ +
+
+
+

Contributing

+

To contribute a new reference, either

+
    +
  • fork the iesopt repository, and directly add to the above list, or

  • +
  • open an issue with the reference details.

  • +
+

See the template below for the structure of a reference.

+
+

Template

+

Please stick to APA format here, and always include a link as badge (if possible a DOI, if not other links are okay +too).

+
### Title of the Publication
+
+[![CITATION](url-of-your-badge)](link-to-doi-or-pure-or-other)
+
+> _**Abstract --**_ Put your abstract text here.
+
+> _**Keywords --**_ Put some, Keywords, In this, List
+
+```{admonition} Expand to show citation
+:class: dropdown
+
+Add your (APA styled!) citation here
+```
+
+---
+
+
+
+
+

Creating citation badges

+

You can use shields.io to create badges, or use standardized ones that you already have +(e.g., from Zenodo), otherwise stick to the ones provided below.

+

Pure: (publications.ait.ac.at)

+
+
[![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-none?style=social)](ADDYOURLINKHERE)
+
+
+
+

DOI:

+
+
[![CITATION](https://img.shields.io/badge/DOI-10.XXXX%2Fname.YYYY.ZZZZZZ-none?style=social)](https://doi.org/10.XXXX/name.YYYY.ZZZZZZ)
+
+
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/tutorials/addons.html b/pages/tutorials/addons.html new file mode 100644 index 0000000..63f59f3 --- /dev/null +++ b/pages/tutorials/addons.html @@ -0,0 +1,212 @@ + + + + + + + + + +Addons | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/tutorials/templates_1.html b/pages/tutorials/templates_1.html new file mode 100644 index 0000000..c07f869 --- /dev/null +++ b/pages/tutorials/templates_1.html @@ -0,0 +1,651 @@ + + + + + + + + + +Templates: Part I | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Templates: Part I

+

Templates are a powerful feature of IESopt that allow you to define new types of “components” by yourself. This makes +use of the existing CoreComponents, and combines them in multiple ways, which allows for a high degree of flexibility +without having to write any mathematical model yourself.

+

This tutorial will guide you through the process of creating a new template, and we will do that on the example of +creating the HeatPump template (a template shipped via IESoptLib).

+
+

The basic structure

+

A template is defined by a YAML file, similar to the config.iesopt.yaml file that you already know. First, we need to +think about the parameters that we want to define for our heat pump. Let’s create a new file for that. The pre-defined +one in IESoptLib is called HeatPump, so we need a different name: Templates must always have a unique name.

+

Possibilities for that could be:

+
    +
  • CustomHeatPump, if you do not have any more details

  • +
  • GroundSourceHeatPump, if we want to implement a ground-source heat pump with different parameters/features than the +standard one

  • +
  • FooHeatPump, if you need it specifically for a project called “Foo”

  • +
+

!!! info “Naming conventions” +Templates follow a naming convention similar to PascalCase: +- The name must start with an upper-case letter +- It must consist of at least two letters +- Numbers and special characters are not allowed

+

Let’s go with CustomHeatPump for now. Create a new file CustomHeatPump.iesopt.template.yaml (if you are already +working on a model, the best place to put this would be a templates/ folder), and add the following lines:

+
parameters:
+  p_nom: null
+  electricity_from: null
+  heat_from: null
+  heat_to: null
+  cop: null
+
+
+

This defines the basic parameters that we want to use for our heat pump. The null values indicate that they all +default to nothing in Julia, which corresponds to None in Python. Let’s go through them:

+
    +
  • p_nom: The nominal power of the heat pump, which will be specified on the electricity input side

  • +
  • electricity_from: The Node that this heat pump is connected to for electricity input

  • +
  • heat_from: The Node that this heat pump is connected to for heat input

  • +
  • heat_to: The Node that this heat pump is connected to for heat output

  • +
  • cop: The coefficient of performance of the heat pump

  • +
+

Next, we will set up the actual component. This is done in the component section of the template file. Let’s add the +following lines:

+
component:
+  type: Unit
+  inputs: {electricity: <electricity_from>, heat: <heat_from>}
+  outputs: {heat: <heat_to>}
+  conversion: 1 electricity + (<cop> - 1) heat -> <cop> heat
+  capacity: <p_nom> in:electricity
+
+
+

This defines the component that we want to create. The type is Unit, which is a core component type in IESopt that +you are already familiar with. Instead of providing fixed values, we make use of the parameters that we defined above. +This is done by using the <...> syntax.

+

That’s it! You have created a new template. You can now use this template in your model configuration, as you would with +any other component. For example, you could add the following lines to your config.iesopt.yaml file:

+
# other parts of the configuration file
+# ...
+
+components:
+  # some other components
+  # ...
+
+  heat_pump:
+    template: CustomHeatPump
+    parameters:
+      p_nom: 10
+      electricity_from: electricity
+      heat_from: ambient
+      heat_to: heating
+      cop: 3
+
+
+
+
+

Accounting for different configurations

+

But wait. What if you want to have different configurations for your heat pump? For example, you might want to have a +heat pump that does not explicitly consume any heat, because they low-temperature heat source is not explicitly modeled. +Currently, the template does not allow for that, because the heat_from parameter is mandatory.

+

Why do we know it is mandatory? Because it is used in the inputs section of the Unit definition. But that is not +clear, or transparent. Before we continue, we will fill in the mandatory documentation fields for the template. We do +that by adding the following information directly at the beginning of the template file, right before the parameters:

+
# # Custom Heat Pump
+
+# A (custom) heat pump that consumes electricity and heat, and produces heat.
+
+# ## Parameters
+# - `p_nom`: The nominal power (electricity) of the heat pump.
+# - `electricity_from`: The `Node` that this heat pump is connected to for electricity input.
+# - `heat_from`: The `Node` that this heat pump is connected to for heat input.
+# - `heat_to`: The `Node` that this heat pump is connected to for heat output.
+# - `cop`: The coefficient of performance of the heat pump.
+
+# ## Components
+# _to be added_
+
+# ## Usage
+# _to be added_
+
+# ## Details
+# _to be added_
+
+parameters:
+  # ...
+
+
+

!!! info “Docstring format” +All of that is actually just Markdown inserted into your template. However, make sure to stick to separating the +leading # from the actual text by a space, as this is required for IESopt to better understand your documentation.

+

Now, every user of the template will see this information, and they will notice, that none of the parameters are marked +as optional. As you see, there are a lot of other sections to be added, but we will fill them out at the end, after we +have finished the template, see the section on finalizing the docstring.

+

Let’s continue with accounting for different configurations. We will cover the following steps:

+
    +
  1. Making the heat_from parameter optional

  2. +
  3. Extending the template to allow for sizing the heat pump (an investment decision)

  4. +
  5. Handling more complex COP configurations

  6. +
+
+

Optional parameter and sizing decision

+

While there are multiple ways to make a parameter optional, we will make use of the most powerful one, so that you are +able to apply it for your models as well. For that, we will add “complex” functionalities to the template, which is done +using three different “functions”:

+
    +
  1. validate: This function is called when the template is parsed, and it is used to check if the parameters are valid. +If they are not, an error is thrown. This helps to inform the user of any misconfiguration.

  2. +
  3. prepare: This function is called when the template is instantiated, and it is used to prepare the component for +usage. This can be used to set default values, or to calculate derived parameters (which we will use to tackle the +three additions mentioned above).

  4. +
  5. finalize: This function is called when the template is finalized, and it enables a wide range of options. We will +use this to allow a smooth result extraction for the heat pump, but you could also use it to add additional (more +complex) constraints to the component, or even modify the model’s objective function.

  6. +
+

Let’s start by adding the functions entry (which we suggest doing at the end of the file):

+
# ... the whole docstring ...
+
+parameters:
+  # ...
+
+component:
+  # ...
+
+functions:
+  validate: |
+    # ... we will put the validation code here ...
+  prepare: |
+    # ... we will put the preparation code here ...
+  finalize: |
+    # ... we will put the finalization code here ...
+
+
+
+

The | at the end of the line indicates that the following lines are a multiline string. This is a YAML feature that +allows you to write more complex code in a more readable way.

+
+

Let’s start by filling out the validation function. Everything you do and write here, is interpreted as Julia code, and +compiled directly into your model. This means that you can use all the power of Julia, but also that you need to be +careful with what you do. You have access to certain helper functions and constants, which we will introduce here. If +you have never written a line of Julia code, don’t worry. We will guide you through this - it’s actually (at least for +the parts that you will need) extremely similar to Python.

+
+

Validation

+

The validation function is used to check if the parameters are valid. Add the following code to the validate section:

+
functions:
+  validate: |
+    # Check if `p_nom` is non-negative.
+    @check get("p_nom") isa Number
+    @check get("p_nom") >= 0
+
+    # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`.
+    @check get("electricity_from") isa String
+    @check get("heat_from") isa String || isnothing(get("heat_from"))
+    @check get("heat_to") isa String
+
+    # Check if `cop` is positive.
+    @check get("cop") isa Number
+    @check get("cop") > 0
+  # ... the rest of the template ...
+
+
+

Let’s go through this step by step:

+
    +
  • You can start comments (as separate line or inline) with #, as you would in Python.

  • +
  • You can use get("some_param") to access the value of a parameter.

  • +
  • You can use @check to check if a condition is met. If it is not, an error will be thrown. All statements starting +with @ are so called “macros”, which are just “special” functions. You can do @check(condition) or +@check condition, since macros do not require parentheses.

  • +
  • You can use isa to check if a value is of a certain type. This is similar to isinstance in Python. While it is a +special keyword, if you prefer, you can also call it in a more conventional way: isa(get("p_nom"), Number).

  • +
  • Data types are capitalized in Julia, so it is String instead of string, and Number is a superset of all numeric +types (if necessary you could instead, e.g., check for get("some_param") isa Int).

  • +
  • Logical operators are similar to Python, so || is like or, and && is like and.

  • +
  • If all checks pass, the template is considered valid, and the model can be built.

  • +
+
+
+

Preparation

+

Next, we will add the preparation function. This function is used to prepare the component for usage. Since we would +like to make the heat_from parameter optional, and we would like to account for optional sizing, we will first modify +the parameters accordingly:

+
parameters:
+  p_nom: null
+  p_nom_max: null
+  electricity_from: null
+  heat_from: null
+  heat_to: null
+  cop: null
+  _inputs: null
+  _conversion: null
+  _capacity: null
+  _invest: null
+
+
+

One step at a time. We added the following parameters:

+
    +
  • p_nom_max: The maximum nominal power of the heat pump. This is optional, and if not specified, it will default to +p_nom, which will disable the sizing feature.

  • +
  • _inputs: This is an internal / private parameter (since it starts with an underscore), which we will user later. +These parameters are not exposed to the user, and can not be set or modified from the outside.

  • +
  • _capacity: This is another internal parameter, which we will use to store the capacity of the heat pump (which could +now either bne p_nom or whatever the investment decision results in).

  • +
  • _conversion: This is another internal parameter, which we will use to store the conversion formula.

  • +
+

Before we can actually add the code for the prepare function, we need to modify our component definition, as well. +We (1) will change from component to components (since it now contains more than just one), (2) will add a +Decision that should handle the sizing / investment, and modify the Unit slightly:

+
components:
+  unit:
+    type: Unit
+    inputs: <_inputs>
+    outputs: {heat: <heat_to>}
+    conversion: <_conversion>
+    capacity: <_capacity> in:electricity
+  
+  decision:
+    type: Decision
+    enabled: <_invest>
+    lb: <p_nom>
+    ub: <p_nom_max>
+
+
+

So … a lot of changes. Let’s go through them step by step:

+
    +
  • We changed component to components, because we now have multiple components.

  • +
  • We added a unit component, which is the actual heat pump. We replaced the fixed values with the internal parameters.

  • +
  • We added a new component decision, which is a Decision. This component is used to handle investment +decisions. It is enabled if _invest evaluates to true. It has a lower bound lb and an upper bound ub, which +are the minimum and maximum values that the decision can take. In our case, the decision is the nominal power of the +heat pump, which can be between p_nom and p_nom_max.

  • +
+

!!! info “Naming the components” +The names of the components are arbitrary, and you can choose whatever you like. However, it is recommended to use +meaningful names, so that you can easily understand what the component does. Component names follow a naming +convention similar to snake_case: They must start with a lower-case letter, and can contain numbers and +underscores (but are not allowed to end in an _). They can further contain ., but this is “dangerous” and an +expert feature, that you should not use unless you know what it does, and why you need it.

+

Onto the actual functionality. Let’s add the prepare function, and some additional validation code:

+
functions:
+  validate: |
+    # ... the previous validation code ...
+
+    # Check if `p_nom_max` is either `nothing` or at least `p_nom`.
+    @check isnothing(get("p_nom_max")) || (get("p_nom_max") isa Number && get("p_nom_max") >= get("p_nom"))
+  prepare: |
+    # Determine if investment should be enabled, and set the parameter (used to enable `decision`).
+    invest = !isnothing(get("p_nom_max")) && get("p_nom_max") > get("p_nom")
+    set("_invest", invest)
+
+    if invest
+        # Set the capacity to the size of the decision variable.
+        myself = get("self")
+        set("_capacity", "$(myself).decision:value")
+    else
+        # Set the capacity to the value of `p_nom`.
+        set("_capacity", get("p_nom"))
+    end
+
+    # Prepare some helper variables to make the code afterwards more readable.
+    elec_from = get("electricity_from")
+    heat_from = get("heat_from")
+    cop = get("cop")
+
+    # Handle the optional `heat_from` parameter.
+    if isnothing(heat_from)
+        # If `heat_from` is not specified, we just use electricity as input.
+        set("_inputs", "{electricity: $(elec_from)}")
+        set("_conversion", "1 electricity -> $(cop) heat")
+    else
+        # If `heat_from` is specified, we now have to account for two inputs.
+        set("_inputs", "{electricity: $(elec_from), heat: $(heat_from)}")
+        set("_conversion", "1 electricity + $(cop - 1) heat -> $(cop) heat")
+    end
+
+
+

Once again, let’s go through this step by step:

+

To be added.

+
+
+
+

Complex COP configurations

+

To be added.

+
+
+
+

The finalize function

+

To be added.

+
+
+

Finalizing the docstring

+

To be added.

+
+
+

Conclusion

+

To be added.

+

!!! details “Complete template YAML” +```yaml +# # Custom Heat Pump

+
# A (custom) heat pump that consumes electricity and heat, and produces heat.
+
+# ## Parameters
+# - `p_nom`: The nominal power (electricity) of the heat pump.
+# - `electricity_from`: The `Node` that this heat pump is connected to for electricity input.
+# - `heat_from`: The `Node` that this heat pump is connected to for heat input.
+# - `heat_to`: The `Node` that this heat pump is connected to for heat output.
+# - `cop`: The coefficient of performance of the heat pump.
+
+# ## Components
+# _to be added_
+
+# ## Usage
+# _to be added_
+
+# ## Details
+# _to be added_
+
+parameters:
+  p_nom: null
+  p_nom_max: null
+  electricity_from: null
+  heat_from: null
+  heat_to: null
+  cop: null
+  _inputs: null
+  _conversion: null
+  _capacity: null
+  _invest: null
+
+components:
+  unit:
+    type: Unit
+    inputs: <_inputs>
+    outputs: {heat: <heat_to>}
+    conversion: <_conversion>
+    capacity: <_capacity> in:electricity
+    
+  decision:
+    type: Decision
+    enabled: <_invest>
+    lb: <p_nom>
+    ub: <p_nom_max>
+
+functions:
+  validate: |
+    # Check if `p_nom` is non-negative.
+    @check get("p_nom") isa Number
+    @check get("p_nom") >= 0
+
+    # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`.
+    @check get("electricity_from") isa String
+    @check get("heat_from") isa String || isnothing(get("heat_from"))
+    @check get("heat_to") isa String
+
+    # Check if `cop` is positive.
+    @check get("cop") isa Number
+    @check get("cop") > 0
+
+    # Check if `p_nom_max` is either `nothing` or at least `p_nom`.
+    @check isnothing(get("p_nom_max")) || (get("p_nom_max") isa Number && get("p_nom_max") >= get("p_nom"))
+  prepare: |
+    # Determine if investment should be enabled, and set the parameter (used to enable `decision`).
+    invest = !isnothing(get("p_nom_max")) && get("p_nom_max") > get("p_nom")
+    set("_invest", invest)
+
+    if invest
+        # Set the capacity to the size of the decision variable.
+        myself = get("self")
+        set("_capacity", "$(myself).decision:value")
+    else
+        # Set the capacity to the value of `p_nom`.
+        set("_capacity", get("p_nom"))
+    end
+
+    # Prepare some helper variables to make the code afterwards more readable.
+    elec_from = get("electricity_from")
+    heat_from = get("heat_from")
+    cop = get("cop")
+
+    # Handle the optional `heat_from` parameter.
+    if isnothing(heat_from)
+        # If `heat_from` is not specified, we just use electricity as input.
+        set("_inputs", "{electricity: $(elec_from)}")
+        set("_conversion", "1 electricity -> $(cop) heat")
+    else
+        # If `heat_from` is specified, we now have to account for two inputs.
+        set("_inputs", "{electricity: $(elec_from), heat: $(heat_from)}")
+        set("_conversion", "1 electricity + $(cop - 1) heat -> $(cop) heat")
+    end
+```
+
+
+
+
+

Next steps

+

While the above template is already quite powerful, it can become hard to maintain and understand if it grows too large. +In the next tutorial, we will cover how to separate the functions part of the template into a separate file, and later +will see how this approach can then be extended even further (a concept that we call Addons), which allows +intercepting steps of the model build process.

+

But … before we go there, let’s start “small”. Check out the section Templates: Part II, where we walk +through the process of “out-sourcing” the functions part of the template.

+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/tutorials/templates_2.html b/pages/tutorials/templates_2.html new file mode 100644 index 0000000..c5241a3 --- /dev/null +++ b/pages/tutorials/templates_2.html @@ -0,0 +1,254 @@ + + + + + + + + + +Templates: Part II | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Templates: Part II

+

To be added.

+
+

Catching errors

+

To be added.

+
+
+

Separate .jl files

+

To be added.

+
+
+

Some examples

+
+

HeatPump

+

To be added.

+
+
+

BESS

+

To be added.

+
+
+

PV

+

To be added.

+
+
+

CHP

+

To be added.

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/general/best_practice.html b/pages/user_guides/general/best_practice.html new file mode 100644 index 0000000..f460b27 --- /dev/null +++ b/pages/user_guides/general/best_practice.html @@ -0,0 +1,253 @@ + + + + + + + + + +Best practices | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Best practices

+
+

Separate energy carriers

+

Imagine a system where a heat pump is used to produce heat for both heating and hot water. One may be tempted to use +heat as corresponding energy carrier for both purposes. However, this is not recommended. Instead, one could use +heating and hotwater as energy carriers (or any other naming, as long as it creates a distinction).

+

Why?

+
    +
  1. Clarity: It is easier to understand the system when energy carriers are clearly separated.

  2. +
  3. Flexibility: It is easier to change the system later on. For example, if one wants to replace the heat pump with +a gas boiler - but just for hot water - it is easier to do so when the energy carriers are separated.

  4. +
  5. Consistency: It is easier to compare different systems when the energy carriers are consistently named.

  6. +
  7. Error prevention: It is less likely to make mistakes when the energy carriers are clearly separated. Mistkenly +connecting two Nodes with heat, that in reality could not be connected since they are part of two different +systems, cannot happen when the energy carriers are separated.

  8. +
  9. Plotting: It is easier to plot the system when the energy carriers are separated. For example, one can plot the +heat demand for heating and hot water separately, or immediately see how much energy is being spent to supply the +different demands.

  10. +
+
+

These are just a few reasons why it is recommended to separate energy carriers. Different systems may have different +requirements, so it is up to the user to decide how to name the energy carriers. However, it is recommended to keep +the energy carriers as separate as reasonable.

+
+
+

Definition: Energy carrier

+

According to ISO 13600, an energy carrier is either a substance or a phenomenon that can be used to produce +mechanical work or heat or to operate chemical or physical processes.

+

This may be seen as motivation why, in this +case, the argument “both are heat” may not be valid: In the end, the actual “carrier” is most likely water, and not +heat (even if that is most commonly used as “carrier”). However, no one would argue to actually use water in +this example, which shows that the choice of heat would already be “not 100% exact”; therefore, the separation +into different two carriers does not “mis-represent” reality, but instead just makes our “abstraction” (= not using +water as carrier) more explicit.

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/general/common_errors.html b/pages/user_guides/general/common_errors.html new file mode 100644 index 0000000..a3fa6b7 --- /dev/null +++ b/pages/user_guides/general/common_errors.html @@ -0,0 +1,225 @@ + + + + + + + + + +Common errors | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Common errors

+
+

During package load

+

Errors that may occur when executing import iesopt.

+
    +
  • “Exception: no version of Julia is compatible with =x.y.z - perhaps you need to update JuliaUp”: Open a terminal and execute juliaup update. If it still reports the same error afterwards, checkout the specific version that it fails to find, and manually install that.

  • +
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/general/linking_components.html b/pages/user_guides/general/linking_components.html new file mode 100644 index 0000000..808f2bc --- /dev/null +++ b/pages/user_guides/general/linking_components.html @@ -0,0 +1,261 @@ + + + + + + + + + +Linking components | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Linking components

+

Sometimes, two or more components need to be linked together. While this can be achieved using addons, it can be achieved using custom functionality within a template. Consider the following (shortened) version of the CHP template:

+
parameters:
+  p_max: null
+  h_max: null
+  power_ratio: 0.55
+  power_loss_ratio: 0.20
+  efficiency: 0.40
+  fuel: gas
+  fuel_co2_emission_factor: 0.2
+  fuel_in: null
+  power_out: null
+  heat_out: null
+  co2_out: null
+
+components:
+  power:
+    type: Unit
+    inputs: {<fuel>: <fuel_in>}
+    outputs: {electricity: <power_out>, co2: <co2_out>}
+    conversion: 1 <fuel> -> <efficiency> electricity + <fuel_co2_emission_factor> co2
+    capacity: <p_max> out:electricity
+
+  heat:
+    type: Unit
+    inputs: {<fuel>: <fuel_in>}
+    outputs: {heat: <heat_out>, co2: <co2_out>}
+    conversion: 1 <fuel> -> <efficiency>/<power_loss_ratio> heat + <fuel_co2_emission_factor> co2
+    capacity: <h_max> out:heat
+
+functions:
+  finalize: |
+    # Parameters.
+    cm = get("power_ratio")
+    cv = get("power_loss_ratio")
+    p_max = get("p_max")
+
+    # Output expressions.
+    out_heat = access("heat").exp.out_heat
+    out_elec = access("power").exp.out_electricity
+
+    # Add constraints.
+    @constraint(MODEL.model, cm .* out_heat .<= out_elec)
+    @constraint(MODEL.model, out_elec .<= p_max .- cv .* out_heat)
+
+
+

This makes use of the finalize(...) function to link the power and heat components. Using an addon can be complicated, because the components do not inherently know about each other. The finalize(...) function however is attached to the template itself, can access both components, and is called after both are fully constructed, which means it can freely access their variables and expressions.

+
+

Note: A similar approach is possible by using the build_priority parameter in the component definition. This parameter allows you to specify the order in which components are built, which can be used to ensure that one component is built before another.

+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/general/solvers.html b/pages/user_guides/general/solvers.html new file mode 100644 index 0000000..73981ea --- /dev/null +++ b/pages/user_guides/general/solvers.html @@ -0,0 +1,333 @@ + + + + + + + + + +Solvers | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Solvers

+ +
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/index.html b/pages/user_guides/index.html new file mode 100644 index 0000000..100413c --- /dev/null +++ b/pages/user_guides/index.html @@ -0,0 +1,225 @@ + + + + + + + + + +Intro | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Intro

+

These user guides cover a wide range of topics, going into detail on specifics, but do not “take you by the hand” as much as a tutorial would:

+
+

Contents

+
    +
  • General: Crafted user guides covering a specific topic. These are mostly used to explain advanced topics, that are too complex to fully explain in a tutorial, or are linked as “dive deeper” options to read up on something.

  • +
  • Q & A: These are based on previous questions that users had and discussions that occurred to clarify. They can be used to point users to for any questions that repeatedly occur.

  • +
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/qna/dynamic_marginal_costs.html b/pages/user_guides/qna/dynamic_marginal_costs.html new file mode 100644 index 0000000..2a2fc9a --- /dev/null +++ b/pages/user_guides/qna/dynamic_marginal_costs.html @@ -0,0 +1,305 @@ + + + + + + + + + +Output-dependent marginal costs | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Output-dependent marginal costs

+

Question:

+
+

How can I model a Unit where the marginal cost changes based on the level of energy output? For example, the cost is 5 €/MWh for outputs below 5 MW of power and 10 €/MWh for outputs above 5 MW of power.

+
+

Answer:

+
+

To model changing marginal costs based on output levels in IESopt, you can split the Unit into two separate Units:

+
+
+

Details

+
+

Explanation

+
    +
  1. Low-output Unit:

    +
      +
    • Represents the output up to 5 MW.

    • +
    • Has a marginal cost of 5 €/MWh.

    • +
    • Capacity is limited to 5 MW.

    • +
    +
  2. +
  3. High-output Unit:

    +
      +
    • Represents any output above 5 MW.

    • +
    • Has a marginal cost of 10 €/MWh.

    • +
    • The capacity is given as total capacity of the “real” Unit, reduced by 5 MW.

    • +
    +
  4. +
+

By doing this, the model will prioritize the low-cost Unit up to its capacity before utilizing the high-cost Unit for additional demand.

+
+
+

Implementation

+
unit_pool_1:
+  type: Unit
+  inputs: {}                    # fill this with the original configuration
+  outputs: {}                   # fill this with the original configuration
+  conversion: foo -> bar        # fill this with the original configuration
+  capacity: 5 out:electricity
+  marginal_cost: 5 per out:electricity
+
+unit_pool_2:
+  type: Unit
+  inputs: {}                    # fill this with the original configuration
+  outputs: {}                   # fill this with the original configuration
+  conversion: foo -> bar        # fill this with the original configuration
+  capacity: 95 out:electricity  # `100 - 5` MW, assuming 100 MW is the max.
+  marginal_cost: 10 per out:electricity
+
+
+
+
+
+

Summary

+
+

Notes

+
    +
  • Flexibility: This method allows you to create multiple cost bands by adding more Units with different capacities and marginal costs.

  • +
  • Intuitiveness: Splitting Units makes the model easier to understand and maintain.

  • +
  • Accuracy: The model will naturally favor the lower-cost Unit up to its capacity limit before using higher-cost options.

  • +
+

But …

+
    +
  • This might be an over complicated way to represent stuff like actual cost-power curve dynamics.

  • +
  • This might add too many Units, making the model hard to understand or analyse.

  • +
  • If there is any reason that the model might save costs by not running the “lower price” Unit it will do so! This means the “higher price” part of your asset could be used with out running the “lower price”, which in reality is not possible (where it’s one asset).

  • +
+

Note that the last point most likely will not occur for your model, but … it’s something to keep in mind.

+
+
+

Conclusion

+

By representing different output levels with separate Units in IESopt, you can effectively model Units with changing marginal costs based on their energy output. This approach is straightforward and leverages the existing capabilities of IESopt without the need for complex cost functions.

+
+
+

Improvements

+

The “best” or final way to represent stuff like this will always be a custom made addon (potentially making use of SOS variables).

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/qna/passive_charging.html b/pages/user_guides/qna/passive_charging.html new file mode 100644 index 0000000..405097f --- /dev/null +++ b/pages/user_guides/qna/passive_charging.html @@ -0,0 +1,309 @@ + + + + + + + + + +Passive charging of an underground heat storage | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Passive charging of an underground heat storage

+
+

Intro

+

Question:

+
+

How can I represent passive charging in a borehole heat storage within my energy system optimization model?

+
+

Answer:

+
+

It’s complicated … But the only generally applicable answer is: A custom addon. The underlying idea is to separate the passive charging from the storage, into a separate “artificial” Profile, that can then accurately depict the passive behavior.

+
+
+
+

The problem

+

When modeling a borehole heat storage - a deep underground heat storage surrounded by warm soil - it’s important to account for passive charging from the surrounding soil. This guide explains how to represent this passive energy input in your model.

+
+

Common misconception

+

You might consider using the state_percentage_loss parameter with a negative value to simulate passive charging:

+
state_percentage_loss: -0.01  # Attempting to charge 1% per timestep
+
+
+

However, state_percentage_loss is designed for losses based on the current storage level. Using a negative value would incorrectly charge the storage by a percentage of its existing energy content, not the energy it lacks.

+

Therefore, while -0.01 might be an allowed or in other cases even reasonable choice, it’s not made for what is needed here.

+
+
+ +
+

Summary

+
+

Steps to implement

+
    +
  1. Create the storage component: Define your underground storage Node without using state_percentage_loss for passive charging.

  2. +
  3. Add the Passive Charging Input: Introduce a Profile to represent the passive energy inflow.

  4. +
  5. Set Profile Bounds: Use the mode ranged for the Profile with appropriate lower (lb) and upper (ub) bounds.

  6. +
  7. Add the Constraint: Implement the constraint to tie the passive charging rate to the storage’s unfilled capacity.

  8. +
  9. Refine as Needed: Adjust the constraint for more complex behaviors if necessary. There might be a lot (!) that you might want to specialize.

  10. +
+
+
+

Conclusion

+

By modeling passive charging as a constrained input energy flow based on the storage’s remaining capacity, you accurately represent the thermal interactions of an underground heat storage with its environment.

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/qna/profiles_sign.html b/pages/user_guides/qna/profiles_sign.html new file mode 100644 index 0000000..62e338b --- /dev/null +++ b/pages/user_guides/qna/profiles_sign.html @@ -0,0 +1,314 @@ + + + + + + + + + +Sign convention for time series | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Sign convention for time series

+
+

Intro

+

Question:

+
+

How does IESopt interpret positive and negative values in a Profile connected using node_from?

+
+

Answer:

+
+

When modeling energy systems in IESopt, you might have a fixed Profile representing both energy demand and supply — the proper sign of generation / consumption then depends on the way it is configured and connects to other components. Understanding how to correctly connect this Profile to a Node is crucial for accurate results.

+
+
+
+

Details

+
+

Understanding node_from and node_to

+

Generally the following intuition applies:

+
    +
  • node_from: Indicates that the component draws energy from the specified Node.

  • +
  • node_to: Indicates that the component injects energy into the specified Node.

  • +
+
+

Example: node_from

+

If you connect your fixed Profile (mode: fixed, which is the default) to a Node using node_from, here’s how IESopt interprets the values:

+
    +
  • Positive Values: The Profile draws energy from the Node (consumption).

  • +
  • Negative Values: The Profile effectively injects energy into the Node (generation).

  • +
+
+
+
+

Why does this happen?

+

In IESopt, drawing a negative amount of energy from a Node (-x kWh) is mathematically equivalent to injecting a positive amount of energy (+x kWh) into it. This means, the following are equal:

+
    +
  • A negative value in a node_from-configured Profile

  • +
  • A positive value in a node_to-configured Profile

  • +
  • Injecting x > 0 units of energy into a Node

  • +
  • Withdrawing a negative amount of energy from a Node

  • +
+
+
+
+

Practical example

+

Suppose you have a fixed Profile connected to an electricity grid Node:

+
myprofile:
+  type: Profile
+  carrier: electricity
+  node_from: elec_grid_node
+  value: [100, -50, 150, -75]  # in kW
+
+
+
    +
  • At time step 1: The Profile draws with a power of 100 kW from the Node (consumption).

  • +
  • At time step 2: The Profile injects with a power of 50 kW into the Node (generation).

  • +
  • And so on.

  • +
+
+

Alternative approach with node_to

+

If you prefer to handle injections explicitly, you can use node_to:

+
    +
  • Connect your Profile using node_to.

  • +
  • Positive values will inject energy into the Node.

  • +
  • Negative values will draw energy from the Node.

  • +
+
+
+
+

Summary

+
    +
  • Using node_from:

    +
      +
    • Positive values = Draw from Node.

    • +
    • Negative values = Inject into Node.

    • +
    +
  • +
  • Using node_to:

    +
      +
    • Positive values = Inject into Node.

    • +
    • Negative values = Draw from Node.

    • +
    +
  • +
+

By correctly setting up your Profile and understanding the sign conventions, you ensure that your model accurately reflects the intended energy flows.

+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/qna/snapshot_duration.html b/pages/user_guides/qna/snapshot_duration.html new file mode 100644 index 0000000..081b03c --- /dev/null +++ b/pages/user_guides/qna/snapshot_duration.html @@ -0,0 +1,379 @@ + + + + + + + + + +Time resolution & power vs. energy | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Time resolution & power vs. energy

+
+

Intro

+

Question:

+
+

I’m moving from hourly to daily time steps in my IESopt model. How should I adjust my input data, especially capacities and costs, to ensure accurate results? Should I express capacities as energy per time step (e.g., kWh/day) instead of power (kW)?

+
+

Answer:

+
+

When working with IESopt models, it’s crucial to understand how the model interprets units of power and energy, especially when changing the duration of your time steps (snapshots).

+
+
+
+

Details

+

The following steps guide you through the changes:

+
    +
  1. Update the duration of each time step using the weights parameter in your snapshots configuration.

  2. +
  3. Make sure that you are using the correct power/energy convention for configuring the model’s components.

  4. +
  5. Resample time series data, where necessary.

  6. +
+
+

Configuring snapshots

+

First, update your snapshots section of the top-level YAML config. This can be used not only to set the total number of snapshots in your model, but also how long each one is, using the weights parameter.

+
config:
+  optimization:
+    # ... other settings ...
+    snapshots:
+      count: 365       # Number of time steps (e.g., days in a year)
+      weights: 24      # Duration of each time step in hours (for daily steps)
+
+
+
+

Example:
+If each time step represents one day, setting weights: 24 tells the model that each snapshot spans 24 hours.

+
+
+

Time step duration is handled internally

+

Accounting for the “conversion” between power and energy terms is handled internally:

+
    +
  • IESopt multiplies power by the duration of each time step to calculate energy.

  • +
  • You don’t need to convert capacities to energy units per time step; the model does this for you.

  • +
+
+

Internally, the total cost is calculated using a formula along the lines of:

+
+\[ +> \text{Cost (€)} = \text{Power (kW)} \times \text{Duration (h)} \times \text{Cost per Energy Unit (€/kWh)} +> \]
+
+
+
+
+

Checking power/energy units

+
+

Keep capacities in power units

+
    +
  • Unit Capacities: Always express capacities in power units (e.g., kW), regardless of the time step duration.

  • +
  • Profiles and Connections: Similarly, Profiles (e.g., time series data) and connection bounds should remain in power units.

  • +
+
+
+

Costs are per unit of energy

+
    +
  • Variable Costs: Input costs as monetary units per unit of energy (e.g., €/kWh).

  • +
  • Consistency: This approach ensures that cost calculations remain accurate, as the model internally accounts for time step durations.

  • +
+
+
+

Storage units are in energy units

+
    +
  • Exception for storage: Capacities for storage units are specified in energy units (e.g., kWh) because they represent stored energy, not power output.

  • +
+
+
+

Practical example

+

Suppose you have a generator with:

+
    +
  • Capacity: 100 kW

  • +
  • Marginal cost: 0.10 €/kWh

  • +
  • Time step duration: 24 hours (daily)

  • +
+

The model calculates the energy produced and the cost as follows:

+
    +
  • Energy produced per time step:

    +
    +\[ + \text{Energy (kWh)} = \text{Power (kW)} \times \text{Duration (h)} = 100 \times 24 = 2,400 \text{ kWh} + \]
    +
  • +
  • Total cost per time step:

    +
    +\[ + \text{Cost (€)} = \text{Energy (kWh)} \times \text{Cost per Energy Unit (€/kWh)} = 2,400 \times 0.10 = 240 \text{ €} + \]
    +
  • +
+
+
+
+

Resampling time series data

+

Sometimes, or rather most of the time, you will need to update the input time series data. Not because a change of units happened, but because moving from hourly to daily snapshots will essentially reduce the length of your data by the factor 24. A quick “checklist” on what to use:

+
    +
  • Resampling a time series given in a power unit is done by taking the mean of all grouped time steps (you should almost only need this).

  • +
  • Resampling a time series given in an energy unit is done by taking the sum of all grouped time steps (you most likely never need this).

  • +
+
+

If you are using Python, a simple way to achieve this is using the groupby or resample functionalities that a pandas.DataFrame offers.

+
+
+

Example

+

Consider a time series of energy demand. This is used as input to configure a Profile. That means: It’s given in power units - and if it’s not then make sure it is, because the reason it worked for a “1-hourly” model was only because the numerical values mapping kW -> kWh are the same; however, for any other time resolution that fails.

+

If we now aggregate / group 24 time steps, and then the next 24 and so on, then the most common way to calculate the proper daily power is by taking the mean. Let’s assume the demand is 10 kW for the first 12 hours, and 20 kW for the last 12 hours. Taking the mean would tell us to use 15 kW as value of our demand time series, when using a daily resolution.

+

Let’s check if that is correct …

+
    +
  1. The total energy consumption in the first 12 hours is 120 kWh, and 240 kWh for the last 12 hours, for a total of 360 kWh during the day.

  2. +
  3. A resampled demand time series of 15 kW would results in \(15 kW \times 24h = 360 kWh\).

  4. +
+

So, we see that the energy consumed stays the same. Note: Other methods of resampling the power time series could be valid in certain cases, but this here is the only one that preserves the total energy consumed - which is what drives costs in our model and is therefore one of the most important parameters.

+
+
+
+
+

Summary

+
+

Key takeaways

+
    +
  • No need to adjust units: Keep capacities in power units; do not convert them to energy per time step.

  • +
  • Costs remain per energy unit: Continue to provide costs in €/kWh or similar units.

  • +
  • Model handles duration: The model uses the weights parameter to account for the duration of each time step in calculations.

  • +
  • Consistency is crucial: By keeping units consistent, you ensure accurate modeling results without additional adjustments.

  • +
+
+
+

Conclusion

+

Changing the time step duration in your IESopt model doesn’t require you to alter your input units. By specifying capacities in power units and costs per unit of energy, and by correctly configuring the time step durations, IESopt will accurately compute the energy and costs internally.

+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/toc_general.html b/pages/user_guides/toc_general.html new file mode 100644 index 0000000..1ee32ee --- /dev/null +++ b/pages/user_guides/toc_general.html @@ -0,0 +1,234 @@ + + + + + + + + + +General | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

General

+

Crafted user guides covering a specific topic. These are mostly used to explain advanced topics, that are too complex to fully explain in a tutorial, or are linked as “dive deeper” options to read up on something.

+
+

Contents

+ +
+
+

Format & Structure

+

Currently, the general topic user guides do not follow a specific format.

+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/pages/user_guides/toc_qna.html b/pages/user_guides/toc_qna.html new file mode 100644 index 0000000..b839a2b --- /dev/null +++ b/pages/user_guides/toc_qna.html @@ -0,0 +1,281 @@ + + + + + + + + + +Q & A | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+

Q & A

+

These are based on previous questions that users had and discussions that occurred to clarify. They can be used to point users to for any questions that repeatedly occur.

+
+

Contents

+ +
+
+

Assisted writing

+

To simplify the process of converting these interactions into actually usable user guides for the future, one may apply a LLM of their choice. If doing that, the following base prompt might be helpful (tip: hover your mouse & one-click copy in the top right):

+
You are a seasoned developer and work on energy system optimization models; write a short and concise tutorial / user-guide based on the interaction of a user with our support and Q&A team, that covers the discussed topic and addresses the initial problem or misunderstanding. Make sure to keep it short. Use simple and generally understandable wording. You can assume basic familiarity with programming, especially with Python. Output everything in markdown format. Start the tutorial with a proper short first-level header, followed by a "virtual question" that a user might ask themself. Base that question on the submitted information - no need to repeat any question directly verbatim. Remove all names from your answer if there are any. Header texts should start with an upper case letter, but use proper English case (mostly lower) for all other words.
+
+Background information is available in the following documentation: https://ait-energy.github.io/iesopt/
+
+The exchange below in triple quotes is the chat that you can use:
+
+"""
+... add stuff here ...
+"""
+
+
+

Make sure to read over the returned text and properly check it for various commonly occurring issues. Proceed with caution …

+
+
+

Format & Structure

+
+

Only loosely defined guidelines, feel free to do what’s best for the docs.

+
+

These user guides roughly follow the following structure:

+
# Title header
+
+## Intro
+
+**Question:**  
+> I'm moving from hourly to daily time steps in my IESopt model. How should I adjust my input data, especially capacities and costs, to ensure accurate results? Should I express capacities as energy per time step (e.g., kWh/day) instead of power (kW)?
+
+**Answer:**  
+> When working with IESopt models, it's crucial to understand how the model interprets units of power and energy, especially when changing the duration of your time steps (snapshots). Here's how to approach this:
+
+## Details
+
+_... a more detailed answer and further explanations go here ..._
+
+## Summary
+
+_... a summary goes here ..._
+
+_... often this is the place for further notes, comments, key takeaways ..._
+
+### Conclusion
+
+_... and finally a 1-3 sentence conclusion ..._
+
+
+
+
+
+
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 0000000..2ac2563 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,271 @@ + + + + + + + + + Python Module Index | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+ +
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 0000000..a5bf26f --- /dev/null +++ b/search.html @@ -0,0 +1,278 @@ + + + + + + + + + Search | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + +
+ Skip to content +
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ Please activate Javascript to enable searching the documentation.
+
+
+
+ +
+
+
+
+
+

© AIT Austrian Institute of Technology GmbH Built with Sphinx 8.1.3

+
+
+
+
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000..612e6ba --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"1.x.y to 2.0.0": [[6, "x-y-to-2-0-0"]], "@check": [[8, "check"]], "@config": [[8, "config"]], "@critical": [[8, "critical"]], "@profile": [[8, "profile"]], "A first model": [[3, null]], "API Reference": [[7, "api-reference"], [8, "api-reference"], [9, "api-reference"], [10, "api-reference"], [11, "api-reference"]], "Access using get(...)": [[1, "access-using-get"]], "Accessing model results: Objectives": [[1, "accessing-model-results-objectives"]], "Accessing model results: Variables": [[1, "accessing-model-results-variables"]], "Accounting for different configurations": [[29, "accounting-for-different-configurations"]], "Addons": [[28, null]], "Aggregated units": [[22, null]], "Alternative approach with node_to": [[38, "alternative-approach-with-node-to"]], "Arbitrary packages": [[12, "arbitrary-packages"]], "Assets": [[7, null]], "Assisted writing": [[41, "assisted-writing"]], "BESS": [[30, "bess"]], "Basic Examples": [[20, null], [21, null], [22, null]], "Best practices": [[31, null]], "Building locally": [[5, "building-locally"]], "CHP": [[30, "chp"]], "CPLEX": [[34, "cplex"]], "Calling into Julia": [[1, "calling-into-julia"]], "Carrier": [[8, "carrier"]], "Catching errors": [[30, "catching-errors"]], "Changes to addons": [[6, "changes-to-addons"]], "Changes to examples, and more": [[6, "changes-to-examples-and-more"]], "Changes to keyword arguments": [[6, "changes-to-keyword-arguments"]], "Changes to result extraction": [[6, "changes-to-result-extraction"]], "Changes to the top-level config": [[6, "changes-to-the-top-level-config"]], "Checking power/energy units": [[39, "checking-power-energy-units"]], "Citing IESopt": [[0, "citing-iesopt"]], "Code Style and Guidelines": [[5, "code-style-and-guidelines"]], "Code formatting and linting": [[5, "code-formatting-and-linting"]], "Collective results": [[1, "collective-results"]], "Common errors": [[32, null]], "Common misconception": [[37, "common-misconception"]], "Complex COP configurations": [[29, "complex-cop-configurations"]], "Components": [[23, null]], "Conclusion": [[29, "conclusion"], [36, "conclusion"], [37, "conclusion"], [39, "conclusion"]], "Configuration": [[12, null]], "Configuring snapshots": [[39, "configuring-snapshots"]], "Connection": [[18, null]], "Constraint safety": [[18, null], [20, null], [20, null]], "Constraints": [[18, "constraints"], [19, "constraints"], [20, "constraints"], [21, "constraints"], [22, "constraints"]], "Contents": [[24, "contents"], [35, "contents"], [40, "contents"], [41, "contents"]], "Contributing": [[5, null], [26, "contributing"], [27, "contributing"]], "CoreTemplate": [[8, "coretemplate"]], "Costs are per unit of energy": [[39, "costs-are-per-unit-of-energy"]], "Costs for flows in both directions": [[18, null]], "Creating citation badges": [[26, "creating-citation-badges"], [27, "creating-citation-badges"]], "Custom constraint": [[37, "custom-constraint"]], "Decision": [[19, null]], "Definition: Energy carrier": [[31, null]], "Detailed reference": [[18, "detailed-reference"], [19, "detailed-reference"], [20, "detailed-reference"], [21, "detailed-reference"], [22, "detailed-reference"]], "Details": [[36, "details"], [38, "details"], [39, "details"]], "Details: Result extraction modes": [[25, null]], "Different projects": [[0, "different-projects"]], "Direct access": [[1, "direct-access"]], "During package load": [[32, "during-package-load"]], "Energy carriers": [[3, "energy-carriers"]], "Example": [[39, "example"]], "Example: node_from": [[38, "example-node-from"]], "Expand to show citation": [[27, null], [27, null]], "Explanation": [[36, "explanation"]], "Expression": [[8, "expression"]], "Expressions": [[18, "expressions"], [19, "expressions"], [20, "expressions"], [21, "expressions"], [22, "expressions"]], "Extracting model results": [[3, "extracting-model-results"]], "Extracting results": [[42, null]], "Extracting results: Part I": [[1, null]], "Extracting results: Part II": [[2, null]], "File results: Access using get(...)": [[1, "file-results-access-using-get"]], "File results: Collective results": [[1, "file-results-collective-results"]], "File results: Direct access": [[1, "file-results-direct-access"]], "File results: Using to_dict(...)": [[1, "file-results-using-to-dict"]], "File results: Using to_pandas(...)": [[1, "file-results-using-to-pandas"]], "Filtering components": [[2, "filtering-components"]], "Final config file": [[3, "final-config-file"]], "Finalizing the docstring": [[29, "finalizing-the-docstring"]], "Forcing a solver executable version": [[12, "forcing-a-solver-executable-version"]], "Format & Structure": [[40, "format-structure"], [41, "format-structure"]], "Functions": [[7, "functions"], [8, "functions"], [9, "functions"], [10, "functions"], [11, "functions"]], "General": [[40, null]], "Getting started": [[0, "getting-started"]], "Good to know": [[24, "good-to-know"]], "Gurobi": [[34, "gurobi"]], "Gurobi (NumFocus)": [[34, "gurobi-numfocus"]], "Gurobi (fallback)": [[34, "gurobi-fallback"]], "Heat Highway - Heat Transmission Network Design Optimization and Robustness Analysis for a Case Study in Tyrol - Methodology": [[27, "heat-highway-heat-transmission-network-design-optimization-and-robustness-analysis-for-a-case-study-in-tyrol-methodology"]], "HeatPump": [[30, "heatpump"]], "HiGHS": [[34, "highs"]], "How to access this constraint?": [[18, null], [19, null], [19, null], [19, null], [19, null], [20, null], [20, null], [20, null], [21, null], [22, null], [22, null], [22, null], [22, null], [22, null], [22, null]], "How to access this expression?": [[18, null], [18, null], [18, null], [20, null], [21, null]], "How to access this objective?": [[18, null], [19, null], [19, null], [19, null], [21, null], [22, null], [22, null], [22, null]], "How to access this variable?": [[18, null], [19, null], [19, null], [19, null], [20, null], [20, null], [21, null], [22, null], [22, null], [22, null], [22, null]], "IESopt \u2013 an Integrated Energy System Optimization framework.": [[0, null]], "IESopt.jl": [[4, null], [12, "iesopt-jl"]], "Implementation": [[36, "implementation"]], "Important versions": [[12, "important-versions"]], "Improvements": [[36, "improvements"]], "Improving the documentation": [[5, "improving-the-documentation"]], "Installation": [[0, "installation"]], "Installing iesopt": [[0, "installing-iesopt"]], "Integrated Energy System Optimization": [[0, null]], "InternalData": [[8, "internaldata"]], "Intro": [[35, null], [37, "intro"], [38, "intro"], [39, "intro"]], "JuMP": [[14, null]], "Julia": [[8, null]], "Julia packages": [[12, "julia-packages"]], "Keep capacities in power units": [[39, "keep-capacities-in-power-units"]], "Key takeaways": [[39, "key-takeaways"]], "Learning more about managing dependencies with Poetry": [[0, null]], "Linking components": [[33, null]], "List of references": [[26, "list-of-references"], [27, "list-of-references"]], "Loading results from file": [[1, "loading-results-from-file"]], "Looking at (a) specific component(s)": [[2, "looking-at-a-specific-component-s"]], "Macros": [[7, "macros"], [8, "macros"], [9, "macros"], [10, "macros"], [11, "macros"]], "Making changes": [[5, "making-changes"]], "Manually running checks": [[5, null]], "Model": [[15, null]], "Model components": [[3, "model-components"]], "Model configuration": [[3, "model-configuration"]], "Naive static charging": [[37, "naive-static-charging"]], "Next steps": [[29, "next-steps"]], "Node": [[20, null]], "Non-registered packages": [[12, "non-registered-packages"]], "Non-temporal results": [[2, "non-temporal-results"]], "NonEmptyExpressionValue": [[8, "nonemptyexpressionvalue"]], "NonEmptyNumericalExpressionValue": [[8, "nonemptynumericalexpressionvalue"]], "NonEmptyScalarExpressionValue": [[8, "nonemptyscalarexpressionvalue"]], "Note on --keep-going": [[5, null]], "Notes": [[8, "notes"], [36, "notes"]], "Objectives": [[18, "objectives"], [19, "objectives"], [20, "objectives"], [21, "objectives"], [22, "objectives"]], "Optimizing the Domestic Production and Infrastructure for Green Hydrogen in Austria for 2030": [[27, "optimizing-the-domestic-production-and-infrastructure-for-green-hydrogen-in-austria-for-2030"]], "Optional parameter and sizing decision": [[29, "optional-parameter-and-sizing-decision"]], "OptionalScalarExpressionValue": [[8, "optionalscalarexpressionvalue"]], "Options": [[12, "options"]], "Other changes": [[6, "other-changes"]], "Output-dependent marginal costs": [[36, null]], "Overview": [[0, "overview"], [7, "overview"], [9, "overview"], [10, "overview"], [11, "overview"], [18, "overview"], [19, "overview"], [20, "overview"], [21, "overview"], [22, "overview"], [26, "overview"], [27, "overview"]], "PV": [[30, "pv"]], "Parameters": [[18, "parameters"], [19, "parameters"], [20, "parameters"], [21, "parameters"], [22, "parameters"]], "Passive charging of an underground heat storage": [[37, null]], "Practical example": [[38, "practical-example"], [39, "practical-example"]], "Precompiling": [[0, "precompiling"]], "Preparation": [[29, "preparation"]], "Profile": [[21, null]], "Projects": [[26, null]], "Publications": [[27, null]], "Python": [[13, null]], "Q & A": [[41, null]], "Recommended Configurations": [[34, "recommended-configurations"]], "Recommended solution": [[37, "recommended-solution"]], "Reducing overhead": [[0, null]], "Reporting issues": [[5, "reporting-issues"]], "Resampling time series data": [[39, "resampling-time-series-data"]], "Results": [[16, null]], "ResultsDuckDB": [[9, null]], "ResultsJLD2": [[10, null]], "Running tests": [[5, "running-tests"]], "Running the optimization": [[3, "running-the-optimization"]], "Running tox tests": [[5, "running-tox-tests"]], "Selecting a specific component": [[2, "selecting-a-specific-component"]], "Selecting all components containing \u201cplant\u201d": [[2, "selecting-all-components-containing-plant"]], "Selecting specific components": [[2, "selecting-specific-components"]], "Separate .jl files": [[30, "separate-jl-files"]], "Separate energy carriers": [[31, "separate-energy-carriers"]], "Setting up an environment": [[0, "setting-up-an-environment"]], "Setting up the development environment": [[5, "setting-up-the-development-environment"]], "Sign convention for time series": [[38, null]], "Snapshot": [[8, "snapshot"]], "Solvers": [[12, "solvers"], [34, null]], "Some examples": [[30, "some-examples"]], "Steps to implement": [[37, "steps-to-implement"]], "Storage units are in energy units": [[39, "storage-units-are-in-energy-units"]], "Submitting a pull request (PR)": [[5, "submitting-a-pull-request-pr"]], "Summary": [[36, "summary"], [37, "summary"], [38, "summary"], [39, "summary"]], "Tags": [[6, "tags"]], "Template": [[26, "template"], [27, "template"]], "Templates": [[43, null]], "Templates: Part I": [[29, null]], "Templates: Part II": [[30, null]], "Temporal results": [[2, "temporal-results"]], "The basic structure": [[29, "the-basic-structure"]], "The finalize function": [[29, "the-finalize-function"]], "The problem": [[37, "the-problem"]], "Time resolution & power vs. energy": [[39, null]], "Time step duration is handled internally": [[39, "time-step-duration-is-handled-internally"]], "Top-level config": [[25, null]], "Types": [[7, "types"], [8, "types"], [9, "types"], [10, "types"], [11, "types"]], "Understanding node_from and node_to": [[38, "understanding-node-from-and-node-to"]], "Unit": [[22, null]], "Updating": [[6, null]], "Using this documentation": [[0, "using-this-documentation"]], "Using to_dict(...)": [[1, "using-to-dict"]], "Using to_pandas(...)": [[1, "using-to-pandas"]], "Utilities": [[11, null], [17, null]], "Validation": [[29, "validation"]], "Variables": [[18, "variables"], [19, "variables"], [20, "variables"], [21, "variables"], [22, "variables"]], "Virtual": [[8, "virtual"]], "What should I look at?": [[2, "what-should-i-look-at"]], "Which result extraction should I use?": [[1, "which-result-extraction-should-i-use"]], "Why does this happen?": [[38, "why-does-this-happen"]], "YAML": [[24, null]], "Yet another markup language?": [[24, "yet-another-markup-language"]], "_csv_config": [[25, "csv-config"]], "access": [[8, "access"]], "adapt_min_to_availability": [[22, "adapt-min-to-availability"]], "add_term_to_objective!": [[8, "add-term-to-objective"]], "addons": [[25, "addons"]], "annuity": [[11, "annuity"]], "aux_value": [[21, "aux-value"]], "availability": [[22, "availability"]], "availability_factor": [[22, "availability-factor"]], "build!": [[8, "build"]], "build_priority": [[18, "build-priority"], [19, "build-priority"], [20, "build-priority"], [21, "build-priority"], [22, "build-priority"]], "capacity": [[18, "capacity"], [22, "capacity"]], "carrier": [[18, "carrier"], [20, "carrier"], [21, "carrier"]], "carriers": [[25, "carriers"]], "components": [[25, "components"]], "compute_IIS": [[8, "compute-iis"]], "config": [[6, "config"], [25, "config"]], "conversion": [[22, "conversion"], [22, "id1"]], "conversion_at_min": [[22, "conversion-at-min"]], "conversion_bounds": [[22, "conversion-bounds"]], "cost": [[18, "cost"], [18, "id1"], [19, "cost"], [21, "cost"], [21, "id2"]], "delay": [[18, "delay"]], "enable_ramp_down": [[22, "enable-ramp-down"]], "enable_ramp_up": [[22, "enable-ramp-up"]], "extract_result": [[8, "extract-result"]], "files": [[25, "files"]], "fixed": [[19, "fixed"], [19, "id2"], [19, "id3"]], "fixed_cost": [[19, "fixed-cost"]], "fixed_value": [[19, "fixed-value"]], "flow": [[18, "flow"]], "flow_bounds": [[18, "flow-bounds"]], "general": [[25, "general"]], "generate!": [[8, "generate"]], "get_T": [[8, "get-t"]], "get_component": [[8, "get-component"]], "get_components": [[8, "get-components"]], "get_global": [[8, "get-global"]], "get_path": [[7, "get-path"]], "get_value_at": [[8, "get-value-at"]], "get_version": [[8, "get-version"]], "has_state": [[20, "has-state"]], "in": [[18, "in"]], "injection": [[20, "injection"]], "inputs": [[22, "inputs"]], "internal": [[8, "internal"]], "is_on_before": [[22, "is-on-before"]], "ison": [[22, "ison"], [22, "id2"]], "last_state": [[20, "last-state"]], "lb": [[18, "lb"], [19, "lb"], [21, "lb"]], "load_results": [[10, "load-results"]], "loss": [[18, "loss"]], "loss_mode": [[18, "loss-mode"]], "make_base_name": [[8, "make-base-name"]], "marginal_cost": [[22, "marginal-cost"], [22, "id5"]], "min_conversion": [[22, "min-conversion"]], "min_off_time": [[22, "min-off-time"]], "min_on_time": [[22, "min-on-time"]], "min_onoff_time": [[22, "min-onoff-time"]], "mode": [[19, "mode"], [21, "mode"]], "multiobjective": [[25, "multiobjective"]], "name": [[25, "name"]], "nodal_balance": [[20, "nodal-balance"]], "nodalbalance": [[20, "nodalbalance"]], "node_from": [[18, "node-from"], [21, "node-from"]], "node_to": [[18, "node-to"], [21, "node-to"]], "objectives": [[25, "objectives"]], "off_time_before": [[22, "off-time-before"]], "on_time_before": [[22, "on-time-before"]], "optimization": [[25, "optimization"]], "optimize!": [[8, "optimize"]], "out": [[18, "out"]], "outputs": [[22, "outputs"]], "overview": [[8, "overview"]], "pack": [[8, "pack"]], "parameters": [[6, "parameters"], [25, "parameters"]], "parse!": [[8, "parse"]], "paths": [[25, "paths"]], "performance": [[25, "performance"]], "pf_flow": [[18, "pf-flow"]], "pf_theta": [[20, "pf-theta"]], "problem_type": [[25, "problem-type"]], "ramp": [[22, "ramp"], [22, "id3"]], "ramp_cost": [[22, "ramp-cost"]], "ramp_down_cost": [[22, "ramp-down-cost"]], "ramp_down_limit": [[22, "ramp-down-limit"]], "ramp_limit": [[22, "ramp-limit"]], "ramp_up_cost": [[22, "ramp-up-cost"]], "ramp_up_limit": [[22, "ramp-up-limit"]], "register_objective!": [[8, "register-objective"]], "results": [[25, "results"]], "run": [[8, "run"]], "safe_close_filelogger": [[8, "safe-close-filelogger"]], "set_global!": [[8, "set-global"]], "snapshots": [[25, "snapshots"]], "soft_constraints": [[25, "soft-constraints"]], "solver": [[25, "solver"]], "sos": [[19, "sos"], [19, "id1"], [19, "id4"]], "sos1": [[19, "sos1"]], "sos2": [[19, "sos2"]], "sos_value": [[19, "sos-value"]], "startup": [[22, "startup"], [22, "id4"]], "startup_cost": [[22, "startup-cost"], [22, "id6"]], "state": [[20, "state"]], "state_bounds": [[20, "state-bounds"]], "state_cyclic": [[20, "state-cyclic"]], "state_final": [[20, "state-final"]], "state_initial": [[20, "state-initial"]], "state_lb": [[20, "state-lb"]], "state_percentage_loss": [[20, "state-percentage-loss"]], "state_ub": [[20, "state-ub"]], "sum_window_size": [[20, "sum-window-size"]], "sum_window_step": [[20, "sum-window-step"]], "timespan": [[11, "timespan"]], "to_table": [[8, "to-table"]], "ub": [[18, "ub"], [19, "ub"], [21, "ub"]], "unit_commitment": [[22, "unit-commitment"]], "unit_count": [[22, "unit-count"]], "unpack": [[8, "unpack"]], "validate": [[8, "validate"]], "value": [[19, "value"], [19, "id5"], [21, "value"], [21, "id1"]], "value_bounds": [[21, "value-bounds"]], "verbosity": [[25, "verbosity"]], "version": [[25, "version"]], "write_to_file": [[8, "write-to-file"]], "yearspan": [[11, "yearspan"]]}, "docnames": ["index", "notebooks/custom_results_1", "notebooks/custom_results_2", "notebooks/first_model", "pages/dev/core", "pages/dev/general", "pages/dev/updating", "pages/manual/julia/assets", "pages/manual/julia/index", "pages/manual/julia/resultsduckdb", "pages/manual/julia/resultsjld2", "pages/manual/julia/utilities", "pages/manual/python/configuration", "pages/manual/python/index", "pages/manual/python/jump", "pages/manual/python/model", "pages/manual/python/results", "pages/manual/python/util", "pages/manual/yaml/core/connection", "pages/manual/yaml/core/decision", "pages/manual/yaml/core/node", "pages/manual/yaml/core/profile", "pages/manual/yaml/core/unit", "pages/manual/yaml/core_components", "pages/manual/yaml/index", "pages/manual/yaml/top_level", "pages/references/projects", "pages/references/publications", "pages/tutorials/addons", "pages/tutorials/templates_1", "pages/tutorials/templates_2", "pages/user_guides/general/best_practice", "pages/user_guides/general/common_errors", "pages/user_guides/general/linking_components", "pages/user_guides/general/solvers", "pages/user_guides/index", "pages/user_guides/qna/dynamic_marginal_costs", "pages/user_guides/qna/passive_charging", "pages/user_guides/qna/profiles_sign", "pages/user_guides/qna/snapshot_duration", "pages/user_guides/toc_general", "pages/user_guides/toc_qna", "tocs/tutorials_extracting_results", "tocs/tutorials_templates"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["index.md", "notebooks/custom_results_1.ipynb", "notebooks/custom_results_2.ipynb", "notebooks/first_model.ipynb", "pages/dev/core.md", "pages/dev/general.md", "pages/dev/updating.md", "pages/manual/julia/assets.md", "pages/manual/julia/index.md", "pages/manual/julia/resultsduckdb.md", "pages/manual/julia/resultsjld2.md", "pages/manual/julia/utilities.md", "pages/manual/python/configuration.md", "pages/manual/python/index.md", "pages/manual/python/jump.md", "pages/manual/python/model.md", "pages/manual/python/results.md", "pages/manual/python/util.md", "pages/manual/yaml/core/connection.md", "pages/manual/yaml/core/decision.md", "pages/manual/yaml/core/node.md", "pages/manual/yaml/core/profile.md", "pages/manual/yaml/core/unit.md", "pages/manual/yaml/core_components.md", "pages/manual/yaml/index.md", "pages/manual/yaml/top_level.md", "pages/references/projects.md", "pages/references/publications.md", "pages/tutorials/addons.md", "pages/tutorials/templates_1.md", "pages/tutorials/templates_2.md", "pages/user_guides/general/best_practice.md", "pages/user_guides/general/common_errors.md", "pages/user_guides/general/linking_components.md", "pages/user_guides/general/solvers.md", "pages/user_guides/index.md", "pages/user_guides/qna/dynamic_marginal_costs.md", "pages/user_guides/qna/passive_charging.md", "pages/user_guides/qna/profiles_sign.md", "pages/user_guides/qna/snapshot_duration.md", "pages/user_guides/toc_general.md", "pages/user_guides/toc_qna.md", "tocs/tutorials_extracting_results.md", "tocs/tutorials_templates.md"], "indexentries": {"compute_iis() (iesopt.model method)": [[15, "iesopt.Model.compute_iis", false]], "core (iesopt.model property)": [[15, "iesopt.Model.core", false]], "data (iesopt.model property)": [[15, "iesopt.Model.data", false]], "empty (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.EMPTY", false]], "entries() (iesopt.results method)": [[16, "iesopt.Results.entries", false]], "examples() (in module iesopt)": [[13, "iesopt.examples", false]], "extract_result() (iesopt.model method)": [[15, "iesopt.Model.extract_result", false]], "failed_generate (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.FAILED_GENERATE", false]], "failed_optimize (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.FAILED_OPTIMIZE", false]], "generate() (iesopt.model method)": [[15, "iesopt.Model.generate", false]], "generated (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.GENERATED", false]], "get_component() (iesopt.model method)": [[15, "iesopt.Model.get_component", false]], "get_components() (iesopt.model method)": [[15, "iesopt.Model.get_components", false]], "get_constraint() (iesopt.model method)": [[15, "iesopt.Model.get_constraint", false]], "get_jl_docstr() (in module iesopt)": [[17, "iesopt.get_jl_docstr", false]], "get_variable() (iesopt.model method)": [[15, "iesopt.Model.get_variable", false]], "iesopt": [[13, "module-iesopt", false]], "iesopt (in module iesopt)": [[13, "iesopt.IESopt", false]], "infeasible (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.INFEASIBLE", false]], "infeasible_or_unbounded (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.INFEASIBLE_OR_UNBOUNDED", false]], "internal (iesopt.model property)": [[15, "iesopt.Model.internal", false]], "julia (in module iesopt)": [[17, "iesopt.julia", false]], "jump (in module iesopt)": [[14, "iesopt.JuMP", false]], "jump_dual() (in module iesopt)": [[14, "iesopt.jump_dual", false]], "jump_reduced_cost() (in module iesopt)": [[14, "iesopt.jump_reduced_cost", false]], "jump_shadow_price() (in module iesopt)": [[14, "iesopt.jump_shadow_price", false]], "jump_value() (in module iesopt)": [[14, "iesopt.jump_value", false]], "make_example() (in module iesopt)": [[13, "iesopt.make_example", false]], "model (class in iesopt)": [[15, "iesopt.Model", false]], "modelstatus (class in iesopt.model)": [[15, "iesopt.model.ModelStatus", false]], "module": [[13, "module-iesopt", false]], "nvar() (iesopt.model method)": [[15, "iesopt.Model.nvar", false]], "objective_value (iesopt.model property)": [[15, "iesopt.Model.objective_value", false]], "optimal (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.OPTIMAL", false]], "optimal_locally (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.OPTIMAL_LOCALLY", false]], "optimize() (iesopt.model method)": [[15, "iesopt.Model.optimize", false]], "other (iesopt.model.modelstatus attribute)": [[15, "iesopt.model.ModelStatus.OTHER", false]], "query_available_results() (iesopt.results method)": [[16, "iesopt.Results.query_available_results", false]], "results (class in iesopt)": [[16, "iesopt.Results", false]], "results (iesopt.model property)": [[15, "iesopt.Model.results", false]], "run() (in module iesopt)": [[13, "iesopt.run", false]], "status (iesopt.model property)": [[15, "iesopt.Model.status", false]], "symbol() (in module iesopt)": [[17, "iesopt.Symbol", false]], "to_dict() (iesopt.results method)": [[16, "iesopt.Results.to_dict", false]], "to_pandas() (iesopt.results method)": [[16, "iesopt.Results.to_pandas", false]], "write_to_file() (iesopt.model method)": [[15, "iesopt.Model.write_to_file", false]]}, "objects": {"": [[13, 0, 0, "-", "iesopt"]], "iesopt": [[13, 1, 1, "", "IESopt"], [14, 1, 1, "", "JuMP"], [15, 2, 1, "", "Model"], [16, 2, 1, "", "Results"], [17, 5, 1, "", "Symbol"], [13, 5, 1, "", "examples"], [17, 5, 1, "", "get_jl_docstr"], [17, 1, 1, "", "julia"], [14, 5, 1, "", "jump_dual"], [14, 5, 1, "", "jump_reduced_cost"], [14, 5, 1, "", "jump_shadow_price"], [14, 5, 1, "", "jump_value"], [13, 5, 1, "", "make_example"], [13, 5, 1, "", "run"]], "iesopt.Model": [[15, 3, 1, "", "compute_iis"], [15, 4, 1, "", "core"], [15, 4, 1, "", "data"], [15, 3, 1, "", "extract_result"], [15, 3, 1, "", "generate"], [15, 3, 1, "", "get_component"], [15, 3, 1, "", "get_components"], [15, 3, 1, "", "get_constraint"], [15, 3, 1, "", "get_variable"], [15, 4, 1, "", "internal"], [15, 3, 1, "", "nvar"], [15, 4, 1, "", "objective_value"], [15, 3, 1, "", "optimize"], [15, 4, 1, "", "results"], [15, 4, 1, "", "status"], [15, 3, 1, "", "write_to_file"]], "iesopt.Results": [[16, 3, 1, "", "entries"], [16, 3, 1, "", "query_available_results"], [16, 3, 1, "", "to_dict"], [16, 3, 1, "", "to_pandas"]], "iesopt.model": [[15, 2, 1, "", "ModelStatus"]], "iesopt.model.ModelStatus": [[15, 6, 1, "", "EMPTY"], [15, 6, 1, "", "FAILED_GENERATE"], [15, 6, 1, "", "FAILED_OPTIMIZE"], [15, 6, 1, "", "GENERATED"], [15, 6, 1, "", "INFEASIBLE"], [15, 6, 1, "", "INFEASIBLE_OR_UNBOUNDED"], [15, 6, 1, "", "OPTIMAL"], [15, 6, 1, "", "OPTIMAL_LOCALLY"], [15, 6, 1, "", "OTHER"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "data", "Python data"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "property", "Python property"], "5": ["py", "function", "Python function"], "6": ["py", "attribute", "Python attribute"]}, "objtypes": {"0": "py:module", "1": "py:data", "2": "py:class", "3": "py:method", "4": "py:property", "5": "py:function", "6": "py:attribute"}, "terms": {"": [0, 1, 3, 6, 8, 12, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 29, 34, 36, 37, 38, 39, 41, 42], "0": [0, 1, 2, 3, 5, 8, 11, 12, 18, 19, 20, 21, 22, 25, 29, 33, 34, 37, 38, 39], "00": [1, 2], "00000": [1, 2], "000000": [1, 2], "000000e": 1, "01": [20, 37], "01_basic_single_nod": 15, "02": 2, "02526316": 2, "026316": 2, "027143": 2, "034286": 2, "035000": 2, "05": [11, 18], "054286": 2, "06": 0, "07": 2, "070000": 2, "071429": 2, "080000": 2, "080332": 2, "08_basic_invest": 2, "09": 25, "1": [0, 1, 2, 3, 8, 11, 12, 18, 19, 20, 22, 24, 25, 29, 33, 34, 37, 38, 39, 41], "10": [2, 11, 22, 26, 27, 29, 34, 36, 39], "100": [3, 5, 18, 21, 22, 25, 31, 36, 38, 39], "1000": [11, 19], "10000": 1, "101": 2, "103": 27, "104": 27, "11": [0, 12], "110223e": 1, "12": [0, 3, 12, 39], "120": 39, "123": 34, "1234": 34, "125": 25, "13052632": 2, "13600": 31, "15": [3, 25, 39], "150": [21, 38], "16": [0, 1, 2], "160000": 2, "168": [6, 25], "172": 5, "174515": 2, "1745152354571": 1, "19": 0, "190": 18, "19552632": 2, "1e": 34, "1e6": 25, "1e9": 34, "2": [0, 1, 2, 3, 8, 12, 18, 22, 29, 33, 34, 38, 39], "20": [22, 33, 39], "200": [18, 34], "202": 25, "2021": 0, "2022": 34, "2023": 27, "2024": 0, "2024_09_11_09520837": 25, "22": 34, "220446e": 1, "23": 12, "24": [3, 25, 39], "240": 39, "24h": 39, "25": 25, "250": 3, "278": 27, "279": 27, "28842105": 2, "2fname": [26, 27], "3": [0, 1, 2, 3, 12, 22, 24, 29, 34, 41], "30": 21, "303": 2, "314159": 22, "31578947": 1, "33": 5, "35": 22, "360": 39, "365": 39, "38": 3, "4": [1, 2, 3, 5, 22, 25, 34], "40": [3, 22, 33], "400": 39, "417": 5, "42105263": [1, 2], "440892e": 1, "44631579": 2, "45": 2, "450714": 2, "46149584": 1, "47": 22, "473684": [1, 2], "47368421": [1, 2], "473684210526316": 1, "48": 0, "48_custom_result": [1, 2], "4th": 27, "5": [1, 3, 5, 8, 11, 18, 22, 25, 34, 36], "50": [3, 20, 22, 38], "50221607": 1, "52631579": [1, 2], "55": [22, 33], "571429": 2, "57894737": 1, "59": 5, "6": [1, 2, 12, 34], "60": 25, "607143": 2, "63157895": 1, "65374": 2, "656509695290859": 1, "6565097": 1, "65651": 1, "656510": 1, "65684211": 2, "7": [1, 5, 22, 24, 25], "70": 22, "70000": 1, "70947368": 2, "72631579": 2, "75": 38, "750": 2, "750000": 2, "8": [2, 3, 5, 34], "80552632": 2, "84210526": 1, "8760": 11, "88": 5, "9": [0, 22], "90052632": 2, "917": 2, "93": 25, "947368": [1, 2], "94736842": [1, 2], "9473684210526315": 1, "95": [3, 18, 36], "9500": 3, "981": [1, 2], "997229916897507": 1, "99722992": 1, "99723": 1, "997230": 1, "9th": 27, "A": [0, 1, 5, 6, 8, 12, 13, 18, 19, 20, 21, 22, 23, 25, 27, 29, 33, 35, 37, 38, 39], "And": [0, 1, 15, 38], "As": [1, 2, 6, 25, 27, 29], "At": [25, 38], "Be": [8, 25], "But": [1, 2, 29, 36, 37], "By": [36, 37, 38, 39], "For": [0, 1, 3, 6, 8, 12, 18, 22, 25, 27, 29, 31, 34, 36], "INTO": 3, "If": [0, 2, 3, 5, 7, 8, 9, 10, 11, 15, 18, 19, 20, 21, 22, 25, 26, 27, 29, 32, 36, 37, 38, 39, 41], "In": [2, 3, 5, 12, 22, 24, 25, 27, 29, 31, 38], "It": [0, 1, 2, 3, 8, 15, 18, 20, 21, 22, 23, 24, 25, 29, 31, 37, 39], "NO": 21, "NOT": [1, 2], "No": [24, 39], "Not": [18, 39], "One": [29, 31], "Or": 2, "THERE": 3, "That": [2, 3, 6, 22, 29, 39], "The": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17, 18, 20, 21, 22, 24, 25, 27, 33, 34, 36, 38, 39, 41, 43], "Their": [8, 23], "Then": [0, 1], "There": [6, 21, 23, 25, 37], "These": [6, 8, 12, 22, 23, 25, 29, 31, 35, 40, 41], "To": [0, 1, 2, 4, 5, 6, 8, 12, 22, 23, 26, 27, 28, 29, 30, 36, 41], "Will": 13, "With": [0, 6], "_": [18, 20, 21, 22, 27, 29, 41], "_1": 20, "__component__": 8, "__init__": 5, "_build": 5, "_capac": 29, "_connection_var_flow": 18, "_convers": 29, "_corecompon": 8, "_csv_config": 24, "_filelogg": 8, "_id": 8, "_iesoptdata": 8, "_input": 29, "_invest": 29, "_jll": 12, "_off": 22, "_on": 22, "_profil": 8, "_profile_con_value_bound": 21, "_scalarinput": 8, "_string": 8, "_t": [18, 20, 21, 22], "_time": 22, "_to": 29, "abbrevi": 8, "abil": 22, "abl": [0, 1, 2, 3, 6, 8, 12, 24, 29], "about": [1, 3, 6, 8, 25, 29, 33], "abov": [1, 3, 6, 22, 25, 26, 27, 29, 36, 37], "absolut": [8, 18], "abstract": [0, 27, 31], "abstractstr": 8, "ac": [26, 27], "accept": [3, 20, 25], "access": [2, 6, 12, 13, 15, 23, 25, 29, 33, 42], "accessor": 6, "accomplish": 0, "accord": [12, 31], "accordingli": [5, 29], "account": [3, 5, 6, 22, 25, 37, 39, 43], "accur": [37, 38, 39, 41], "accuraci": 36, "achiev": [2, 33, 39], "act": [20, 23], "action": 8, "activ": [0, 6, 19, 22, 25], "actual": [0, 1, 2, 3, 6, 8, 12, 20, 21, 23, 24, 29, 31, 36, 41], "ad": [0, 3, 4, 5, 6, 8, 12, 18, 19, 20, 21, 25, 26, 29, 30, 36], "adapt": [0, 22], "add": [0, 3, 8, 12, 18, 19, 20, 21, 22, 25, 26, 27, 29, 33, 36, 37, 41], "add_passive_charg": 37, "added_": 29, "addit": [3, 8, 13, 22, 25, 29, 36, 37, 39], "addition": [18, 20, 21], "addon": [0, 7, 8, 18, 19, 20, 21, 22, 24, 29, 33, 36, 37], "address": [27, 41], "addyourlinkher": [26, 27], "adher": 5, "adjust": [3, 6, 37, 39, 41], "admonit": 27, "advanc": [1, 23, 25, 35, 37, 40], "advantag": 1, "aesthet": 25, "affect": 25, "affexpr": [8, 18, 20, 21], "affin": 8, "after": [0, 1, 2, 3, 5, 8, 13, 19, 22, 24, 25, 29, 33], "afterward": [29, 32], "again": [1, 3, 6, 18, 29], "against": 5, "aggfil": 34, "aggreg": 39, "ahead": 2, "ain": 24, "air": 20, "ait": [0, 12, 15, 26, 27, 41], "algorithm": [1, 20], "alia": 17, "align": [18, 20, 21, 22], "all": [0, 1, 3, 5, 6, 8, 12, 13, 15, 16, 18, 19, 20, 21, 22, 24, 25, 29, 39, 41], "alloc": 8, "allow": [3, 5, 6, 8, 18, 19, 20, 21, 22, 23, 25, 29, 33, 36, 37], "almost": [8, 23, 39], "along": 39, "alpha": [22, 25], "alphabet": [26, 27], "alphanumer": 25, "alreadi": [0, 1, 2, 6, 7, 15, 21, 22, 25, 26, 27, 29, 31, 37], "also": [1, 2, 3, 6, 8, 15, 17, 20, 21, 22, 23, 24, 25, 29, 37, 39], "alter": 39, "altern": [5, 25], "alwai": [1, 3, 5, 8, 18, 20, 27, 29, 36, 39], "ambient": 29, "ambigu": 24, "amount": [3, 11, 22, 38], "an": [1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 15, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 31, 33, 36, 38, 39, 41], "anaconda": 0, "analys": [1, 2, 11, 36], "analysi": [1, 8], "analyz": 8, "angl": 20, "ani": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 18, 19, 20, 21, 22, 23, 24, 25, 29, 31, 35, 36, 37, 39, 41], "annuit": 11, "annuiti": 8, "anoth": [0, 18, 22, 23, 29, 33], "answer": [2, 36, 37, 38, 39, 41], "anymor": 6, "anyth": 0, "anywai": 1, "apa": 27, "apa7": 0, "api": [0, 15], "append": 8, "appli": [0, 1, 5, 8, 16, 18, 19, 22, 26, 27, 29, 38, 41], "applic": [0, 12, 21, 22, 25, 37], "approach": [3, 19, 25, 26, 27, 29, 33, 36, 39, 41], "appropri": 37, "approxim": 25, "apr": 0, "ar": [0, 1, 2, 3, 5, 6, 8, 12, 15, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 31, 33, 34, 35, 38, 40, 41], "arbitrari": [6, 8, 15, 18, 21, 23, 25, 29], "arg": [8, 14], "arg1": 8, "arg2": 8, "argcheck": 8, "argu": 31, "argument": [7, 8, 10, 11, 13, 15, 16, 31], "around": [6, 18], "arrai": [1, 2, 8], "artifici": 37, "ask": [2, 41], "assert": [1, 2, 8], "asset": [0, 6, 8, 36], "asset_typ": 7, "assist": 19, "associ": [2, 3, 8, 19, 23], "assum": [0, 3, 11, 18, 19, 20, 21, 22, 25, 36, 37, 39, 41], "asymmetr": [3, 18], "atmospher": 21, "attach": [15, 33], "attempt": 37, "attent": 1, "attribut": [6, 25, 34], "austria": 6, "austrian": [0, 27], "author": 0, "auto": [6, 7, 8, 9, 10, 11, 18, 19, 20, 21, 22], "autom": 5, "automat": [0, 1, 2, 3, 5, 6, 7, 8, 12, 15, 18, 22, 25], "auxiliari": [20, 22, 25], "avail": [0, 2, 3, 8, 12, 13, 15, 16, 18, 37, 41], "avoid": 3, "awar": [8, 24, 25], "b": [1, 5], "back": [2, 6, 8], "backend": [6, 25], "background": 41, "balanc": [20, 23], "band": 36, "bar": [6, 25, 36], "barconvtol": 34, "barhomogen": 34, "barrier_convergetol": 34, "base": [0, 1, 3, 6, 7, 8, 9, 10, 11, 15, 18, 19, 20, 21, 22, 25, 27, 34, 35, 36, 37, 41], "base_2022_low": 25, "base_2022_low_t": 25, "base_nam": 8, "basic": [0, 1, 3, 12, 19, 23, 25, 37, 41, 43], "batteri": [6, 8, 20, 23], "becaus": [29, 33, 39], "beck": 27, "becom": [1, 29], "been": [15, 22, 25], "befor": [0, 2, 3, 5, 6, 8, 12, 18, 19, 20, 21, 22, 29, 33, 36], "beforehand": 15, "begin": [8, 18, 20, 21, 22, 29], "behavior": [25, 37], "behaviour": [18, 20, 22], "being": [0, 1, 2, 3, 18, 19, 20, 21, 22, 25, 31], "belong": 3, "below": [1, 3, 6, 8, 21, 22, 25, 26, 27, 36, 41], "bender": 19, "benefici": 37, "benefit": 0, "besid": [6, 21, 22, 23], "best": [0, 12, 22, 25, 29, 36, 40, 41], "beta": 22, "better": [6, 8, 25, 29], "between": [0, 1, 3, 6, 18, 20, 21, 23, 24, 25, 29, 39], "beyond": 0, "bibtex": 0, "bidirect": 18, "binari": [0, 3, 12, 19, 22], "bind": 8, "biogen": 27, "bit": 25, "blakcori": 27, "block": [0, 3, 8], "bne": 29, "boiler": 31, "book": 27, "bool": [8, 16, 25], "borehol": 37, "both": [2, 3, 8, 16, 22, 25, 31, 33, 38], "bought": 3, "bound": [3, 12, 18, 19, 20, 21, 22, 29, 37, 39], "boundari": [20, 21, 23], "branch": 5, "break": [6, 25], "broken": [5, 25], "bsa": 8, "bu": [20, 23], "bug": [5, 25], "bui": [3, 21], "build": [0, 3, 16, 18, 19, 20, 21, 22, 29], "build_": 2, "build_cach": 16, "build_ga": 2, "build_pipelin": 2, "build_prior": 33, "build_storag": 2, "builder": 5, "built": [5, 8, 18, 19, 20, 21, 22, 24, 25, 29, 33], "c": [1, 5, 16], "c_passive_charg": 37, "c_storag": 37, "cach": [1, 16], "calcul": [1, 11, 18, 22, 29, 39], "call": [3, 6, 8, 12, 15, 17, 23, 25, 29, 33, 42], "callabl": 16, "can": [0, 1, 2, 3, 5, 6, 8, 11, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 41], "cannot": [24, 31, 37], "capabl": [0, 36], "capac": [3, 29, 33, 36, 37, 41], "capex": 11, "capit": 29, "captur": [8, 25], "care": [1, 3, 24, 29], "carlo": 27, "carri": 3, "carrier": [0, 22, 24, 38], "carrier_1": 22, "carrier_2": 22, "carrier_3": 22, "carrier_4": 22, "cascad": 18, "case": [1, 2, 8, 12, 18, 21, 22, 25, 29, 31, 37, 39, 41], "catch": [8, 43], "caus": 3, "caution": 41, "cd": 5, "cdot": [18, 19, 20, 21, 22], "center": 0, "centralis": 27, "certain": [0, 21, 25, 29, 39], "cfg": 15, "challeng": [27, 34], "chang": [0, 1, 2, 3, 12, 19, 22, 25, 29, 31, 36, 37, 39, 41], "char": 25, "charact": [2, 24, 25, 29], "character": 0, "charg": [2, 3, 20, 41], "charger": 20, "chat": 41, "check": [0, 1, 2, 3, 12, 29, 41], "checker": 5, "checklist": 39, "checkout": [0, 5, 32], "chemic": 31, "choic": [12, 18, 31, 37, 41], "choos": [3, 29, 37], "chosen": 12, "chp": [6, 33], "citat": 0, "clarifi": [35, 41], "clariti": [5, 31], "class": [5, 6, 15, 16, 27], "clean": [0, 5], "clear": [1, 2, 5, 29], "clearli": 31, "click": [5, 41], "clone": 5, "close": [8, 22], "cm": 33, "co2": [8, 21, 22, 25, 33], "co2_cost": [21, 25], "co2_emiss": 25, "co2_out": 33, "code": [0, 1, 3, 7, 8, 9, 10, 11, 12, 18, 19, 20, 21, 22, 25, 29, 34], "codespel": 5, "coeffici": [22, 29], "col": [18, 20, 21, 25], "collect": 6, "column": [3, 22, 25], "com": [0, 2, 5, 12, 34], "combin": [0, 3, 20, 21, 22, 25, 29], "combust": 27, "come": [0, 3, 6, 21], "command": 3, "comment": [3, 24, 25, 29, 41], "commerci": 12, "commit": [5, 22], "commod": 8, "common": [18, 24, 25, 39, 40], "commonli": [2, 12, 31, 41], "comp": 8, "compar": [0, 1, 31], "compat": [5, 25, 32], "compil": [0, 8, 29], "complet": [0, 6, 8, 29], "complex": [0, 3, 6, 19, 25, 35, 36, 37, 40], "complic": [33, 36, 37], "compon": [0, 1, 6, 8, 11, 15, 16, 18, 19, 20, 21, 22, 24, 29, 37, 38, 39, 40, 42], "component_nam": 8, "compress": [8, 25], "compromis": [0, 12], "comput": [8, 15, 39], "compute_ii": 15, "con": [1, 2, 16, 18, 19, 20, 21, 22], "con_conversion_bound": 22, "con_custom": 8, "con_fix": 19, "con_flow_bound": 18, "con_ison": 22, "con_last_st": 20, "con_min_onoff_tim": 22, "con_nodalbal": 20, "con_ramp": 22, "con_ramp_limit": 22, "con_sos1": 19, "con_sos2": 19, "con_sos_valu": 19, "con_startup": 22, "con_state_bound": 20, "con_value_bound": 21, "concept": [0, 3, 29], "concis": [0, 41], "conclus": [41, 43], "concret": 21, "conda": 0, "condit": [8, 29], "confer": 27, "config": [1, 2, 13, 15, 24, 29, 39], "config_fil": [1, 2], "configur": [6, 8, 18, 20, 22, 24, 25, 36, 38, 43], "confus": 1, "conjunct": 25, "conn": 3, "connect": [3, 19, 20, 21, 22, 23, 29, 31, 38, 39], "consid": [3, 6, 12, 18, 19, 20, 22, 24, 25, 27, 29, 33, 37, 39], "consider": [0, 3], "consist": [8, 25, 29, 31, 39], "consol": 15, "constant": [8, 21, 29], "constrain": [18, 19, 37], "constraint": [1, 2, 8, 15, 23, 25, 29, 33], "constraint_safeti": [6, 25], "construct": [2, 3, 8, 18, 19, 20, 21, 22, 23, 25, 33], "constructor": 13, "consult": [0, 5, 15], "consum": [3, 18, 29, 39], "consumpt": [22, 38, 39], "contain": [0, 1, 7, 8, 11, 13, 25, 29], "content": [1, 2, 25, 34, 37], "continu": [0, 19, 25, 29, 39], "contribut": [2, 4, 21], "contributor": 0, "control": [12, 18, 20, 21, 22, 25], "conveni": 25, "convent": [0, 2, 5, 29, 39, 41], "convers": [1, 2, 3, 6, 7, 8, 29, 33, 36, 39], "convert": [3, 15, 17, 22, 39, 41], "convolut": 3, "cop": 22, "copi": [1, 2, 13, 41], "copyright": 0, "core": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 15, 18, 19, 20, 21, 22, 23, 25, 29], "corecompon": [8, 29], "correct": [3, 18, 21, 39], "correctli": [0, 1, 25, 38, 39], "correspond": [12, 29, 31], "cost": [2, 3, 6, 12, 22, 23, 25, 41], "costli": 37, "could": [0, 1, 2, 3, 8, 20, 25, 29, 31, 36, 39], "count": [3, 6, 8, 19, 25, 39], "country_gas_grid": 21, "coupl": 0, "cover": [1, 3, 5, 29, 35, 40, 41], "coverag": 5, "craft": [35, 40], "creat": [0, 1, 2, 3, 5, 6, 8, 12, 13, 17, 20, 21, 25, 29, 31, 36, 37], "creation": [3, 20, 21], "credit": 0, "crossov": 34, "crucial": [38, 39, 41], "csv": [3, 6, 8, 25], "ctrl": 5, "current": [0, 7, 8, 12, 15, 18, 20, 22, 23, 25, 27, 29, 34, 37, 40], "curv": 36, "custom": [1, 25, 29, 33, 36], "customheatpump": 29, "customresult": 1, "customunit": 6, "cv": 33, "cyclic": [18, 20], "d": [0, 2, 3], "dai": [3, 39, 41], "daili": [39, 41], "danger": [1, 29], "daniel": 0, "data": [1, 2, 3, 6, 8, 15, 21, 23, 24, 25, 29, 41], "data_fil": 22, "data_in": 6, "databas": 9, "datafram": [1, 2, 8, 16, 39], "de": 25, "deactiv": 25, "debug": 25, "decarbon": 27, "decarbonis": 27, "decentralis": 27, "decid": [1, 3, 19, 31], "decim": 25, "decis": [2, 18, 20, 22, 23], "decomposit": 19, "decreas": 22, "deep": 37, "deeper": [35, 40], "default": [1, 3, 5, 6, 8, 11, 12, 13, 15, 16, 18, 19, 20, 21, 22, 25, 29, 34, 37, 38], "defin": [0, 1, 3, 8, 18, 19, 20, 21, 22, 24, 25, 29, 37, 41], "definit": [3, 18, 25, 29, 33, 34], "deflat": 8, "degre": [0, 29], "delai": [3, 23], "delet": 5, "delim": 25, "delimit": 25, "delta": 22, "demand": [1, 2, 3, 20, 21, 23, 27, 31, 36, 38, 39], "demand_xi": 21, "demonstr": 25, "depend": [12, 16, 18, 20, 21, 22, 24, 25, 37, 38, 41], "depict": [21, 37], "deprec": [8, 15], "deriv": 29, "describ": [3, 8, 19, 20, 22], "descript": [3, 5, 8, 25], "descriptor": 25, "design": [8, 37], "desir": 5, "destin": 18, "destroi": [20, 21, 25], "destruct": [3, 20, 21], "detail": [0, 3, 7, 8, 9, 10, 11, 23, 26, 27, 29, 35, 41], "detect": [8, 18, 24], "determin": [8, 15, 19, 29], "determinist": 27, "dev": [5, 15], "develop": [0, 12, 25, 27, 41], "df": [1, 2], "dh": 27, "dict": [16, 22, 24, 25], "dict_kei": 1, "dictionari": [3, 6, 8, 16, 22, 24, 25], "did": [0, 2, 3], "differ": [1, 2, 3, 6, 12, 15, 18, 20, 21, 23, 25, 31, 34, 36, 43], "dir": 22, "directli": [0, 1, 3, 5, 8, 17, 18, 22, 25, 26, 27, 29, 41], "directori": [8, 13, 25], "dirti": 8, "disabl": [20, 22, 25, 29], "disadvantag": 2, "discharg": [2, 20], "discontinu": 25, "discours": [0, 25], "discuss": [25, 35, 37, 41], "disk": 25, "displai": [8, 25], "distinct": 31, "district": 27, "dive": [35, 40], "di\u00e1taxi": 0, "do": [0, 1, 2, 3, 5, 6, 8, 18, 29, 31, 33, 35, 36, 39, 40, 41], "doc": [0, 3, 5, 6, 34, 41], "docstr": [5, 8, 43], "document": [2, 6, 7, 8, 9, 10, 11, 17, 18, 19, 20, 21, 22, 23, 24, 25, 29, 34, 41], "doe": [0, 1, 2, 3, 6, 8, 19, 20, 21, 22, 25, 29, 31, 39], "doesn": [37, 39], "doi": [26, 27], "don": [2, 5, 29, 39], "done": [0, 1, 5, 6, 12, 15, 18, 25, 29, 39], "dotenv": 12, "down": [22, 25], "downgrad": 0, "downward": 22, "draw": [3, 18, 21, 38], "drive": 39, "drop": [3, 21, 23], "dropdown": 27, "dst_dir": [1, 2, 13, 15], "dst_name": [1, 2, 13], "dtype": [1, 2], "dual": [1, 2, 16, 19, 25], "dual_feasibility_toler": 34, "duckdb": [6, 9, 25], "due": [3, 8, 22, 23], "duplic": 24, "durat": [3, 11, 18, 22, 25, 41], "dure": [1, 3, 6, 8, 20, 22, 25, 39], "dynam": [8, 22, 25, 36], "e": [0, 1, 2, 3, 5, 6, 8, 13, 15, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 34, 39, 41], "each": [0, 3, 5, 6, 8, 12, 18, 19, 20, 21, 23, 25, 33, 37, 39], "earli": 25, "earlier": 37, "easi": 25, "easier": [8, 31, 36], "easiest": [7, 9, 10, 11, 18, 19, 20, 21, 22, 25], "easili": [8, 12, 29], "econom": 37, "edit": [1, 5], "editor": 5, "effect": [22, 36, 38], "effici": [22, 33], "either": [1, 2, 5, 8, 16, 18, 19, 20, 21, 22, 25, 26, 27, 29, 31], "elec": 2, "elec_cost": 25, "elec_from": 29, "elec_grid": 3, "elec_grid_nod": 38, "electr": [1, 3, 20, 21, 22, 23, 25, 29, 33, 36, 38], "electricity_from": 29, "electrolys": 27, "electrolysi": [2, 27], "element": 1, "els": [18, 29], "eltyp": 1, "emb": 0, "emiss": [21, 25], "empti": [3, 8, 15, 20, 21, 25], "en": [25, 34], "enabl": [0, 1, 5, 6, 8, 20, 22, 23, 25, 29], "encount": [5, 8], "end": [0, 2, 3, 8, 18, 20, 21, 22, 29, 31, 37], "endogen": [3, 21, 23], "energi": [8, 12, 15, 18, 20, 21, 22, 23, 25, 27, 36, 37, 38, 41], "enforc": [20, 22, 23], "engag": 5, "english": 41, "ensur": [1, 5, 8, 25, 27, 33, 37, 38, 39, 41], "entail": 22, "enter": 3, "entri": [1, 2, 12, 16, 24, 25, 26, 27, 29], "env": [5, 12], "environ": [3, 12, 24, 37], "epsilonconstraint": 25, "eq": [3, 20], "equal": [18, 20, 22, 38], "equat": [20, 23], "equival": [8, 25, 38], "ergo": 34, "error": [1, 3, 5, 6, 8, 24, 25, 29, 31, 40, 43], "escap": 25, "especi": [0, 6, 8, 22, 39, 41], "essenti": [20, 39], "establish": 27, "estim": 37, "etc": [2, 8, 21], "etdf": 18, "eur": 25, "evalu": 29, "even": [0, 1, 2, 12, 18, 22, 25, 29, 31, 37], "ever": [5, 19], "everi": [0, 1, 2, 3, 8, 18, 20, 21, 23, 25, 29], "everyth": [0, 3, 6, 25, 29, 41], "ex_custom_result": [1, 2], "exact": [13, 31, 37], "exactli": [1, 2, 21, 23], "exampl": [0, 1, 2, 3, 7, 8, 11, 12, 13, 15, 17, 19, 23, 25, 29, 31, 36, 43], "exce": 37, "except": [8, 25, 32, 39], "excess": [20, 37], "exchang": [24, 41], "execut": [0, 3, 5, 8, 25, 32], "exist": [0, 1, 2, 6, 8, 18, 21, 22, 23, 24, 25, 29, 36, 37], "exogen": 3, "exp": [1, 2, 8, 16, 18, 20, 21, 25, 33, 37], "exp_in": 18, "exp_inject": 20, "exp_out": 18, "exp_pf_flow": 18, "exp_valu": 21, "expect": [2, 3, 8, 22, 23, 27], "experiment": 25, "expert": 29, "explain": [0, 1, 2, 3, 6, 25, 35, 37, 40], "explan": [2, 41], "explanatori": 3, "explicit": [3, 8, 18, 22, 31], "explicitli": [3, 12, 18, 21, 22, 29, 37, 38], "export": 21, "expos": 29, "expr": 8, "express": [2, 3, 6, 25, 33, 39, 41], "ext": 1, "extend": [18, 29], "extens": [5, 8, 13, 15], "extern": [3, 6, 22, 25], "extract": [8, 9, 15, 16, 18, 19, 21, 29], "extract_result": 15, "extrem": 29, "f": [1, 16], "facilit": 27, "fact": [0, 3, 6], "factor": [3, 20, 22, 27, 39], "fail": [3, 5, 8, 32, 39], "failed_gener": 15, "failed_optim": 15, "fals": [1, 2, 8, 20, 22, 25], "familiar": [2, 7, 9, 10, 11, 18, 19, 20, 21, 22, 29, 41], "far": 8, "farayoptindustri": 25, "farayoptindustry_baselin": 25, "fast": 0, "faster": [0, 12], "favor": 36, "feasibilitytol": 34, "feasibl": [8, 20, 22, 37], "feasopt_toler": 34, "feat": 5, "featur": [0, 5, 8, 12, 25, 29], "feed": [3, 20, 21], "feedback": 5, "feel": [2, 41], "few": [0, 6, 31], "field": [1, 8, 15, 16, 29], "field_typ": [1, 16], "fieldtyp": [1, 16], "file": [0, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 20, 21, 22, 24, 29, 42, 43], "fileformat": [8, 15], "filenam": [3, 8, 10, 13, 15, 25], "fill": [29, 36], "filter": [1, 6, 15, 16], "final": [2, 5, 20, 33, 36, 41, 43], "find": [0, 2, 5, 6, 19, 24, 32], "findm": 6, "fine": [12, 21, 22], "finer": 22, "finish": [0, 29], "first": [0, 1, 2, 5, 6, 8, 20, 29, 39, 41], "fit": 0, "fix": [2, 3, 5, 12, 18, 21, 22, 23, 25, 29, 37, 38], "flag": [5, 8], "flatten": [8, 23], "flexibl": [6, 29, 31, 36], "float": [8, 15, 25], "float64": [1, 2, 8, 11], "flow": [2, 3, 20, 21, 23, 37, 38], "flow_lb": 2, "focus": 0, "folder": [0, 1, 2, 3, 5, 7, 25, 29], "follow": [0, 1, 2, 3, 5, 6, 8, 12, 20, 21, 22, 24, 25, 26, 27, 29, 33, 34, 38, 39, 40, 41], "foo": [6, 8, 15, 29, 36], "fooheatpump": 29, "foral": [18, 20, 21, 22], "forc": [3, 8, 20, 37], "force_addon_reload": 25, "force_overwrit": 8, "forcefulli": 12, "forego": 37, "forget": 1, "fork": [5, 26, 27], "form": [8, 20, 22, 23], "format": [2, 3, 8, 15, 22, 25, 27, 29], "format_": 15, "format_automat": 8, "formatt": 5, "former": 25, "formul": [3, 25], "formula": [29, 39], "forster": 27, "fortun": 2, "fossil": 27, "found": [6, 8, 34], "fraction": [11, 18, 25], "frame": 25, "framework": 25, "free": [2, 41], "freeli": [3, 33, 37], "frequent": 5, "fresh": 5, "friendli": 0, "from": [0, 2, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 29, 37, 38, 39, 41, 42], "front": 12, "fuel": [21, 27, 33], "fuel_co2_emission_factor": 33, "fuel_ga": 21, "fuel_in": 33, "full": [3, 8, 11, 13, 15, 18, 19, 20, 21, 22, 25, 37], "fulli": [0, 6, 18, 22, 23, 25, 33, 35, 37, 40], "function": [0, 1, 2, 3, 5, 6, 15, 16, 17, 18, 19, 21, 22, 25, 33, 36, 37, 39, 43], "further": [0, 1, 2, 3, 5, 6, 7, 9, 10, 11, 12, 18, 19, 20, 21, 22, 23, 25, 29, 41], "furthermor": [19, 22], "futur": [3, 6, 27, 41], "g": [0, 1, 2, 3, 6, 8, 13, 15, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 34, 39, 41], "ga": [3, 8, 21, 22, 23, 31, 33], "gamma": 22, "gas_grid": [3, 22], "gas_market": 3, "gas_turbin": [3, 22], "gasturbin": 3, "gcc": 0, "gener": [1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 15, 18, 19, 20, 21, 22, 24, 27, 35, 37, 38, 39, 41], "geq": [18, 20, 21, 22], "german": 25, "get": [2, 3, 7, 8, 15, 16, 17, 18, 20, 21, 23, 25, 29, 33, 37], "get_compon": [6, 15, 18, 19, 20, 21, 22, 37], "get_constraint": 15, "get_jl_docstr": 17, "get_level_valu": 2, "get_vari": 15, "git": [5, 25], "github": [0, 5, 12, 15, 34, 41], "give": 2, "given": [1, 2, 6, 8, 11, 18, 19, 21, 22, 23, 24, 25, 36, 39], "global": [8, 18, 20, 21, 22, 25], "global_params_scenariohigh": 25, "gmbh": 0, "go": [2, 6, 21, 29, 35, 41], "goe": 41, "gone": 6, "good": [2, 5], "got": 2, "grain": [12, 21], "great": 0, "greater": [19, 22], "grid": [1, 3, 21, 22, 38], "ground": 29, "groundsourceheatpump": 29, "group": [6, 22, 39], "groupbi": 39, "grow": [25, 29], "guarante": [1, 8, 24, 25], "guid": [0, 1, 5, 29, 35, 37, 39, 40, 41], "guidelin": 41, "guro_par_bardensethresh": 34, "gurobi": 12, "gurobi_jl": 12, "gutter": 5, "gzgzg2": 2, "h": 39, "h2_north": 2, "h2_south": 2, "h_max": 33, "ha": [0, 1, 3, 11, 12, 15, 18, 22, 29, 36], "had": [35, 41], "hand": 35, "handl": [2, 6, 7, 8, 12, 21, 22, 29, 38], "happen": [2, 20, 22, 31, 37, 39], "happi": 2, "hard": [29, 36], "has_stat": 3, "have": [0, 1, 2, 3, 5, 6, 8, 12, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 31, 38, 39], "head": [0, 1, 2, 3], "header": 41, "heat": [20, 22, 23, 29, 31, 33, 41], "heat_from": 29, "heat_out": 33, "heat_pump": 29, "heat_storag": 37, "heat_system": 22, "heat_to": 29, "heathighwai": 27, "heatpump": [22, 29], "heavi": 0, "heavili": 21, "hello": 17, "help": [0, 2, 3, 6, 8, 11, 20, 25, 29, 34, 41], "helper": 29, "here": [0, 1, 2, 3, 6, 18, 20, 21, 24, 25, 27, 29, 37, 38, 39, 41], "hide": 2, "hierarch": 25, "high": [0, 12, 25, 29, 36], "high_perform": 6, "higher": [18, 19, 20, 21, 22, 36], "highli": [5, 6, 22], "hint": [2, 24], "hit": 5, "hold": [2, 3, 8, 18], "hook": 5, "hot": 31, "hotwat": 31, "hour": [3, 8, 11, 18, 22, 25, 39], "hourli": [39, 41], "hover": 41, "how": [0, 1, 3, 5, 6, 8, 12, 25, 29, 31, 34, 36, 37, 38, 39, 41], "howev": [0, 1, 3, 5, 6, 12, 19, 21, 24, 25, 29, 31, 33, 37, 39], "html": [5, 15, 34], "htn": 27, "http": [0, 5, 12, 15, 26, 27, 34, 41], "hub": [20, 23], "human": [24, 25], "hydro": [20, 21, 23], "hydrogen": 20, "hypothet": 27, "hytechonomi": 27, "i": [0, 3, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 27, 31, 32, 33, 36, 37, 38, 41, 42, 43], "ibm": 34, "ico": 34, "id": 8, "idea": 37, "ident": 8, "identifi": [8, 27], "ieee": 0, "iesopt": [1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 32, 36, 37, 38, 39, 41], "iesopt_cor": 12, "iesopt_julia": 12, "iesopt_jump": 12, "iesopt_multithread": 12, "iesopt_optim": 12, "iesopt_pkg_gurobi_jl": 12, "iesopt_pkg_modelingtoolkit": 12, "iesopt_solver_cplex": 12, "iesopt_solver_gurobi": 12, "iesopt_solver_high": 12, "iesoptlib": [0, 6, 29], "iesu": [8, 11], "ignor": [25, 27], "ii": [8, 15, 29, 42, 43], "imagin": 31, "img": [26, 27], "immedi": [19, 31], "implement": [0, 6, 7, 18, 19, 20, 21, 22, 24, 29], "implicitli": 3, "import": [0, 1, 2, 3, 6, 8, 13, 15, 17, 18, 19, 20, 21, 22, 23, 25, 27, 32, 37, 39], "importantli": 6, "improp": 1, "improv": [0, 25], "in_electr": 2, "includ": [0, 1, 5, 6, 8, 15, 16, 18, 22, 25, 27], "incorrect": [7, 9, 10, 11, 18, 19, 20, 21, 22], "incorrectli": 37, "increas": [6, 22, 25], "incur": 19, "indcuc": 21, "indent": [3, 24], "independ": 37, "index": [1, 2, 8, 15], "indic": [8, 20, 22, 29, 38], "individu": 25, "induc": [2, 18, 19], "infeas": [15, 25], "infeasible_model": 15, "infeasible_or_unbound": 15, "infeasible_unbound": 15, "infer": 3, "infinit": 18, "inflow": [21, 23, 37], "influenc": 22, "info": [0, 1, 2, 6, 8, 25, 29], "inform": [0, 1, 6, 8, 12, 15, 18, 24, 25, 29, 34, 41], "infti": [18, 19, 20, 21, 22], "ing": 2, "inher": 33, "init": 0, "initi": [0, 6, 12, 18, 19, 20, 21, 22, 23, 25, 41], "inject": [1, 2, 18, 21, 38], "inlin": 29, "inn": 27, "input": [3, 8, 15, 19, 23, 25, 29, 33, 36, 37, 39, 41], "input_data": 22, "input_data_fil": 22, "input_fil": 21, "inputfil": 22, "inputs_2022": 25, "inputs_2023_bas": 25, "ins": 20, "insert": [3, 29], "insid": [0, 1, 11, 17, 24, 25], "inspect": 5, "instal": [3, 5, 12, 22, 32], "instanti": [0, 23, 29], "instead": [0, 1, 2, 6, 8, 13, 15, 19, 21, 22, 29, 31, 37, 39, 41], "institut": 0, "instruct": [5, 26, 27], "int": [25, 29], "integ": [19, 20, 22, 25], "integr": [6, 27], "intellig": 2, "intend": [0, 22, 25, 38], "intens": 1, "inter": 27, "interact": [0, 3, 8, 22, 37, 41], "intercept": 29, "interest": [2, 5, 6, 8, 11, 25], "interfac": [0, 25], "interfer": 25, "intermitt": 22, "intern": [0, 3, 6, 15, 20, 23, 25, 27, 29], "internal_grid_nod": 21, "interpret": [21, 25, 29, 38, 39, 41], "intersect": [20, 23], "interv": 37, "intro": [25, 41], "introduc": [3, 18, 25, 29, 37], "introduct": 6, "intuit": [2, 36, 38], "invalid": 8, "invest": [2, 19, 29], "investig": 27, "invok": 15, "io": [15, 26, 27, 34, 41], "iostream": 8, "ipm": [25, 34], "ipm_optimality_toler": 34, "irreduc": 15, "is_repres": 8, "isa": [8, 29], "isdesign": 0, "isinst": 29, "isnoth": [22, 29], "iso": 31, "ison_": 22, "ison_t": 22, "issu": [7, 9, 10, 11, 18, 19, 20, 21, 22, 25, 26, 27, 41], "iter": [3, 12, 25], "its": [0, 2, 3, 6, 8, 15, 19, 25, 36, 37], "itself": 33, "jl": [0, 6, 7, 8, 9, 10, 11, 13, 14, 15, 18, 19, 20, 21, 22, 24, 25, 43], "jl_symbol": 17, "jld2": [1, 6, 10, 25], "journal": 27, "json": 24, "julia": [0, 3, 4, 6, 7, 9, 10, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 24, 25, 29, 32, 42], "juliacal": [1, 17], "julialang": 25, "juliaup": 32, "jump": [0, 8, 9, 11, 15, 18, 20, 21, 25, 37], "jump_dual": 14, "jump_reduced_cost": 14, "jump_shadow_pric": 14, "jump_valu": [14, 25], "just": [0, 1, 2, 3, 5, 6, 12, 22, 29, 31], "k": 27, "keep": [18, 20, 21, 31, 36, 41], "kei": [1, 8, 16, 24, 27, 41], "kept": 3, "keybind": 5, "keyword": [8, 11, 13, 15, 27, 29], "kind": [12, 22], "kindli": 0, "kirchhoff": 20, "know": [0, 2, 22, 25, 29, 33], "kw": [3, 38, 39, 41], "kwarg": [8, 13, 14, 15], "kwh": [3, 38, 39, 41], "lack": 37, "lambda": 1, "languag": 0, "larg": [12, 25, 27, 29, 34], "large_matrix_valu": 34, "larger": 25, "last": [20, 24, 36, 39], "last_state_lb": 2, "last_state_ub": 2, "latenc": [6, 12], "later": [1, 3, 6, 25, 29, 31], "latest": 12, "latter": [20, 25], "launch": [0, 12], "law": 20, "lb": [20, 29, 37], "lead": [0, 6, 18, 20, 22, 25, 29], "least": [22, 25, 29], "leav": [3, 21], "left": [3, 8, 21, 25], "length": [8, 39], "leq": [18, 20, 21, 22], "less": [31, 37], "lesser": 22, "let": [2, 3, 29, 39], "letter": [29, 41], "level": [0, 1, 2, 8, 15, 24, 36, 37, 39, 41], "leverag": 36, "lexicograph": 25, "librari": 0, "licens": [0, 12], "life": 0, "lifetim": 11, "lift": 0, "like": [0, 1, 2, 3, 5, 6, 8, 19, 20, 23, 24, 25, 29, 31, 36, 39], "limit": [3, 18, 21, 22, 23, 36, 37], "line": [0, 2, 3, 25, 29, 39], "linear": [19, 22, 25], "linear_angl": 20, "link": [1, 3, 5, 6, 21, 25, 27, 35, 40], "linter": 5, "linux": 0, "list": [6, 13, 15, 16, 19, 25, 34], "ll": 3, "llm": [2, 41], "load": [0, 2, 3, 5, 6, 8, 10, 12, 13, 22, 24, 25, 42], "load_compon": [8, 25], "loc": 1, "local": [3, 13, 25], "local_optimum": 15, "locat": [3, 5, 25, 27], "log": [5, 8, 25], "logfil": 25, "logger": 8, "loggingextra": 8, "logic": [1, 29], "long": [0, 1, 3, 16, 31, 39], "longer": 6, "look": [0, 1, 3, 5, 6, 8, 25, 42], "loos": 41, "lose": [1, 20], "loss": [20, 37], "lot": [0, 3, 8, 25, 29, 37], "low": [29, 36], "lower": [8, 18, 19, 20, 21, 22, 29, 36, 37, 41], "lp": [3, 8, 15, 25], "lpmethod": 34, "m": [5, 27, 39, 41], "machin": 5, "macro": 29, "made": [6, 19, 25, 36, 37], "maggauer": 27, "mai": [0, 1, 2, 3, 7, 8, 9, 10, 11, 12, 18, 19, 20, 21, 22, 25, 29, 31, 32, 41], "main": [0, 1, 3, 5, 8, 17, 25], "main_grid_nod": 25, "mainli": [6, 21, 22], "maintain": [0, 5, 26, 27, 29, 36], "mainten": 22, "major": 27, "make": [0, 1, 2, 3, 8, 12, 22, 24, 25, 29, 31, 33, 36, 39, 41], "make_exampl": [1, 2, 13, 15], "mandatori": [18, 19, 20, 21, 22, 29], "mani": [3, 12, 22, 23, 36], "manual": [1, 15, 18, 32], "manual_templates_valid": 8, "map": 39, "margin": [2, 22, 39, 41], "marginal_cost": [2, 3, 36], "marginalcost": 22, "mark": 29, "markdown": [29, 41], "market": [3, 21, 27], "marx": 27, "match": [1, 2, 3, 21], "math": [7, 9, 10, 11, 18, 19, 20, 21, 22], "mathemat": [0, 29, 38], "max": [22, 36], "max_passive_pow": 37, "maximum": [3, 19, 22, 29, 37], "mayb": [21, 22], "mb": 8, "mean": [0, 2, 3, 6, 8, 18, 22, 24, 25, 27, 29, 33, 36, 37, 38, 39], "meaning": 29, "meant": 25, "meanwhil": 19, "mechan": 31, "meet": 27, "memori": [8, 25], "memory_onli": [1, 25], "mention": [25, 29], "mess": 25, "messag": [0, 5, 8], "met": [3, 8, 29], "method": [8, 34, 36, 39], "mfc": 3, "mga": 25, "mi": 31, "might": [3, 6, 8, 12, 22, 23, 24, 25, 29, 36, 37, 38, 41], "milp": [19, 25], "min": 22, "min_capac": 22, "mind": [18, 36], "miniconda": 0, "minim": [2, 22], "minimum": [19, 22, 29], "minor": 25, "minu": 20, "minut": [0, 25], "misc": 0, "misconfigur": 29, "miss": [1, 3, 5], "mistak": [5, 31], "mistkenli": 31, "misunderstand": 41, "mix": 25, "mj": 3, "mo": 25, "moa": 25, "mode": [1, 2, 3, 7, 9, 10, 11, 15, 16, 18, 20, 22, 37, 38], "model": [0, 2, 6, 7, 8, 9, 10, 11, 12, 13, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 33, 34, 36, 37, 38, 39, 41, 42], "modelstatu": [1, 15], "modifi": [6, 8, 18, 20, 21, 23, 29], "modifyheatstorag": 25, "modul": [0, 5, 7, 8, 9, 10, 11, 13, 14, 17], "modular": 0, "mof": 15, "moi": [8, 15], "moment": 3, "monetari": [18, 19, 21, 22, 39], "mont": 27, "month": 8, "more": [1, 2, 3, 8, 12, 15, 18, 19, 20, 21, 22, 24, 25, 29, 31, 33, 34, 36, 37, 41], "most": [0, 1, 2, 3, 5, 6, 8, 12, 18, 22, 24, 25, 27, 29, 31, 36, 37, 39], "mostli": [1, 3, 8, 21, 35, 40, 41], "motiv": 31, "mous": 41, "move": [3, 39, 41], "msg": 8, "much": [3, 31, 35, 37], "multi": [1, 25], "multilin": 29, "multiobject": 24, "multiobjectivealgorithm": 25, "multipl": [1, 2, 3, 18, 19, 20, 21, 22, 25, 29, 36], "multipli": 39, "must": [3, 8, 16, 18, 20, 21, 23, 25, 29], "mutabl": 8, "mw": 36, "mwh": 36, "mwh_el": 25, "mwh_ng": 25, "my": [37, 39, 41], "my_bat": 6, "my_config": 6, "my_exp": 8, "my_first_model": 3, "my_obj": 8, "my_problem": 15, "my_result": [1, 8], "my_snapshot_count": 6, "my_storage_foo": 8, "my_unit": 6, "myprofil": 38, "myself": 29, "myvar": 15, "n": [0, 27], "name": [0, 1, 2, 3, 5, 6, 8, 12, 13, 15, 16, 21, 24, 26, 27, 29, 31, 34, 37, 41], "namedtupl": 8, "natur": 36, "necessari": [0, 3, 8, 12, 18, 20, 22, 25, 29, 37, 39], "need": [0, 1, 3, 5, 6, 8, 12, 18, 19, 20, 21, 22, 25, 27, 29, 32, 33, 36, 37, 39, 41], "neg": [18, 20, 29, 37, 38], "never": [1, 6, 29, 39], "new": [0, 1, 2, 3, 5, 6, 8, 25, 26, 27, 29, 37], "newer": 12, "next": [0, 3, 19, 28, 39, 43], "ng_emission_factor": 25, "nitpicki": 5, "nodal": [20, 23], "nodalbal": 1, "nodalbalance__du": 1, "node": [2, 3, 18, 21, 22, 23, 25, 29, 31, 37, 38], "node_from": [3, 25], "node_to": 3, "nomin": 29, "non": [6, 8, 21, 22, 23, 29], "none": [2, 13, 15, 16, 22, 25, 26, 27, 29], "nonetheless": 0, "normal": [3, 12, 21], "normpath": 7, "nospeci": 8, "note": [1, 2, 3, 4, 15, 18, 19, 24, 25, 33, 39, 41], "noth": [5, 8, 21, 22, 25, 29], "notic": [3, 29], "now": [0, 1, 2, 3, 5, 6, 20, 22, 29, 39], "nput": 25, "null": [8, 25, 29, 33], "number": [6, 8, 22, 25, 29, 39], "numer": [8, 18, 19, 20, 21, 22, 29, 34, 39], "numericalinput": 22, "numericfocu": 34, "nvar": 15, "o": 27, "obj": [2, 16, 17, 18, 19, 21, 22], "obj_cost": [18, 21], "obj_fix": 19, "obj_marginal_cost": 22, "obj_ramp_cost": 22, "obj_so": 19, "obj_startup_cost": 22, "obj_valu": 19, "object": [0, 2, 3, 8, 10, 15, 16, 17, 24, 29, 42], "objective_valu": [1, 3, 15], "objscal": 34, "observ": [1, 2], "obtain": [1, 13], "occur": [18, 22, 32, 35, 36, 41], "occurr": 5, "off": [22, 25, 34], "offer": [0, 24, 39], "offici": [5, 24], "often": [24, 25, 41], "okai": [2, 27], "omega_": 20, "omega_t": [18, 20, 21, 22], "omit": 5, "onc": [1, 3, 5, 25, 29], "one": [0, 1, 2, 3, 8, 12, 18, 21, 22, 23, 25, 29, 31, 33, 36, 37, 39, 41], "ones": [18, 23, 26, 27], "onli": [0, 1, 2, 3, 6, 8, 12, 15, 18, 19, 20, 21, 22, 23, 24, 25, 37, 39, 41], "onlin": [0, 22], "online_capac": 22, "onto": 29, "open": [5, 6, 8, 22, 26, 27, 32], "oper": [2, 21, 22, 29, 31], "oppos": 18, "opposit": 18, "opt": [0, 13, 15], "optim": [1, 6, 13, 15, 19, 22, 24, 37, 39, 41], "optimal_loc": 15, "optimalitytol": 34, "optimis": 27, "option": [3, 5, 6, 8, 13, 15, 16, 18, 24, 25, 34, 35, 36, 40], "order": [18, 19, 20, 21, 22, 24, 25, 26, 27, 33], "orderedcollect": 24, "org": [26, 27], "organ": 0, "orient": [0, 1, 16, 24], "origin": [5, 13, 25, 36], "other": [0, 1, 2, 3, 8, 12, 15, 18, 19, 20, 21, 22, 23, 25, 27, 29, 31, 33, 34, 37, 38, 39, 41], "other_config": 2, "other_model": 2, "otherwis": [0, 8, 15, 20, 21, 25, 26, 27], "our": [0, 1, 3, 5, 22, 28, 29, 31, 39, 41], "out": [1, 2, 3, 8, 12, 15, 20, 22, 23, 25, 29, 33, 36], "out_elec": 33, "out_electr": [1, 2, 33], "out_h2": 2, "out_heat": 33, "out_wat": 8, "outcom": 8, "outlin": 25, "output": [0, 1, 3, 8, 25, 29, 33, 39, 41], "outsid": [8, 25, 29], "over": [0, 3, 5, 11, 18, 21, 36, 41], "overal": [3, 6, 19, 20, 22, 25], "overrid": 8, "overview": 2, "overwhelm": 2, "overwrit": [6, 8, 25], "own": [0, 6], "p": 8, "p_max": 33, "p_nom": 29, "p_nom_max": 29, "packag": [8, 11, 24], "page": [0, 6, 8, 15], "pai": 1, "paid": [3, 19], "panda": [2, 16, 39], "parallel": 34, "param": [6, 25], "paramet": [2, 3, 8, 11, 13, 15, 16, 17, 24, 33, 34, 37, 39], "parameter": 22, "parenthes": 29, "paris_advanced_algorithm": 34, "pars": [6, 29], "parser": 24, "part": [0, 3, 6, 8, 12, 21, 22, 23, 25, 26, 27, 28, 31, 36, 42, 43], "partial": [18, 22, 25], "pascalcas": 29, "pass": [2, 6, 8, 13, 22, 25, 29], "passiv": 41, "passive_charg": 37, "patch": 25, "path": [0, 7, 8, 10, 13, 15, 24], "pattern": [6, 25], "pcname": 0, "pd": 1, "pdf": 34, "penal": [18, 20, 25], "penalti": [8, 21, 25], "pep": 5, "per": [3, 18, 19, 20, 21, 22, 25, 36, 37, 41], "percentag": [20, 22, 37], "perfectli": 12, "perform": [0, 8, 12, 24, 29], "perhap": 32, "period": [11, 18, 20], "permiss": [1, 2], "person": 25, "pf": 18, "pf_i": 18, "pf_slack": 20, "pf_v": 18, "pf_x": 18, "phase": 20, "phenomenon": 31, "photovolta": 3, "physic": 31, "pick": [18, 37], "piecewis": 19, "piecewiselinearcost": 19, "pip": 0, "pipelin": 2, "pkg": 12, "place": [2, 3, 22, 29, 41], "placehold": [0, 23, 25], "plan": [6, 25], "plant": 22, "plant_ga": 2, "plant_solar": 2, "pleas": [5, 7, 8, 9, 10, 11, 18, 19, 20, 21, 22, 25, 26, 27], "plot": [8, 31], "point": [18, 22, 24, 34, 35, 36, 41], "port": 22, "portion": 25, "posit": [2, 18, 20, 29, 38], "possibl": [3, 6, 8, 15, 18, 22, 25, 27, 29, 33, 36, 37], "possibli": [0, 3, 15, 25], "post": 0, "potenti": [8, 12, 18, 21, 22, 24, 36], "power": [0, 6, 18, 20, 21, 22, 29, 33, 36, 37, 38, 41], "power_loss_ratio": 33, "power_out": 33, "power_ratio": 33, "powerflow": [18, 20], "pp": 27, "pr": 25, "practic": 40, "pre": [0, 5, 29], "preced": 24, "preconstruct": 22, "predefin": 3, "predual": 34, "prefer": [29, 38], "prefix": 15, "prepar": [8, 11], "present": 25, "preserv": 39, "prevent": [0, 1, 5, 25, 31, 37], "previou": [6, 19, 22, 25, 29, 35, 41], "previous": 6, "price": [3, 21, 27, 36], "primal": [1, 2, 16], "primal_feasibility_toler": 34, "primari": 22, "print": [0, 3, 5, 8, 15, 25], "println": 17, "priorit": 36, "prioriti": [18, 19, 20, 21, 22], "privat": 29, "probabilist": 8, "probabl": [0, 1], "problem": [6, 8, 15, 19, 21, 23, 25, 41], "problem_typ": [3, 24], "proce": 41, "process": [8, 25, 27, 29, 31, 41], "produc": [3, 29, 31, 39], "product": [3, 12], "profici": 0, "profil": [3, 20, 23, 25, 37, 38, 39], "program": [0, 25, 41], "programmat": 6, "progress": 25, "project": [5, 6, 12, 25, 27, 29], "promis": 27, "prompt": [0, 41], "promptli": 5, "prone": 1, "proper": [0, 1, 3, 6, 8, 12, 18, 38, 39, 41], "properli": [5, 6, 8, 18, 21, 25, 41], "properti": [15, 20, 34], "provid": [0, 5, 6, 8, 9, 10, 24, 26, 27, 29, 39], "ptdf": 18, "public": [6, 8, 26], "publish": 0, "pull": [2, 12], "pump": [22, 29, 31], "pure": [25, 26, 27], "purpos": [1, 3, 25, 31], "push": 5, "put": [0, 27, 29], "pv": 3, "pv_gener": 25, "py": [3, 5], "pyproject": 0, "pypsa": 34, "pytest": 5, "python": [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 18, 19, 20, 21, 22, 24, 25, 29, 39, 41], "pyyaml": 24, "q": 35, "qquad": [18, 20, 21, 22], "qualiti": [0, 5], "quantiti": 27, "queri": 16, "query_available_result": [2, 16], "question": [27, 35, 36, 37, 38, 39, 41], "quick": 39, "quickli": [0, 2], "quit": [2, 6, 29], "quot": 41, "r": [2, 27], "rais": [8, 24], "ramp": 27, "ramp_down": 22, "ramp_up": 22, "rampcost": 22, "ramplimit": 22, "random_se": 34, "rang": [1, 21, 29, 35, 37], "rapid": 12, "rate": [3, 11, 22, 37], "rather": [3, 23, 39], "re": 1, "reach": 25, "read": [0, 2, 3, 5, 8, 25, 35, 40, 41], "readabl": [24, 25, 29], "reader": 25, "readi": 0, "readm": 5, "real": [8, 11, 36], "realis": 1, "realiti": [31, 36, 37], "realli": 6, "realpython": 24, "reason": [31, 36, 37, 39], "rebuild": 5, "recogn": 25, "recommend": [12, 21, 22, 24, 25, 29, 31], "reduc": [36, 39], "ref": 8, "refer": [0, 3, 12, 24, 25], "referenc": [8, 25], "refin": 37, "reflect": [12, 38], "refman": 34, "regard": 27, "regardless": 39, "regex": [2, 25], "regex101": 2, "region": 27, "regist": [1, 8], "regular": 2, "rel": 25, "relat": [0, 3, 6, 8, 12, 18, 25], "relax": [8, 25], "releas": [0, 6, 12, 25], "relev": [8, 12], "reli": [18, 19, 20, 21, 22], "reload": 25, "relocat": 7, "relocatablefold": 7, "remain": [0, 37, 39], "rememb": [2, 3, 18], "remind": 0, "remov": [23, 41], "render": [7, 9, 10, 11, 18, 19, 20, 21, 22], "renew": 22, "repeat": [25, 41], "repeatedli": [1, 12, 35, 41], "repl": [0, 3], "replac": [0, 1, 6, 23, 25, 29, 31], "report": 32, "repositori": [0, 5, 12, 26, 27], "repres": [3, 8, 18, 19, 20, 21, 22, 23, 25, 31, 36, 37, 38, 39], "represent": [3, 20, 25], "reproduc": 25, "request": 0, "requir": [7, 8, 9, 10, 11, 15, 18, 19, 20, 21, 22, 27, 29, 31, 39], "resampl": 2, "reservoir": [18, 20, 21, 23], "resolut": [8, 41], "resolv": [5, 8], "resourc": 21, "respect": [6, 18, 20, 21, 22], "respond": 5, "respons": 22, "rest": 29, "restrict": [3, 22, 25], "result": [0, 8, 9, 10, 13, 15, 18, 19, 20, 21, 22, 24, 29, 37, 38, 39, 41], "result_dict": 1, "retriev": 8, "return": [1, 2, 6, 7, 8, 10, 11, 13, 15, 16, 17, 18, 41], "reuter": 27, "revenu": 18, "revers": 18, "review": 5, "rework": 6, "rid": 37, "right": [0, 29, 41], "rise": 27, "river": 18, "root": [5, 12], "rough": 25, "roughli": [5, 41], "row": 25, "ruff": 5, "run": [0, 1, 2, 6, 12, 13, 15, 18, 19, 20, 21, 22, 25, 36], "run_crossov": 34, "safe": [0, 8], "safeti": 8, "said": 25, "same": [1, 2, 3, 6, 8, 18, 19, 20, 22, 23, 24, 25, 32, 39], "satisfi": 3, "save": [5, 8, 10, 25, 36], "saw": 6, "scalar": 8, "scale": [22, 34], "scenario": [6, 25], "scenario17": 6, "schmidt": 27, "schwabened": 0, "scope": 21, "sddp": 25, "search": 2, "season": 41, "second": [8, 20], "section": [0, 2, 3, 6, 7, 8, 9, 10, 11, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 39], "sector": 0, "secur": 27, "see": [0, 1, 2, 6, 8, 15, 19, 20, 21, 22, 24, 25, 26, 27, 29, 31, 34, 39], "seed": 34, "seen": [3, 31, 34], "self": [3, 29], "sell": 21, "sell_electr": 21, "semant": 25, "sensit": 12, "sentenc": 41, "separ": [18, 22, 25, 29, 36, 37, 43], "septemb": 27, "seri": [1, 2, 3, 16, 21, 22, 23, 41], "serial": 24, "set": [1, 2, 3, 6, 8, 11, 12, 15, 18, 19, 20, 21, 22, 23, 24, 25, 29, 37, 38, 39], "set_string_names_on_cr": 25, "setminu": 20, "setpoint": [1, 22], "setup": [0, 3, 5, 8, 17, 25], "seval": 12, "seven": 3, "shape": 16, "share": 18, "shell": 0, "shield": [26, 27], "shift": [3, 5], "ship": 29, "short": 41, "shorten": 33, "should": [0, 3, 5, 8, 12, 17, 19, 20, 21, 22, 25, 27, 29, 39, 41, 42], "show": [1, 2, 3, 8, 25, 31], "showcas": [1, 2], "shown": [2, 3, 6], "shut": 22, "side": 29, "sign": 41, "silenc": 25, "similar": [0, 1, 6, 7, 8, 9, 10, 11, 15, 18, 19, 20, 21, 22, 29, 33, 39], "similarli": [0, 3, 39], "simpl": [0, 3, 11, 12, 22, 25, 39, 41], "simpli": 5, "simplifi": [6, 20, 41], "simul": [27, 37], "sinc": [1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 18, 19, 20, 21, 22, 24, 29, 31], "singl": [1, 2, 6, 8, 12], "size": 19, "skip": [0, 1, 5, 21], "skip_valid": 8, "slack": [18, 20], "slightli": [6, 29], "slower": 1, "small": [6, 12, 25, 29], "small_matrix_valu": 34, "smart": 27, "smooth": 29, "snake_cas": 29, "snapshot": [2, 3, 6, 18, 20, 21, 22, 23, 24, 37, 41], "so": [0, 1, 2, 3, 5, 6, 8, 12, 13, 21, 22, 23, 24, 25, 29, 31, 36, 38, 39], "social": [26, 27], "soft_constraint": [6, 24], "softwar": 0, "soil": 37, "solar": 22, "sold": 20, "sole": 25, "solut": 25, "solutionlimit": 25, "solutiontyp": 34, "solv": [0, 1, 2, 3, 8, 15, 25], "solver": [8, 15, 24, 40], "some": [0, 1, 3, 6, 7, 8, 9, 10, 11, 12, 18, 19, 20, 21, 22, 23, 25, 27, 29, 43], "some_col": 6, "some_exampl": 13, "some_param": 29, "somescenario": 1, "someth": [1, 2, 8, 35, 36, 37, 40], "sometim": [33, 39], "soon": 6, "sort": [20, 23], "sourc": [8, 13, 15, 16, 18, 22, 24, 27, 29], "space": [22, 24, 25, 29], "span": 39, "special": [0, 25, 29, 37], "specif": [1, 3, 6, 8, 12, 13, 15, 16, 18, 19, 20, 21, 22, 24, 25, 29, 32, 35, 40, 42], "specifi": [3, 8, 18, 20, 21, 22, 25, 29, 33, 38, 39], "spell": 5, "spent": 31, "sphinx": 5, "split": [18, 36], "spot": [7, 9, 10, 11, 18, 19, 20, 21, 22], "sr": 2, "src": 5, "stabl": [15, 34], "stage": 25, "stai": 39, "stand": 24, "standard": [3, 24, 26, 27, 29], "start": [2, 3, 5, 18, 22, 24, 25, 29, 34, 41], "startup": 3, "startupcost": 22, "stash": 5, "state": [2, 3, 18, 22, 23, 37], "state_cycl": 3, "state_lb": 3, "state_percentage_loss": 37, "state_ub": [3, 37], "stateless": 20, "statement": 29, "static": 21, "statu": [1, 8, 15, 25], "stefan": 0, "step": [0, 3, 6, 8, 25, 38, 41, 43], "stick": [2, 25, 26, 27, 29], "still": [0, 3, 19, 25, 27, 32], "stmt": 5, "stochast": 25, "storag": [1, 2, 3, 20, 23, 41], "store": [3, 8, 9, 20, 24, 25, 29, 39], "str": [8, 13, 15, 16, 17, 25], "straightforward": 36, "stream": 8, "string": [7, 8, 10, 17, 18, 20, 21, 22, 25, 29], "string_nam": 25, "struct": [1, 8], "structur": [0, 3, 6, 8, 25, 26, 27, 43], "str\u00f6mer": [0, 27], "stuff": [2, 6, 36, 37, 41], "style": [0, 3, 26, 27], "sub": [6, 8], "subfold": 25, "subject": [18, 20], "submit": 41, "subsect": 25, "subsequ": [5, 8, 12], "subset": 8, "substanc": 31, "subtl": 2, "subtract": 20, "succe": 8, "success": [0, 1, 13, 17], "successfulli": [3, 8], "suddenli": 1, "suggest": [27, 29], "suit": 5, "sum": [18, 39], "sum_": [18, 21, 22], "summari": 41, "super": 12, "superset": [24, 29], "suppli": [20, 21, 27, 31, 38], "supplier": 21, "support": [0, 2, 6, 7, 8, 15, 21, 23, 25, 41], "suppos": [38, 39], "sure": [0, 2, 3, 12, 25, 29, 39, 41], "surround": 37, "switch": [2, 6, 19, 25], "symbol": [1, 8, 17], "symmetr": [3, 18], "sync": 5, "syntax": [0, 3, 12, 22, 24, 25, 29], "system": [3, 20, 23, 25, 27, 31, 37, 38, 41], "t": [1, 2, 5, 8, 16, 18, 20, 21, 22, 24, 25, 27, 29, 37, 39], "t1": [1, 2], "t2": [1, 2], "t3": [1, 2], "t4": [1, 2], "t5": [1, 2], "tab": 25, "tabular": 1, "tackl": 29, "tag": [8, 15], "tagged_model": 6, "take": [0, 1, 2, 3, 6, 16, 18, 22, 29, 35, 39], "takeawai": 41, "talk": 12, "target": 15, "task": [0, 2, 7, 9, 10, 11, 18, 19, 20, 21, 22, 23], "tax": 21, "team": 41, "technic": 0, "technologi": [0, 27], "teelogg": 8, "tell": [3, 5, 39], "temperatur": [25, 29], "templat": [0, 1, 5, 6, 7, 8, 11, 23, 25, 33], "tempor": 8, "temporari": 8, "tempt": 31, "term": [1, 8, 18, 21, 25, 39], "termin": [0, 5, 8, 32], "test": [2, 18, 25], "text": [18, 19, 20, 21, 22, 27, 29, 39, 41], "than": [1, 2, 12, 18, 19, 20, 21, 22, 29], "thank": 5, "thei": [2, 3, 5, 6, 8, 21, 22, 23, 29, 31, 34, 35, 39, 41], "them": [3, 5, 6, 9, 16, 18, 25, 29, 39], "themself": 41, "therefor": [1, 3, 8, 18, 24, 25, 31, 37, 39], "thermal": 37, "thi": [1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17, 23, 24, 25, 26, 27, 29, 31, 33, 36, 37, 39, 41], "thing": [3, 8], "think": [1, 29], "those": [1, 3, 25], "though": 12, "thread": 34, "three": [1, 3, 16, 29], "through": [0, 1, 2, 3, 29, 39], "throughout": [8, 21], "throw": 8, "thrown": [8, 29], "ti": 8, "tie": 37, "time": [0, 1, 2, 3, 6, 8, 11, 18, 20, 21, 22, 23, 25, 29, 37, 41], "timeperiod": 8, "timeseri": 8, "timestamp": [8, 25], "timestep": 37, "tip": [0, 41], "titl": [0, 5, 27, 41], "to_dict": 16, "to_panda": [2, 16], "todai": 27, "todo": [19, 20], "togeth": [0, 21, 33], "toml": 0, "too": [2, 27, 29, 35, 36, 40], "tool": 0, "top": [1, 8, 15, 24, 39, 41], "topic": [34, 35, 40, 41], "toplevel_config_fil": 8, "topologi": [23, 25], "total": [3, 5, 11, 20, 21, 22, 25, 36, 39], "total_co2": [21, 22, 25], "total_cost": [1, 8, 25], "track": [20, 25], "tracker": 5, "traning": 27, "transfer": [18, 27], "transform": [3, 22, 23, 37], "translat": [1, 8, 18], "transpar": 29, "transport": [18, 27], "travel": 18, "treat": [2, 8], "tri": 1, "trigger": [5, 6, 8], "tripl": 41, "true": [1, 2, 3, 8, 16, 20, 22, 25, 29], "try": [1, 2, 8, 25], "tupl": 16, "turbin": [8, 22], "turn": [8, 22], "tutori": [0, 1, 2, 3, 24, 29, 35, 40, 41], "twice": 24, "two": [1, 2, 3, 18, 22, 24, 25, 29, 31, 33, 36], "txt": 15, "type": [0, 1, 2, 3, 6, 15, 16, 19, 20, 21, 22, 23, 25, 29, 33, 36, 37, 38], "u": [2, 12, 39], "ub": [20, 29, 37], "un": 2, "uncertainti": 27, "uncommon": 1, "unconstrain": 18, "uncov": 3, "underground": 41, "underli": 37, "underscor": [25, 29], "understand": [0, 2, 8, 25, 29, 31, 36, 39, 41], "unexpect": [8, 25], "unfil": 37, "unidirect": 18, "union": 8, "uniqu": [3, 21, 24, 25, 29], "unit": [1, 3, 6, 8, 18, 19, 20, 21, 23, 25, 29, 33, 36, 38, 41], "unit_pool_1": 36, "unit_pool_2": 36, "unitcount": 22, "unix": 25, "unless": [12, 22, 25, 29], "unlik": 5, "unord": 24, "unseen": 6, "unsur": [0, 2], "until": [0, 1], "unus": 8, "up": [2, 3, 6, 12, 22, 25, 27, 29, 31, 35, 36, 38, 40], "updat": [3, 5, 8, 12, 32, 39], "upgrad": 25, "upload": 34, "upon": 25, "upper": [12, 18, 19, 20, 21, 22, 29, 37, 41], "uppercas": 15, "upstream": 25, "uptak": 27, "upward": 22, "url": [0, 27], "us": [2, 3, 5, 6, 7, 8, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41], "usabl": 41, "usag": [0, 1, 6, 8, 15, 17, 18, 19, 22, 23, 29], "user": [0, 1, 8, 23, 29, 31, 35, 40, 41], "usernam": 5, "usual": [3, 19, 25], "util": [5, 8, 20, 22, 25, 36], "uv": 5, "v": 41, "v2": [6, 25], "valid": [22, 24, 25, 31, 39], "vallei": 27, "valu": [1, 2, 3, 8, 15, 16, 18, 20, 22, 23, 24, 25, 29, 37, 38, 39], "var": [2, 15, 16, 18, 19, 20, 21, 22, 37], "var_aux_valu": 21, "var_convers": 22, "var_fix": 19, "var_flow": 18, "var_ison": 22, "var_pf_theta": 20, "var_ramp": 22, "var_so": 19, "var_startup": 22, "var_stat": 20, "var_valu": [19, 21], "vari": [21, 22], "variabl": [0, 3, 8, 12, 15, 23, 25, 29, 33, 36, 39, 42], "variableref": 8, "variou": [0, 3, 6, 8, 12, 19, 20, 23, 24, 25, 27, 41], "ve": 2, "vector": [1, 8], "vectorvalu": 1, "venv": 5, "verbatim": 41, "verbos": [1, 2, 6, 8, 24], "veri": [2, 3, 25, 37], "versatil": 2, "version": [0, 5, 6, 8, 15, 24, 32, 33], "via": [3, 5, 18, 19, 20, 21, 22, 29], "view": [0, 1], "violat": 25, "virtual": [5, 6, 23, 41], "vscode": [0, 3, 5], "wa": [3, 6, 8, 15, 17, 19, 25, 26, 27, 39], "wai": [0, 1, 2, 6, 8, 18, 19, 21, 23, 24, 25, 29, 36, 38, 39], "wait": [2, 29], "walk": [1, 29], "want": [0, 1, 2, 5, 8, 12, 18, 21, 25, 26, 27, 29, 31, 37], "warm": 37, "warn": [5, 8, 25], "wast": [20, 27], "watch": 5, "water": [8, 18, 31], "we": [0, 1, 2, 3, 6, 12, 20, 24, 25, 29, 37, 39], "weather": 22, "websit": 24, "weight": [8, 18, 20, 21, 25, 39], "welcom": 0, "well": [0, 2, 6, 12, 18, 19, 22, 23, 25, 29], "were": [6, 8, 24], "what": [0, 1, 6, 21, 25, 29, 37, 39, 41, 42], "whatev": 29, "when": [0, 1, 2, 5, 6, 8, 12, 15, 18, 22, 25, 29, 31, 32, 37, 38, 39, 41], "where": [0, 3, 5, 6, 8, 12, 16, 18, 19, 22, 25, 26, 27, 29, 31, 36, 39], "whether": [8, 16, 20, 22, 25], "which": [0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 16, 18, 19, 20, 21, 22, 24, 25, 27, 29, 31, 33, 36, 37, 38, 39], "while": [0, 3, 8, 18, 20, 21, 22, 24, 25, 27, 29, 33, 37], "whole": [25, 29], "why": [1, 2, 22, 29, 31], "wide": [1, 2, 16, 29, 35], "wind": 22, "wind_factor": 22, "wind_turbin": 22, "window": 25, "wise": 25, "wish": 2, "withdraw": [18, 20, 21, 38], "withdrawn": 18, "within": [0, 27, 33, 37], "without": [0, 2, 5, 8, 13, 24, 25, 29, 36, 37, 39], "wonder": 1, "word": [2, 5, 12, 41], "work": [0, 1, 2, 5, 6, 12, 15, 23, 24, 25, 29, 31, 39, 41], "workshop": 28, "world": 17, "worri": [2, 29], "would": [1, 2, 3, 8, 15, 18, 22, 29, 31, 35, 37, 39], "wp": 34, "wrap": 18, "wrapper": [1, 6, 8, 12, 25], "write": [1, 2, 5, 8, 15, 25, 29], "write_to_fil": 15, "written": [3, 6, 8, 15, 20, 28, 29], "wrongfulli": 5, "wrote": 2, "www": 34, "x": [0, 5, 8, 22, 32, 38], "xml": 5, "xxxx": [26, 27], "y": [0, 32], "yaml": [0, 1, 2, 3, 6, 8, 13, 15, 25, 29, 39], "ye": [12, 18, 20, 21, 22], "year": [0, 8, 11, 20, 21, 25, 27, 39], "yet": 15, "you": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 15, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 32, 33, 35, 36, 37, 38, 39, 41], "your": [0, 1, 2, 3, 5, 6, 12, 15, 18, 19, 20, 21, 22, 23, 25, 27, 29, 36, 37, 38, 39, 41], "your_connect": 18, "your_decis": 19, "your_nod": 20, "your_profil": 21, "your_unit": 22, "yourenvnam": 0, "yourself": [0, 29], "yyyi": [26, 27], "z": 32, "zenodo": [26, 27], "zero": 20, "zzzzzz": [26, 27]}, "titles": ["Integrated Energy System Optimization", "Extracting results: Part I", "Extracting results: Part II", "A first model", "IESopt.jl", "Contributing", "Updating", "Assets", "Julia", "ResultsDuckDB", "ResultsJLD2", "Utilities", "Configuration", "Python", "JuMP", "Model", "Results", "Utilities", "Connection", "Decision", "Node", "Profile", "Unit", "Components", "YAML", "Top-level config", "Projects", "Publications", "Addons", "Templates: Part I", "Templates: Part II", "Best practices", "Common errors", "Linking components", "Solvers", "Intro", "Output-dependent marginal costs", "Passive charging of an underground heat storage", "Sign convention for time series", "Time resolution & power vs. energy", "General", "Q & A", "Extracting results", "Templates"], "titleterms": {"": 2, "0": 6, "1": 6, "2": 6, "2030": 27, "A": [3, 41], "The": [29, 37], "_csv_config": 25, "about": 0, "access": [1, 8, 18, 19, 20, 21, 22], "account": 29, "adapt_min_to_avail": 22, "add_term_to_object": 8, "addon": [6, 25, 28], "aggreg": 22, "all": 2, "altern": 38, "an": [0, 37], "analysi": 27, "annuiti": 11, "anoth": 24, "api": [7, 8, 9, 10, 11], "approach": 38, "ar": 39, "arbitrari": 12, "argument": 6, "asset": 7, "assist": 41, "austria": 27, "aux_valu": 21, "avail": 22, "availability_factor": 22, "badg": [26, 27], "basic": [20, 21, 22, 29], "bess": 30, "best": 31, "both": 18, "build": [5, 8], "build_prior": [18, 19, 20, 21, 22], "call": 1, "capac": [18, 22, 39], "carrier": [3, 8, 18, 20, 21, 25, 31], "case": 27, "catch": 30, "chang": [5, 6], "charg": 37, "check": [5, 8, 39], "chp": 30, "citat": [26, 27], "cite": 0, "code": 5, "collect": 1, "common": [32, 37], "complex": 29, "compon": [2, 3, 23, 25, 33], "compute_ii": 8, "conclus": [29, 36, 37, 39], "config": [3, 6, 8, 25], "configur": [3, 12, 29, 34, 39], "connect": 18, "constraint": [18, 19, 20, 21, 22, 37], "contain": 2, "content": [24, 35, 40, 41], "contribut": [5, 26, 27], "convent": 38, "convers": 22, "conversion_at_min": 22, "conversion_bound": 22, "cop": 29, "coretempl": 8, "cost": [18, 19, 21, 36, 39], "cplex": 34, "creat": [26, 27], "critic": 8, "custom": 37, "data": 39, "decis": [19, 29], "definit": 31, "delai": 18, "depend": [0, 36], "design": 27, "detail": [18, 19, 20, 21, 22, 25, 36, 38, 39], "develop": 5, "differ": [0, 29], "direct": [1, 18], "docstr": 29, "document": [0, 5], "doe": 38, "domest": 27, "durat": 39, "dure": 32, "enable_ramp_down": 22, "enable_ramp_up": 22, "energi": [0, 3, 31, 39], "environ": [0, 5], "error": [30, 32], "exampl": [6, 20, 21, 22, 30, 38, 39], "execut": 12, "expand": 27, "explan": 36, "express": [8, 18, 19, 20, 21, 22], "extract": [1, 2, 3, 6, 25, 42], "extract_result": 8, "fallback": 34, "file": [1, 3, 25, 30], "filter": 2, "final": [3, 29], "first": 3, "fix": 19, "fixed_cost": 19, "fixed_valu": 19, "flow": 18, "flow_bound": 18, "forc": 12, "format": [5, 40, 41], "framework": 0, "from": 1, "function": [7, 8, 9, 10, 11, 29], "gener": [8, 25, 40], "get": [0, 1], "get_compon": 8, "get_glob": 8, "get_path": 7, "get_t": 8, "get_value_at": 8, "get_vers": 8, "go": 5, "good": 24, "green": 27, "guidelin": 5, "gurobi": 34, "handl": 39, "happen": 38, "has_stat": 20, "heat": [27, 37], "heatpump": 30, "high": 34, "highwai": 27, "how": [18, 19, 20, 21, 22], "hydrogen": 27, "i": [1, 2, 29, 39], "iesopt": [0, 4, 12], "ii": [2, 30], "implement": [36, 37], "import": 12, "improv": [5, 36], "infrastructur": 27, "inject": 20, "input": 22, "instal": 0, "integr": 0, "intern": [8, 39], "internaldata": 8, "intro": [35, 37, 38, 39], "is_on_befor": 22, "ison": 22, "issu": 5, "jl": [4, 12, 30], "julia": [1, 8, 12], "jump": 14, "keep": [5, 39], "kei": 39, "keyword": 6, "know": 24, "languag": 24, "last_stat": 20, "lb": [18, 19, 21], "learn": 0, "level": [6, 25], "link": 33, "lint": 5, "list": [26, 27], "load": [1, 32], "load_result": 10, "local": 5, "look": 2, "loss": 18, "loss_mod": 18, "macro": [7, 8, 9, 10, 11], "make": 5, "make_base_nam": 8, "manag": 0, "manual": 5, "margin": 36, "marginal_cost": 22, "markup": 24, "methodologi": 27, "min_convers": 22, "min_off_tim": 22, "min_on_tim": 22, "min_onoff_tim": 22, "misconcept": 37, "mode": [19, 21, 25], "model": [1, 3, 15], "more": [0, 6], "multiobject": 25, "naiv": 37, "name": 25, "network": 27, "next": 29, "nodal_bal": 20, "nodalbal": 20, "node": 20, "node_from": [18, 21, 38], "node_to": [18, 21, 38], "non": [2, 12], "nonemptyexpressionvalu": 8, "nonemptynumericalexpressionvalu": 8, "nonemptyscalarexpressionvalu": 8, "note": [5, 8, 36], "numfocu": 34, "object": [1, 18, 19, 20, 21, 22, 25], "off_time_befor": 22, "on_time_befor": 22, "optim": [0, 3, 8, 25, 27], "option": [12, 29], "optionalscalarexpressionvalu": 8, "other": 6, "out": 18, "output": [22, 36], "overhead": 0, "overview": [0, 7, 8, 9, 10, 11, 18, 19, 20, 21, 22, 26, 27], "pack": 8, "packag": [12, 32], "paramet": [6, 18, 19, 20, 21, 22, 25, 29], "pars": 8, "part": [1, 2, 29, 30], "passiv": 37, "path": 25, "per": 39, "perform": 25, "pf_flow": 18, "pf_theta": 20, "plant": 2, "poetri": 0, "power": 39, "pr": 5, "practic": [31, 38, 39], "precompil": 0, "prepar": 29, "problem": 37, "problem_typ": 25, "product": 27, "profil": [8, 21], "project": [0, 26], "public": 27, "pull": 5, "pv": 30, "python": 13, "q": 41, "ramp": 22, "ramp_cost": 22, "ramp_down_cost": 22, "ramp_down_limit": 22, "ramp_limit": 22, "ramp_up_cost": 22, "ramp_up_limit": 22, "recommend": [34, 37], "reduc": 0, "refer": [7, 8, 9, 10, 11, 18, 19, 20, 21, 22, 26, 27], "regist": 12, "register_object": 8, "report": 5, "request": 5, "resampl": 39, "resolut": 39, "result": [1, 2, 3, 6, 16, 25, 42], "resultsduckdb": 9, "resultsjld2": 10, "robust": 27, "run": [3, 5, 8], "safe_close_filelogg": 8, "safeti": [18, 20], "select": 2, "separ": [30, 31], "seri": [38, 39], "set": [0, 5], "set_glob": 8, "should": [1, 2], "show": 27, "sign": 38, "size": 29, "snapshot": [8, 25, 39], "so": 19, "soft_constraint": 25, "solut": 37, "solver": [12, 25, 34], "some": 30, "sos1": 19, "sos2": 19, "sos_valu": 19, "specif": 2, "start": 0, "startup": 22, "startup_cost": 22, "state": 20, "state_bound": 20, "state_cycl": 20, "state_fin": 20, "state_initi": 20, "state_lb": 20, "state_percentage_loss": 20, "state_ub": 20, "static": 37, "step": [29, 37, 39], "storag": [37, 39], "structur": [29, 40, 41], "studi": 27, "style": 5, "submit": 5, "sum_window_s": 20, "sum_window_step": 20, "summari": [36, 37, 38, 39], "system": 0, "tag": 6, "takeawai": 39, "templat": [26, 27, 29, 30, 43], "tempor": 2, "test": 5, "thi": [0, 18, 19, 20, 21, 22, 38], "time": [38, 39], "timespan": 11, "to_dict": 1, "to_panda": 1, "to_tabl": 8, "top": [6, 25], "tox": 5, "transmiss": 27, "type": [7, 8, 9, 10, 11], "tyrol": 27, "ub": [18, 19, 21], "underground": 37, "understand": 38, "unit": [22, 39], "unit_commit": 22, "unit_count": 22, "unpack": 8, "up": [0, 5], "updat": 6, "us": [0, 1], "util": [11, 17], "v": 39, "valid": [8, 29], "valu": [19, 21], "value_bound": 21, "variabl": [1, 18, 19, 20, 21, 22], "verbos": 25, "version": [12, 25], "virtual": 8, "what": 2, "which": 1, "why": 38, "write": 41, "write_to_fil": 8, "x": 6, "y": 6, "yaml": 24, "yearspan": 11, "yet": 24}}) \ No newline at end of file diff --git a/tocs/tutorials_extracting_results.html b/tocs/tutorials_extracting_results.html new file mode 100644 index 0000000..af81efd --- /dev/null +++ b/tocs/tutorials_extracting_results.html @@ -0,0 +1,227 @@ + + + + + + + + + +Extracting results | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/tocs/tutorials_templates.html b/tocs/tutorials_templates.html new file mode 100644 index 0000000..ecac173 --- /dev/null +++ b/tocs/tutorials_templates.html @@ -0,0 +1,230 @@ + + + + + + + + + +Templates | IESopt 2.4.3.dev0 documentation + + + + + + + + + + + + + + +
+ + + + + + + + + + + + \ No newline at end of file