diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 3034467..52882a1 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.5","generation_timestamp":"2024-09-27T10:33:25","documenter_version":"1.7.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.5","generation_timestamp":"2024-10-23T13:14:49","documenter_version":"1.7.0"}} \ No newline at end of file diff --git a/dev/index.html b/dev/index.html index 372504c..28ebd5a 100644 --- a/dev/index.html +++ b/dev/index.html @@ -6,4 +6,4 @@ url = {https://github.com/ait-energy/IESopt}, type = {Software}, year = {2021-2024}, -} +} diff --git a/dev/pages/changelog/index.html b/dev/pages/changelog/index.html index d7ac7f4..1c0bcbc 100644 --- a/dev/pages/changelog/index.html +++ b/dev/pages/changelog/index.html @@ -1,2 +1,2 @@ -Changelog · -- IESopt --

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[1.0.5] - 2024-09-11

Bug fixes in custom objective building, and better exception handling for log files.

[1.0.4] - 2024-07-26

Migrate full core component parameter docstrings.

[1.0.3] - 2024-06-18

Relax version requirements on IESoptLib to include all v0.2.z versions.

[1.0.2] - 2024-06-10

Fix solver setup for various workflows.

Changed

  • IESoptLib and HiGHS are again required dependencies.

[1.0.1] - 2024-06-09

Added extensions to properly handle loading IESoptLib and various solvers.

Changed

  • IESoptLib and HiGHS are no longer required dependencies.

Fixed

  • Dynamic loading of weakdeps now works properly.

[1.0.0] - 2024-06-01

Added

  • Initial public release of IESopt.jl
+Changelog · -- IESopt --

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[1.0.5] - 2024-09-11

Bug fixes in custom objective building, and better exception handling for log files.

[1.0.4] - 2024-07-26

Migrate full core component parameter docstrings.

[1.0.3] - 2024-06-18

Relax version requirements on IESoptLib to include all v0.2.z versions.

[1.0.2] - 2024-06-10

Fix solver setup for various workflows.

Changed

  • IESoptLib and HiGHS are again required dependencies.

[1.0.1] - 2024-06-09

Added extensions to properly handle loading IESoptLib and various solvers.

Changed

  • IESoptLib and HiGHS are no longer required dependencies.

Fixed

  • Dynamic loading of weakdeps now works properly.

[1.0.0] - 2024-06-01

Added

  • Initial public release of IESopt.jl
diff --git a/dev/pages/dev_docs/index.html b/dev/pages/dev_docs/index.html index 7a655ca..cf0bf48 100644 --- a/dev/pages/dev_docs/index.html +++ b/dev/pages/dev_docs/index.html @@ -1,2 +1,2 @@ -Developer Documentation · -- IESopt --

Developer Documentation

The following sections provide rough guidelines on how to work with IESopt, and mostly IESopt.jl, improving the documentation, testing, and implementing new features.

Helping out

Looking for things to contribute, with a low entry barrier (besides any open issue)? Check for To be added (especially in the documentation), or TODO (especially in the code).

Getting started

General

  1. Install Julia.
  2. Install VSCode, and some extensions (this step is optional, but highly recommended).
  3. Clone/fork the repository.
  4. Happy coding (... see below)!

If you are new to Julia, or not entirely sure how everything works - let's talk. We are happy to help you get started, and to guide you through the process. Stuff like Revise.jl can be a huge help, and we can show you how to use it. Further, if you are coming from, e.g., a standard Python background, the advantages of a dynamic REPL-driven development may be new to you.

Tips and tricks

Architecture

See ARCHITECTURE.md for more information.

Coding conventions

Branches

We mainly use a "feature branch workflow", similar to "trunk based development". We strive to keep the main branch as clean as possible (docs and tests should build and pass), and work on a separate development (trunk) branch. For larger changes, consider starting new feature branches. Where possible we use PRs (or merge requests) to get changes into the main branch, while doing a (light) code review for each other.

Naming conventions

Julia

We make use of the following naming conventions, which slightly differ from the Julia naming conventions, but are similar to other large projects out there:

  • Types and similar items use CamelCase, e.g., MyType.
  • Functions and variables use snake_case, e.g., my_function(...).
  • Functions that modify their arguments should end with an exclamation mark, e.g., optimize!(...).
  • Functions and variables should actually make use of underscores, whenever reasonable (and not only when absolutely necessary), e.g., set_to_zero!(...) (not settozero!(...) like the Julia naming conventions may suggest).
  • Constants are written in UPPERCASE, e.g., MY_CONSTANT.

Python

To be added (black with --line-length 88, ruff, standard naming conventions, ...).

Conventional commits

Refer to the Conventional Commits specification for a detailed explanation. In short, we use the following format:

  • feat: implemented new feature X
  • fix: fixed the bug X
  • refactor: refactored the code X
  • docs: updated the documentation X
  • test: added a new test for X
  • chore: updated the dependencies X

As indicated we use docs, but test (and not tests), which can be remembered by looking at the folder names: docs/ and test/.

Version numbers

Documenter.jl (as of May, 22nd, 2024) aggressively states: "Documenter, like any good Julia package, follows semantic versioning (SemVer)."

Unfortunately, semantic versioning may not be as well suited as one might think for a package like IESopt.jl. Some reasons are:

  • While the (Julia) API has been stable for a long time (in a sense of: backwards-compatible), we consider the YAML configuration syntax as main part of IESopt's "API". This syntax has changed multiple times, and will likely change in the future. Maintaining full backwards compatibility for this is not feasible all the time. This induces a need for a major version bump, even though the Julia API has not changed.
  • A mere bug fix, even a small one, in IESopt.jl may very likely induce changed results of any model run. A user could see vastly different results between v1.3.10 and v1.3.11, even though the changes are minimal. This envolves not taking patch updates lightly, which is not the case in many other packages.

However, as indicated, the use of semantic versioning is still "expected" by large parts of the Julia community, and not doing so may make it harder for some users, and/or some interactions with other packages. So...

  1. IESopt.jl makes use of semantic versioning!
  2. You are advised to consider the above points when deciding on version bumps.
  3. Advise users and make sure you properly document changes.
  4. Expect rising major version numbers.

Working with VSCode

The following set of extensions may be helpful, either for development or documentation purposes:

Improving the documentation

Docstrings of public entries of IESopt.jl are taken from the code, see src/.... Besides that, the documentation is contained in the docs/src/... folder, and built based on docs/make.jl, using Documenter.jl.

Diátaxis

Consider checking out the excellent "project" Diátaxis, by Daniele Procida. We try to adhere to the principles outlined there, and you may find them useful as well. For a quick intro, you may consider starting here: The difference between a tutorial and how-to guide.

Setup

Make sure that you

  1. have a working installation of Julia (otherwise go to julialang.org and install it; we recommend sticking to Juliaup if asked), and
  2. have a terminal of your choice launched at IESopt.jl/.

Then, run the following command once to set up the environment used for the documentation:

julia --project=docs -e 'import Pkg; Pkg.instantiate()'

Building the documentation

Launch an interactive web server that shows you the documentation while you are working on it:

julia --project=docs -e 'using LiveServer; servedocs(; launch_browser=true)'

Note: While the above is your best choice in 95% of all cases, you can also manually build the documentation using

julia --project=docs docs/make.jl

which may be useful if you modify source files (which LiveServer.jl currently does not track in a convenient way). Note however that this will not automatically reload the documentation in your browser (but may in VSCode if you right-click the index.html file and select Preview, using the Live Preview extension), and may fail to properly account for image/... paths.

Code formatting

We provide a custom .JuliaFormatter.toml file that should be used to format the code. The easiest way to use it is to:

  1. Add JuliaFormatter to your Julia base environment by running ] add JuliaFormatter in the package mode of your Julia REPL (without an active IESopt environment).
  2. Run using JuliaFormatter in the Julia REPL (this now works even if you activated the IESopt environment).
  3. Run format(".") in the Julia REPL to format all files in your current directory. This takes a bit of compile time, but after the first run, it should be fairly fast.

Make sure you checked the formatting, before finalizing your changes or opening a PR. If you forgot to include formatting in your actual commits (we all do...), and cannot reasonably ammend them, add all formatting changes at the end in a single commit with the message:

git commit -m "chore: formatting"

Testing

Running tests locally

Launch a new Julia REPL (hit Alt+J and then Alt+O in VSCode), enter Package mode (by pressing ] in your REPL, now showing (IESopt) pkg>), and then execute all tests by running:

(IESopt) pkg> test
+Developer Documentation · -- IESopt --

Developer Documentation

The following sections provide rough guidelines on how to work with IESopt, and mostly IESopt.jl, improving the documentation, testing, and implementing new features.

Helping out

Looking for things to contribute, with a low entry barrier (besides any open issue)? Check for To be added (especially in the documentation), or TODO (especially in the code).

Getting started

General

  1. Install Julia.
  2. Install VSCode, and some extensions (this step is optional, but highly recommended).
  3. Clone/fork the repository.
  4. Happy coding (... see below)!

If you are new to Julia, or not entirely sure how everything works - let's talk. We are happy to help you get started, and to guide you through the process. Stuff like Revise.jl can be a huge help, and we can show you how to use it. Further, if you are coming from, e.g., a standard Python background, the advantages of a dynamic REPL-driven development may be new to you.

Tips and tricks

Architecture

See ARCHITECTURE.md for more information.

Coding conventions

Branches

We mainly use a "feature branch workflow", similar to "trunk based development". We strive to keep the main branch as clean as possible (docs and tests should build and pass), and work on a separate development (trunk) branch. For larger changes, consider starting new feature branches. Where possible we use PRs (or merge requests) to get changes into the main branch, while doing a (light) code review for each other.

Naming conventions

Julia

We make use of the following naming conventions, which slightly differ from the Julia naming conventions, but are similar to other large projects out there:

  • Types and similar items use CamelCase, e.g., MyType.
  • Functions and variables use snake_case, e.g., my_function(...).
  • Functions that modify their arguments should end with an exclamation mark, e.g., optimize!(...).
  • Functions and variables should actually make use of underscores, whenever reasonable (and not only when absolutely necessary), e.g., set_to_zero!(...) (not settozero!(...) like the Julia naming conventions may suggest).
  • Constants are written in UPPERCASE, e.g., MY_CONSTANT.

Python

To be added (black with --line-length 88, ruff, standard naming conventions, ...).

Conventional commits

Refer to the Conventional Commits specification for a detailed explanation. In short, we use the following format:

  • feat: implemented new feature X
  • fix: fixed the bug X
  • refactor: refactored the code X
  • docs: updated the documentation X
  • test: added a new test for X
  • chore: updated the dependencies X

As indicated we use docs, but test (and not tests), which can be remembered by looking at the folder names: docs/ and test/.

Version numbers

Documenter.jl (as of May, 22nd, 2024) aggressively states: "Documenter, like any good Julia package, follows semantic versioning (SemVer)."

Unfortunately, semantic versioning may not be as well suited as one might think for a package like IESopt.jl. Some reasons are:

  • While the (Julia) API has been stable for a long time (in a sense of: backwards-compatible), we consider the YAML configuration syntax as main part of IESopt's "API". This syntax has changed multiple times, and will likely change in the future. Maintaining full backwards compatibility for this is not feasible all the time. This induces a need for a major version bump, even though the Julia API has not changed.
  • A mere bug fix, even a small one, in IESopt.jl may very likely induce changed results of any model run. A user could see vastly different results between v1.3.10 and v1.3.11, even though the changes are minimal. This envolves not taking patch updates lightly, which is not the case in many other packages.

However, as indicated, the use of semantic versioning is still "expected" by large parts of the Julia community, and not doing so may make it harder for some users, and/or some interactions with other packages. So...

  1. IESopt.jl makes use of semantic versioning!
  2. You are advised to consider the above points when deciding on version bumps.
  3. Advise users and make sure you properly document changes.
  4. Expect rising major version numbers.

Working with VSCode

The following set of extensions may be helpful, either for development or documentation purposes:

Improving the documentation

Docstrings of public entries of IESopt.jl are taken from the code, see src/.... Besides that, the documentation is contained in the docs/src/... folder, and built based on docs/make.jl, using Documenter.jl.

Diátaxis

Consider checking out the excellent "project" Diátaxis, by Daniele Procida. We try to adhere to the principles outlined there, and you may find them useful as well. For a quick intro, you may consider starting here: The difference between a tutorial and how-to guide.

Setup

Make sure that you

  1. have a working installation of Julia (otherwise go to julialang.org and install it; we recommend sticking to Juliaup if asked), and
  2. have a terminal of your choice launched at IESopt.jl/.

Then, run the following command once to set up the environment used for the documentation:

julia --project=docs -e 'import Pkg; Pkg.instantiate()'

Building the documentation

Launch an interactive web server that shows you the documentation while you are working on it:

julia --project=docs -e 'using LiveServer; servedocs(; launch_browser=true)'

Note: While the above is your best choice in 95% of all cases, you can also manually build the documentation using

julia --project=docs docs/make.jl

which may be useful if you modify source files (which LiveServer.jl currently does not track in a convenient way). Note however that this will not automatically reload the documentation in your browser (but may in VSCode if you right-click the index.html file and select Preview, using the Live Preview extension), and may fail to properly account for image/... paths.

Code formatting

We provide a custom .JuliaFormatter.toml file that should be used to format the code. The easiest way to use it is to:

  1. Add JuliaFormatter to your Julia base environment by running ] add JuliaFormatter in the package mode of your Julia REPL (without an active IESopt environment).
  2. Run using JuliaFormatter in the Julia REPL (this now works even if you activated the IESopt environment).
  3. Run format(".") in the Julia REPL to format all files in your current directory. This takes a bit of compile time, but after the first run, it should be fairly fast.

Make sure you checked the formatting, before finalizing your changes or opening a PR. If you forgot to include formatting in your actual commits (we all do...), and cannot reasonably ammend them, add all formatting changes at the end in a single commit with the message:

git commit -m "chore: formatting"

Testing

Running tests locally

Launch a new Julia REPL (hit Alt+J and then Alt+O in VSCode), enter Package mode (by pressing ] in your REPL, now showing (IESopt) pkg>), and then execute all tests by running:

(IESopt) pkg> test
diff --git a/dev/pages/manual___reference/api/index.html b/dev/pages/manual___reference/api/index.html index a68bc96..a01cc45 100644 --- a/dev/pages/manual___reference/api/index.html +++ b/dev/pages/manual___reference/api/index.html @@ -1,12 +1,12 @@ -API · -- IESopt --

API

This contains the following raw documentation entries:


Julia

Modules

IESopt.IESoptModule
IESopt

A general purpose solver agnostic energy system optimization framework.

source

Types

IESopt.CarrierType
struct Carrier
+API · -- IESopt --

API

This contains the following raw documentation entries:


Julia

Modules

IESopt.IESoptModule
IESopt

A general purpose solver agnostic energy system optimization framework.

source

Types

IESopt.CarrierType
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.

source
IESopt.SnapshotType
struct Snapshot
+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.

source
IESopt.SnapshotType
struct Snapshot
     name
     id
     weight
-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/...).

source
IESopt.ConnectionType

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

Parameters

NameMandatoryValuesUnitDefaultDescription
node_fromyesstring--This Connection models a flow from node_from to node_to (both are Nodes).
node_toyesstring--This Connection models a flow from node_from to node_to (both are Nodes).
carriernostring--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.
capacitynonumeric, col@file, decision:valuepower$+\infty$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.
lbnonumeric, col@file, decision:valuepower$-\infty$Lower bound of this Connection's flow.
ubnonumeric, col@file, decision:valuepower$+\infty$Upper bound of this Connection's flow.
costnonumericmonetary (per energy)-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.
lossno$\in [0, 1]$-0Fractional loss when transfering energy. 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.
build_prioritynonumeric-0Priority 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.

Detailed Model Reference

Variables

flow
How to?

Access this variable by using:

# Julia
+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/...).

source
IESopt.ConnectionType

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

Parameters

NameMandatoryValuesUnitDefaultDescription
node_fromyesstring--This Connection models a flow from node_from to node_to (both are Nodes).
node_toyesstring--This Connection models a flow from node_from to node_to (both are Nodes).
carriernostring--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.
capacitynonumeric, col@file, decision:valuepower$+\infty$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.
lbnonumeric, col@file, decision:valuepower$-\infty$Lower bound of this Connection's flow.
ubnonumeric, col@file, decision:valuepower$+\infty$Upper bound of this Connection's flow.
costnonumericmonetary (per energy)-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.
lossno$\in [0, 1]$-0Fractional loss when transfering energy. 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.
build_prioritynonumeric-0Priority 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.

Detailed Model Reference

Variables

flow
How to?

Access this variable by using:

# Julia
 component(model, "your_connection").var.flow
# Python
 model.get_component("your_connection").var.flow

You can find the full implementation and all details here: 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{aligned} & \text{connection.node}_{from}\text{.injection}_t = \text{connection.node}_{from}\text{.injection}_t - \text{flow}_t, \qquad \forall t \in T \\ @@ -20,7 +20,7 @@ & \text{flow}_t \leq \text{ub}, \qquad \forall t \in T \end{aligned}\]

math

Constraint safety

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

Objectives

cost
How to?

Access this objective by using:

# Julia
 component(model, "your_connection").obj.cost
# Python
-model.get_component("your_connection").obj.cost

You can find the full implementation and all details here: 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\]

math

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.

source
IESopt.DecisionType

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

NameMandatoryValuesUnitDefaultDescription
lbnonumeric-0Minimum size of the decision value (considered for each "unit" if count allows multiple "units").
ubnonumeric-$+\infty$Maximum size of the decision value (considered for each "unit" if count allows multiple "units").
costnonumericmonetary (per value)0Cost that the decision value induces, given as $cost \cdot value$.
fixed_valuenonumeric--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.
fixed_costno-monetary-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.
modenolinear, binary, integer, sos1, sos2, fixed-linearType 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.
sosnolist--TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).
build_prioritynonumeric-1000Priority 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.

Detailed Model Reference

Variables

fixed
How to?

Access this variable by using:

# Julia
+model.get_component("your_connection").obj.cost

You can find the full implementation and all details here: 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\]

math

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.

source
IESopt.DecisionType

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

NameMandatoryValuesUnitDefaultDescription
lbnonumeric-0Minimum size of the decision value (considered for each "unit" if count allows multiple "units").
ubnonumeric-$+\infty$Maximum size of the decision value (considered for each "unit" if count allows multiple "units").
costnonumericmonetary (per value)0Cost that the decision value induces, given as $cost \cdot value$.
fixed_valuenonumeric--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.
fixed_costno-monetary-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.
modenolinear, binary, integer, sos1, sos2, fixed-linearType 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.
sosnolist--TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).
build_prioritynonumeric-1000Priority 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.

Detailed Model Reference

Variables

fixed
How to?

Access this variable by using:

# Julia
 component(model, "your_decision").var.fixed
# Python
 model.get_component("your_decision").var.fixed

You can find the full implementation and all details here: IESopt.jl.

to be added

sos
How to?

Access this variable by using:

# Julia
 component(model, "your_decision").var.sos
# Python
@@ -40,7 +40,7 @@
 component(model, "your_decision").obj.sos
# Python
 model.get_component("your_decision").obj.sos

You can find the full implementation and all details here: IESopt.jl.

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

value
How to?

Access this objective by using:

# Julia
 component(model, "your_decision").obj.value
# Python
-model.get_component("your_decision").obj.value

You can find the full implementation and all details here: IESopt.jl.

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

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

math

source
IESopt.NodeType

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, ...).

Basic Examples

A Node that represents an electrical bus:

bus:
+model.get_component("your_decision").obj.value

You can find the full implementation and all details here: IESopt.jl.

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

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

math

source
IESopt.NodeType

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, ...).

Basic Examples

A Node that represents an electrical bus:

bus:
   type: Node
   carrier: electricity

A Node that represents a simplified hydrogen storage:

store:
   type: Node
@@ -71,7 +71,7 @@
 model.get_component("your_node").con.state_bounds

You can find the full implementation and all details here: IESopt.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}\]

math

Constraint safety

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

Objectives

source
IESopt.ProfileType

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.

Basic Examples

A Profile that depicts a fixed electricity demand:

demand_XY:
+\end{aligned}\]

math

Constraint safety

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

Objectives

source
IESopt.ProfileType

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.

Basic Examples

A Profile that depicts a fixed electricity demand:

demand_XY:
   type: Profile
   carrier: electricity
   node_from: grid
@@ -106,7 +106,7 @@
     & \text{value}_t \leq \text{ub}_t, \qquad \forall t \in T
 \end{aligned}\]

math

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

Objectives

cost
How to?

Access this objective by using:

# Julia
 component(model, "your_profile").obj.cost
# Python
-model.get_component("your_profile").obj.cost

You can find the full implementation and all details here: 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\]

math

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).

source
IESopt.UnitType

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

Basic Examples

A Unit that represents a basic gas turbine:

gas_turbine:
+model.get_component("your_profile").obj.cost

You can find the full implementation and all details here: 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\]

math

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).

source
IESopt.UnitType

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

Basic Examples

A Unit that represents a basic gas turbine:

gas_turbine:
   type: Unit
   inputs: {gas: gas_grid}
   outputs: {electricity: node, co2: total_co2}
@@ -122,7 +122,7 @@
   inputs: {electricity: grid}
   outputs: {heat: heat_system}
   conversion: 1 electricity -> cop@inputfile heat
-  capacity: 10 in:electricity

Parameters

NameMandatoryValuesUnitDefaultDescription
conversionyesstring--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).
capacityyesvalue dir:carrier--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").
outputsyesdict--Dictionary specifying the output "ports" of this Unit. Refer to the basic examples for the general syntax.
inputsnodict--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.
availabilitynonumericpower$+\infty$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 50 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.
availability_factorno$\in [0, 1]$-1Similar 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.
adapt_min_to_availabilitynotrue, false-falseIf 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.
marginal_costnovalue per dir:carriermonetary per energy0Marginal 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.
enable_ramp_upnotrue, false-falseEnables calculation of upward ramps. Ramping is based on the carrier specified in capacity.
enable_ramp_downnotrue, false-falseEnables calculation of downward ramps. Ramping is based on the carrier specified in capacity.
ramp_up_costnonumericmonetary per power0Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.
ramp_down_costnonumericmonetary per power0Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.
ramp_up_limitno$\in [0, 1]$-1Limits 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.
ramp_down_limitno$\in [0, 1]$-1Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.
min_on_timenonumerichours0Minimum 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.
min_off_timenonumerichours0Minimum 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.
on_time_beforenonumerichours0Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.
off_time_beforenonumerichours0Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.
is_on_beforenonumeric-1Number 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.
unit_commitmentnooff, linear, binary, integer-offControls 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).
unit_countnonumeric-1Number 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, ...).
min_conversionno$\in [0, 1]$--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.
conversion_at_minnostring--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.
startup_costnonumericmonetary per start0Costs 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.
build_prioritynonumeric-0Priority 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.

Detailed Model Reference

Variables

conversion
How to?

Access this variable by using:

# Julia
+  capacity: 10 in:electricity

Parameters

NameMandatoryValuesUnitDefaultDescription
conversionyesstring--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).
capacityyesvalue dir:carrier--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").
outputsyesdict--Dictionary specifying the output "ports" of this Unit. Refer to the basic examples for the general syntax.
inputsnodict--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.
availabilitynonumericpower$+\infty$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.
availability_factorno$\in [0, 1]$-1Similar 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.
adapt_min_to_availabilitynotrue, false-falseIf 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.
marginal_costnovalue per dir:carriermonetary per energy0Marginal 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.
enable_ramp_upnotrue, false-falseEnables calculation of upward ramps. Ramping is based on the carrier specified in capacity.
enable_ramp_downnotrue, false-falseEnables calculation of downward ramps. Ramping is based on the carrier specified in capacity.
ramp_up_costnonumericmonetary per power0Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.
ramp_down_costnonumericmonetary per power0Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.
ramp_up_limitno$\in [0, 1]$-1Limits 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.
ramp_down_limitno$\in [0, 1]$-1Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.
min_on_timenonumerichours0Minimum 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.
min_off_timenonumerichours0Minimum 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.
on_time_beforenonumerichours0Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.
off_time_beforenonumerichours0Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.
is_on_beforenonumeric-1Number 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.
unit_commitmentnooff, linear, binary, integer-offControls 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).
unit_countnonumeric-1Number 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, ...).
min_conversionno$\in [0, 1]$--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.
conversion_at_minnostring--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.
startup_costnonumericmonetary per start0Costs 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.
build_prioritynonumeric-0Priority 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.

Detailed Model Reference

Variables

conversion
How to?

Access this variable by using:

# Julia
 component(model, "your_unit").var.conversion
# Python
 model.get_component("your_unit").var.conversion

You can find the full implementation and all details here: 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.

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).

conversion_connect

No documentation found.

IESopt._unit_var_conversion_connect! is a Function.

How to?

Access this variable by using:

# Julia
 component(model, "your_unit").var.conversion_connect
# Python
@@ -176,4 +176,4 @@
 component(model, "your_unit").obj.ramp_cost
# Python
 model.get_component("your_unit").obj.ramp_cost

You can find the full implementation and all details here: 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}}\]

math

startup_cost
How to?

Access this objective by using:

# Julia
 component(model, "your_unit").obj.startup_cost
# Python
-model.get_component("your_unit").obj.startup_cost

You can find the full implementation and all details here: 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}\]

math

source

Functions

IESopt.runFunction
run(filename::String; verbosity=nothing, kwargs...)

Build, optimize, and return a model.

Arguments

  • filename::String: The path to the top-level configuration file.
  • verbosity: The verbosity level to use. Supports true (= verbose mode), "warning" (= warnings and above), and false (suppressing logs).

If verbosity = true, the verbosity setting of the solver defaults to true as well, otherwise it defaults to false (the verbosity setting of the solver can also be directly controled using the verbosity_solve setting in the top-level config file).

Keyword Arguments

Keyword arguments are passed to the generate! function.

source
IESopt.generate!Function
generate!(filename::String)

Builds and returns a model using the IESopt framework.

This loads the configuration file specified by filename. Requires full specification of the solver entry in config.

source
generate!(model::JuMP.Model, filename::String)

Builds a model using the IESopt framework, "into" the provided model.

This loads the configuration file specified by filename. Be careful when creating your model in any other way than in the provided examples, as this can conflict with IESopt internals (especially for model/optimizer combinations that do not support bridges). Returns the model for convenience, even though it is modified in place.

source
IESopt.optimize!Function
optimize!(model::JuMP.Model; save_results::Bool=true, kwargs...)

Use JuMP.optimize! to optimize the given model, optionally serializing the model afterwards for later use.

source
IESopt.componentFunction
function component(model::JuMP.Model, component_name::String)

Get the component component_name from model.

source
IESopt.compute_IISFunction
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.

source
IESopt.overviewFunction
overview(file::String)

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

source
IESopt.packFunction
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.

source
IESopt.unpackFunction
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.

source

Python

To be added.

+model.get_component("your_unit").obj.startup_cost

You can find the full implementation and all details here: 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}\]

math

source

Functions

IESopt.runFunction
run(filename::String; verbosity=nothing, kwargs...)

Build, optimize, and return a model.

Arguments

  • filename::String: The path to the top-level configuration file.
  • verbosity: The verbosity level to use. Supports true (= verbose mode), "warning" (= warnings and above), and false (suppressing logs).

If verbosity = true, the verbosity setting of the solver defaults to true as well, otherwise it defaults to false (the verbosity setting of the solver can also be directly controled using the verbosity_solve setting in the top-level config file).

Keyword Arguments

Keyword arguments are passed to the generate! function.

source
IESopt.generate!Function
generate!(filename::String)

Builds and returns a model using the IESopt framework.

This loads the configuration file specified by filename. Requires full specification of the solver entry in config.

source
generate!(model::JuMP.Model, filename::String)

Builds a model using the IESopt framework, "into" the provided model.

This loads the configuration file specified by filename. Be careful when creating your model in any other way than in the provided examples, as this can conflict with IESopt internals (especially for model/optimizer combinations that do not support bridges). Returns the model for convenience, even though it is modified in place.

source
IESopt.optimize!Function
optimize!(model::JuMP.Model; save_results::Bool=true, kwargs...)

Use JuMP.optimize! to optimize the given model, optionally serializing the model afterwards for later use.

source
IESopt.componentFunction
function component(model::JuMP.Model, component_name::String)

Get the component component_name from model.

source
IESopt.compute_IISFunction
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.

source
IESopt.overviewFunction
overview(file::String)

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

source
IESopt.packFunction
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.

source
IESopt.unpackFunction
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.

source

Python

To be added.

diff --git a/dev/pages/manual___reference/core_components/index.html b/dev/pages/manual___reference/core_components/index.html index dfc84d7..ecadd2f 100644 --- a/dev/pages/manual___reference/core_components/index.html +++ b/dev/pages/manual___reference/core_components/index.html @@ -13,7 +13,7 @@ & \text{flow}_t \leq \text{ub}, \qquad \forall t \in T \end{aligned}\]

math

Constraint safety

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

Objectives

cost
How to?

Access this objective by using:

# Julia
 component(model, "your_connection").obj.cost
# Python
-model.get_component("your_connection").obj.cost

You can find the full implementation and all details here: 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\]

math

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.

source
IESopt.DecisionType

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

NameMandatoryValuesUnitDefaultDescription
lbnonumeric-0Minimum size of the decision value (considered for each "unit" if count allows multiple "units").
ubnonumeric-$+\infty$Maximum size of the decision value (considered for each "unit" if count allows multiple "units").
costnonumericmonetary (per value)0Cost that the decision value induces, given as $cost \cdot value$.
fixed_valuenonumeric--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.
fixed_costno-monetary-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.
modenolinear, binary, integer, sos1, sos2, fixed-linearType 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.
sosnolist--TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).
build_prioritynonumeric-1000Priority 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.

Detailed Model Reference

Variables

fixed
How to?

Access this variable by using:

# Julia
+model.get_component("your_connection").obj.cost

You can find the full implementation and all details here: 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\]

math

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.

source
IESopt.DecisionType

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

NameMandatoryValuesUnitDefaultDescription
lbnonumeric-0Minimum size of the decision value (considered for each "unit" if count allows multiple "units").
ubnonumeric-$+\infty$Maximum size of the decision value (considered for each "unit" if count allows multiple "units").
costnonumericmonetary (per value)0Cost that the decision value induces, given as $cost \cdot value$.
fixed_valuenonumeric--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.
fixed_costno-monetary-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.
modenolinear, binary, integer, sos1, sos2, fixed-linearType 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.
sosnolist--TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).
build_prioritynonumeric-1000Priority 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.

Detailed Model Reference

Variables

fixed
How to?

Access this variable by using:

# Julia
 component(model, "your_decision").var.fixed
# Python
 model.get_component("your_decision").var.fixed

You can find the full implementation and all details here: IESopt.jl.

to be added

sos
How to?

Access this variable by using:

# Julia
 component(model, "your_decision").var.sos
# Python
@@ -33,7 +33,7 @@
 component(model, "your_decision").obj.sos
# Python
 model.get_component("your_decision").obj.sos

You can find the full implementation and all details here: IESopt.jl.

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

value
How to?

Access this objective by using:

# Julia
 component(model, "your_decision").obj.value
# Python
-model.get_component("your_decision").obj.value

You can find the full implementation and all details here: IESopt.jl.

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

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

math

source
IESopt.NodeType

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, ...).

Basic Examples

A Node that represents an electrical bus:

bus:
+model.get_component("your_decision").obj.value

You can find the full implementation and all details here: IESopt.jl.

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

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

math

source
IESopt.NodeType

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, ...).

Basic Examples

A Node that represents an electrical bus:

bus:
   type: Node
   carrier: electricity

A Node that represents a simplified hydrogen storage:

store:
   type: Node
@@ -64,7 +64,7 @@
 model.get_component("your_node").con.state_bounds

You can find the full implementation and all details here: IESopt.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}\]

math

Constraint safety

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

Objectives

source
IESopt.ProfileType

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.

Basic Examples

A Profile that depicts a fixed electricity demand:

demand_XY:
+\end{aligned}\]

math

Constraint safety

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

Objectives

source
IESopt.ProfileType

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.

Basic Examples

A Profile that depicts a fixed electricity demand:

demand_XY:
   type: Profile
   carrier: electricity
   node_from: grid
@@ -99,7 +99,7 @@
     & \text{value}_t \leq \text{ub}_t, \qquad \forall t \in T
 \end{aligned}\]

math

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

Objectives

cost
How to?

Access this objective by using:

# Julia
 component(model, "your_profile").obj.cost
# Python
-model.get_component("your_profile").obj.cost

You can find the full implementation and all details here: 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\]

math

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).

source
IESopt.UnitType

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

Basic Examples

A Unit that represents a basic gas turbine:

gas_turbine:
+model.get_component("your_profile").obj.cost

You can find the full implementation and all details here: 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\]

math

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).

source
IESopt.UnitType

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

Basic Examples

A Unit that represents a basic gas turbine:

gas_turbine:
   type: Unit
   inputs: {gas: gas_grid}
   outputs: {electricity: node, co2: total_co2}
@@ -115,7 +115,7 @@
   inputs: {electricity: grid}
   outputs: {heat: heat_system}
   conversion: 1 electricity -> cop@inputfile heat
-  capacity: 10 in:electricity

Parameters

NameMandatoryValuesUnitDefaultDescription
conversionyesstring--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).
capacityyesvalue dir:carrier--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").
outputsyesdict--Dictionary specifying the output "ports" of this Unit. Refer to the basic examples for the general syntax.
inputsnodict--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.
availabilitynonumericpower$+\infty$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 50 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.
availability_factorno$\in [0, 1]$-1Similar 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.
adapt_min_to_availabilitynotrue, false-falseIf 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.
marginal_costnovalue per dir:carriermonetary per energy0Marginal 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.
enable_ramp_upnotrue, false-falseEnables calculation of upward ramps. Ramping is based on the carrier specified in capacity.
enable_ramp_downnotrue, false-falseEnables calculation of downward ramps. Ramping is based on the carrier specified in capacity.
ramp_up_costnonumericmonetary per power0Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.
ramp_down_costnonumericmonetary per power0Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.
ramp_up_limitno$\in [0, 1]$-1Limits 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.
ramp_down_limitno$\in [0, 1]$-1Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.
min_on_timenonumerichours0Minimum 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.
min_off_timenonumerichours0Minimum 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.
on_time_beforenonumerichours0Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.
off_time_beforenonumerichours0Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.
is_on_beforenonumeric-1Number 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.
unit_commitmentnooff, linear, binary, integer-offControls 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).
unit_countnonumeric-1Number 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, ...).
min_conversionno$\in [0, 1]$--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.
conversion_at_minnostring--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.
startup_costnonumericmonetary per start0Costs 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.
build_prioritynonumeric-0Priority 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.

Detailed Model Reference

Variables

conversion
How to?

Access this variable by using:

# Julia
+  capacity: 10 in:electricity

Parameters

NameMandatoryValuesUnitDefaultDescription
conversionyesstring--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).
capacityyesvalue dir:carrier--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").
outputsyesdict--Dictionary specifying the output "ports" of this Unit. Refer to the basic examples for the general syntax.
inputsnodict--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.
availabilitynonumericpower$+\infty$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.
availability_factorno$\in [0, 1]$-1Similar 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.
adapt_min_to_availabilitynotrue, false-falseIf 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.
marginal_costnovalue per dir:carriermonetary per energy0Marginal 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.
enable_ramp_upnotrue, false-falseEnables calculation of upward ramps. Ramping is based on the carrier specified in capacity.
enable_ramp_downnotrue, false-falseEnables calculation of downward ramps. Ramping is based on the carrier specified in capacity.
ramp_up_costnonumericmonetary per power0Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.
ramp_down_costnonumericmonetary per power0Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.
ramp_up_limitno$\in [0, 1]$-1Limits 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.
ramp_down_limitno$\in [0, 1]$-1Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.
min_on_timenonumerichours0Minimum 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.
min_off_timenonumerichours0Minimum 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.
on_time_beforenonumerichours0Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.
off_time_beforenonumerichours0Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.
is_on_beforenonumeric-1Number 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.
unit_commitmentnooff, linear, binary, integer-offControls 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).
unit_countnonumeric-1Number 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, ...).
min_conversionno$\in [0, 1]$--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.
conversion_at_minnostring--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.
startup_costnonumericmonetary per start0Costs 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.
build_prioritynonumeric-0Priority 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.

Detailed Model Reference

Variables

conversion
How to?

Access this variable by using:

# Julia
 component(model, "your_unit").var.conversion
# Python
 model.get_component("your_unit").var.conversion

You can find the full implementation and all details here: 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.

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).

conversion_connect

No documentation found.

IESopt._unit_var_conversion_connect! is a Function.

How to?

Access this variable by using:

# Julia
 component(model, "your_unit").var.conversion_connect
# Python
@@ -169,4 +169,4 @@
 component(model, "your_unit").obj.ramp_cost
# Python
 model.get_component("your_unit").obj.ramp_cost

You can find the full implementation and all details here: 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}}\]

math

startup_cost
How to?

Access this objective by using:

# Julia
 component(model, "your_unit").obj.startup_cost
# Python
-model.get_component("your_unit").obj.startup_cost

You can find the full implementation and all details here: 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}\]

math

source
+model.get_component("your_unit").obj.startup_cost

You can find the full implementation and all details here: 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}\]

math

source
diff --git a/dev/pages/manual___reference/templates/index.html b/dev/pages/manual___reference/templates/index.html index 7c8f835..474e0e0 100644 --- a/dev/pages/manual___reference/templates/index.html +++ b/dev/pages/manual___reference/templates/index.html @@ -1,2 +1,2 @@ -Templates · -- IESopt --
+Templates · -- IESopt --
diff --git a/dev/pages/manual___reference/yaml/index.html b/dev/pages/manual___reference/yaml/index.html index 1918eaf..9f84f5e 100644 --- a/dev/pages/manual___reference/yaml/index.html +++ b/dev/pages/manual___reference/yaml/index.html @@ -1,2 +1,2 @@ -YAML · -- IESopt --

YAML

To be added (based on docstrings from, e.g., the configs).

+YAML · -- IESopt --

YAML

To be added (based on docstrings from, e.g., the configs).

diff --git a/dev/pages/references/index.html b/dev/pages/references/index.html index 103a626..673dfa8 100644 --- a/dev/pages/references/index.html +++ b/dev/pages/references/index.html @@ -9,4 +9,4 @@ !!! details "Expand: Show citation" > Add your (APA styled!) citation here -

Project 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), otherwhise 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)
+

Project 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), otherwhise 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)
diff --git a/dev/pages/tutorials/creating_new_components/index.html b/dev/pages/tutorials/creating_new_components/index.html index 5f048e9..d0639e2 100644 --- a/dev/pages/tutorials/creating_new_components/index.html +++ b/dev/pages/tutorials/creating_new_components/index.html @@ -1,2 +1,2 @@ -Custom Components · -- IESopt --
+Custom Components · -- IESopt --
diff --git a/dev/pages/tutorials/creating_new_components/templates_1/index.html b/dev/pages/tutorials/creating_new_components/templates_1/index.html index 2ecd7f0..465aebd 100644 --- a/dev/pages/tutorials/creating_new_components/templates_1/index.html +++ b/dev/pages/tutorials/creating_new_components/templates_1/index.html @@ -219,4 +219,4 @@ # 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.

+ 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.

diff --git a/dev/pages/tutorials/creating_new_components/templates_2/index.html b/dev/pages/tutorials/creating_new_components/templates_2/index.html index de3c29c..e16bc2f 100644 --- a/dev/pages/tutorials/creating_new_components/templates_2/index.html +++ b/dev/pages/tutorials/creating_new_components/templates_2/index.html @@ -1,2 +1,2 @@ -Templates: Part II · -- IESopt --
+Templates: Part II · -- IESopt --
diff --git a/dev/pages/tutorials/first_model/index.html b/dev/pages/tutorials/first_model/index.html index ceed742..a5cc7af 100644 --- a/dev/pages/tutorials/first_model/index.html +++ b/dev/pages/tutorials/first_model/index.html @@ -1,2 +1,2 @@ -First steps · -- IESopt --

First steps

Need help?

To be added.

Overview

IESopt (the framework) consists of various sub-projects. It is a component-based optimization framework, where each component can be seen as block containing some predefined functionality. There are five "core components": Connection, Decision, Node, Profile, and Unit. These will be used to define arbitrary energy system models, similar to how a general commodity flow model works. Furthermore, they can be combined to create more complicated (non-core) components.

In their most basic form, core components can be described as:

  • 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).
  • A Profile allows representing exogenous functionality with a support for time series data.
  • A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.

For most models, the Units will pack the most raw functionality, while the other components represent the structure of the overall model.

Your first model

To be added (translate from internal version).

Model config

To be added (translate from internal version).

Energy carriers

To be added (translate from internal version).

Model components

To be added (translate from internal version).

Final config file

To be added (translate from internal version).

Running the optimization

To be added (translate from internal version).

Extracting results

To be added (translate from internal version).

General result structure

To be added (translate from internal version).

Changing the model config

To be added (translate from internal version).

Adapting components

To be added (translate from internal version).

Analyzing the results

To be added (translate from internal version).

Extracting results directly into pd.DataFrames

To be added (translate from internal version).

Final thoughts

To be added (translate from internal version).

+First steps · -- IESopt --

First steps

Need help?

To be added.

Overview

IESopt (the framework) consists of various sub-projects. It is a component-based optimization framework, where each component can be seen as block containing some predefined functionality. There are five "core components": Connection, Decision, Node, Profile, and Unit. These will be used to define arbitrary energy system models, similar to how a general commodity flow model works. Furthermore, they can be combined to create more complicated (non-core) components.

In their most basic form, core components can be described as:

  • 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).
  • A Profile allows representing exogenous functionality with a support for time series data.
  • A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.

For most models, the Units will pack the most raw functionality, while the other components represent the structure of the overall model.

Your first model

To be added (translate from internal version).

Model config

To be added (translate from internal version).

Energy carriers

To be added (translate from internal version).

Model components

To be added (translate from internal version).

Final config file

To be added (translate from internal version).

Running the optimization

To be added (translate from internal version).

Extracting results

To be added (translate from internal version).

General result structure

To be added (translate from internal version).

Changing the model config

To be added (translate from internal version).

Adapting components

To be added (translate from internal version).

Analyzing the results

To be added (translate from internal version).

Extracting results directly into pd.DataFrames

To be added (translate from internal version).

Final thoughts

To be added (translate from internal version).

diff --git a/dev/pages/tutorials/next_steps/index.html b/dev/pages/tutorials/next_steps/index.html index 8b2b186..fd04bc2 100644 --- a/dev/pages/tutorials/next_steps/index.html +++ b/dev/pages/tutorials/next_steps/index.html @@ -1,2 +1,2 @@ -Next steps · -- IESopt --
+Next steps · -- IESopt --
diff --git a/dev/pages/tutorials/results/index.html b/dev/pages/tutorials/results/index.html index 84c4a9f..76b0a9e 100644 --- a/dev/pages/tutorials/results/index.html +++ b/dev/pages/tutorials/results/index.html @@ -1,2 +1,2 @@ -Result extraction · -- IESopt --
+Result extraction · -- IESopt --
diff --git a/dev/pages/tutorials/setup/index.html b/dev/pages/tutorials/setup/index.html index c768776..12234e1 100644 --- a/dev/pages/tutorials/setup/index.html +++ b/dev/pages/tutorials/setup/index.html @@ -1,2 +1,2 @@ -Installation · -- IESopt --
+Installation · -- IESopt --
diff --git a/dev/pages/user_guide/custom_functionality/addons/index.html b/dev/pages/user_guide/custom_functionality/addons/index.html index 8dcb4de..828f342 100644 --- a/dev/pages/user_guide/custom_functionality/addons/index.html +++ b/dev/pages/user_guide/custom_functionality/addons/index.html @@ -1,2 +1,2 @@ -Addons · -- IESopt --
+Addons · -- IESopt --
diff --git a/dev/pages/user_guide/custom_functionality/templates/index.html b/dev/pages/user_guide/custom_functionality/templates/index.html index e6053aa..a275604 100644 --- a/dev/pages/user_guide/custom_functionality/templates/index.html +++ b/dev/pages/user_guide/custom_functionality/templates/index.html @@ -11,4 +11,4 @@ b_node: type: Node

To be added (more details).

Validate

To be added (explanation).

validation: |
   @check parameters["carrier"] isa String
-  @check parameters["carrier"] in ["heat", "electricity"]

To be added (more examples).

Prepare

To be added (explanation).

Finalize

To be added (explanation).

+ @check parameters["carrier"] in ["heat", "electricity"]

To be added (more examples).

Prepare

To be added (explanation).

Finalize

To be added (explanation).

diff --git a/dev/pages/user_guide/general/index.html b/dev/pages/user_guide/general/index.html index a62431e..8c7b5b3 100644 --- a/dev/pages/user_guide/general/index.html +++ b/dev/pages/user_guide/general/index.html @@ -1,2 +1,2 @@ -General · -- IESopt --
+General · -- IESopt --
diff --git a/dev/pages/user_guide/sectors/electricity/index.html b/dev/pages/user_guide/sectors/electricity/index.html index 4b90906..48b5496 100644 --- a/dev/pages/user_guide/sectors/electricity/index.html +++ b/dev/pages/user_guide/sectors/electricity/index.html @@ -1,2 +1,2 @@ -Electricity · -- IESopt --
+Electricity · -- IESopt --
diff --git a/dev/pages/user_guide/sectors/gas/index.html b/dev/pages/user_guide/sectors/gas/index.html index 5b80acc..9b47b87 100644 --- a/dev/pages/user_guide/sectors/gas/index.html +++ b/dev/pages/user_guide/sectors/gas/index.html @@ -1,2 +1,2 @@ -Gas · -- IESopt --

Gas

To be added.

+Gas · -- IESopt --

Gas

To be added.

diff --git a/dev/pages/user_guide/sectors/heat/index.html b/dev/pages/user_guide/sectors/heat/index.html index 714b727..a676902 100644 --- a/dev/pages/user_guide/sectors/heat/index.html +++ b/dev/pages/user_guide/sectors/heat/index.html @@ -1,2 +1,2 @@ -Heat · -- IESopt --
+Heat · -- IESopt --
diff --git a/dev/pages/user_guide/solvers/index.html b/dev/pages/user_guide/solvers/index.html index 2bd6415..471381d 100644 --- a/dev/pages/user_guide/solvers/index.html +++ b/dev/pages/user_guide/solvers/index.html @@ -51,4 +51,4 @@ lpmethod: 4 solutiontype: 2 barrier_convergetol: 1.e-5 - feasopt_tolerance: 1.e-6 + feasopt_tolerance: 1.e-6 diff --git a/dev/search_index.js b/dev/search_index.js index fd4c771..4d7c9ca 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"pages/tutorials/next_steps/#Next-steps","page":"Next steps","title":"Next steps","text":"","category":"section"},{"location":"pages/tutorials/next_steps/","page":"Next steps","title":"Next steps","text":"To be added.","category":"page"},{"location":"pages/user_guide/solvers/#Solvers","page":"Solvers","title":"Solvers","text":"","category":"section"},{"location":"pages/user_guide/solvers/#Recommended-Configurations","page":"Solvers","title":"Recommended Configurations","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"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).","category":"page"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"More informations can be found at:","category":"page"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"HiGHS:\nhttps://ergo-code.github.io/HiGHS/stable/options/definitions/\nGurobi:\nhttps://www.gurobi.com/wp-content/uploads/2022-10-ParisAdvancedAlgorithms.pdf\nhttps://www.gurobi.com/documentation/current/refman/parameters.html\nCPLEX:\nhttps://www.ibm.com/docs/en/icos/22.1.1?topic=cplex-list-parameters","category":"page"},{"location":"pages/user_guide/solvers/#HiGHS","page":"Solvers","title":"HiGHS","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: highs\n attributes:\n threads: 4\n solver: \"ipm\"\n run_crossover: \"off\"\n small_matrix_value: 1e-6\n large_matrix_value: 1e9\n primal_feasibility_tolerance: 1e-5\n dual_feasibility_tolerance: 1e-5\n ipm_optimality_tolerance: 1e-4\n parallel: \"on\"\n random_seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#Gurobi","page":"Solvers","title":"Gurobi","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: gurobi\n attributes:\n Method: 2\n Crossover: 0\n BarConvTol: 1.e-6\n Seed: 123\n AggFill: 0\n PreDual: 0\n GURO_PAR_BARDENSETHRESH: 200\n Threads: 8\n Seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#Gurobi-(NumFocus)","page":"Solvers","title":"Gurobi (NumFocus)","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"For models with \"challenging\" numerical properties, the following can be useful:","category":"page"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: gurobi\n attributes:\n NumericFocus: 3\n Method: 2\n Crossover: 0\n BarHomogeneous: 1\n BarConvTol: 1.e-5\n FeasibilityTol: 1.e-4\n OptimalityTol: 1.e-4\n ObjScale: -0.5\n Threads: 8\n Seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#Gurobi-(fallback)","page":"Solvers","title":"Gurobi (fallback)","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: gurobi\n attributes:\n Crossover: 0\n Method: 2\n BarHomogeneous: 1\n BarConvTol: 1.e-5\n FeasibilityTol: 1.e-5\n OptimalityTol: 1.e-5\n Threads: 8\n Seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#CPLEX","page":"Solvers","title":"CPLEX","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: cplex\n attributes:\n threads: 4\n lpmethod: 4\n solutiontype: 2\n barrier_convergetol: 1.e-5\n feasopt_tolerance: 1.e-6","category":"page"},{"location":"pages/manual___reference/api/#API","page":"API","title":"API","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"This contains the following raw documentation entries:","category":"page"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"Pages = [\"api.md\"]\nDepth = 2:4","category":"page"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"","category":"page"},{"location":"pages/manual___reference/api/#Julia","page":"API","title":"Julia","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"CurrentModule = IESopt\nCollapsedDocStrings = true","category":"page"},{"location":"pages/manual___reference/api/#Modules","page":"API","title":"Modules","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"IESopt","category":"page"},{"location":"pages/manual___reference/api/#IESopt.IESopt","page":"API","title":"IESopt.IESopt","text":"IESopt\n\nA general purpose solver agnostic energy system optimization framework.\n\n\n\n\n\n","category":"module"},{"location":"pages/manual___reference/api/#Types","page":"API","title":"Types","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"Carrier\nSnapshot","category":"page"},{"location":"pages/manual___reference/api/#IESopt.Carrier","page":"API","title":"IESopt.Carrier","text":"struct Carrier\n name::String\n unit::Union{String, Nothing}\nend\n\nRepresents a single (energy) carrier with a given name.\n\nThis 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.\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Snapshot","page":"API","title":"IESopt.Snapshot","text":"struct Snapshot\n name\n id\n weight\nend\n\nRepresent a specific timestamp, that can be tied to timeseries values.\n\nEach 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/...).\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"Connection\nDecision\nNode\nProfile\nUnit","category":"page"},{"location":"pages/manual___reference/api/#IESopt.Connection-pages-manual___reference-api","page":"API","title":"IESopt.Connection","text":"A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, ...\n\nParameters\n\nName Mandatory Values Unit Default Description\nnode_from yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\nnode_to yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\ncarrier no string - - 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.\ncapacity no numeric, col@file, decision:value power +infty 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.\nlb no numeric, col@file, decision:value power -infty Lower bound of this Connection's flow.\nub no numeric, col@file, decision:value power +infty Upper bound of this Connection's flow.\ncost no numeric monetary (per energy) - 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.\nloss no in 0 1 - 0 Fractional loss when transfering energy. 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: flow\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_connection\").var.flow# Python\nmodel.get_component(\"your_connection\").var.flowYou can find the full implementation and all details here: 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 inbeginaligned\n textconnectionnode_fromtextinjection_t = textconnectionnode_fromtextinjection_t - textflow_t qquad forall t in T \n textconnectionnode_totextinjection_t = textconnectionnode_totextinjection_t + textflow_t qquad forall t in T\nendalignedmathFor \"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].\n\nExpressions\n\ndetails: pf_flow\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_connection\").exp.pf_flow# Python\nmodel.get_component(\"your_connection\").exp.pf_flowYou can find the full implementation and all details here: 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, ...).\n\nConstraints\n\ndetails: flow_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_connection\").con.flow_bounds# Python\nmodel.get_component(\"your_connection\").con.flow_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the flow (related to connection) to the model.Specifiying capacity will lead to symmetric bounds (textlb = -capacity and textub = capacity), while asymmetric bounds can be set by explicitly specifiying lb and ub.note: Note\nUsage of etdf is currently not fully tested, and not documented.Upper and lower bounds can be \"infinite\" (by not setting them) resulting in the repective 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,\nif connection.exp.pf_flow is available, it equals this\nelse it equal connection.var.flowThis flow is then constrained:beginaligned\n textflow_t geq textlb qquad forall t in T \n textflow_t leq textub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_connection\").obj.cost# Python\nmodel.get_component(\"your_connection\").obj.costYou can find the full implementation and all details here: 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 textflow_t cdot textcost_t cdot omega_tmathHere omega_t is the weight of Snapshot t.note: Costs for flows in both directions\nIf 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.\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Decision-pages-manual___reference-api","page":"API","title":"IESopt.Decision","text":"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.\n\nParameters\n\nName Mandatory Values Unit Default Description\nlb no numeric - 0 Minimum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\nub no numeric - +infty Maximum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\ncost no numeric monetary (per value) 0 Cost that the decision value induces, given as cost cdot value.\nfixed_value no numeric - - 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.\nfixed_cost no - monetary - 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.\nmode no linear, binary, integer, sos1, sos2, fixed - linear 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.\nsos no list - - TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).\nbuild_priority no numeric - 1000 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: fixed\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.fixed# Python\nmodel.get_component(\"your_decision\").var.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.sos# Python\nmodel.get_component(\"your_decision\").var.sosYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.value# Python\nmodel.get_component(\"your_decision\").var.valueYou can find the full implementation and all details here: 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.\n\nExpressions\n\nConstraints\n\ndetails: fixed\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.fixed# Python\nmodel.get_component(\"your_decision\").con.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos1\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos1# Python\nmodel.get_component(\"your_decision\").con.sos1You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos2\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos2# Python\nmodel.get_component(\"your_decision\").con.sos2You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos_value\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos_value# Python\nmodel.get_component(\"your_decision\").con.sos_valueYou can find the full implementation and all details here: IESopt.jl.to be added\n\nObjectives\n\ndetails: fixed\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.fixed# Python\nmodel.get_component(\"your_decision\").obj.fixedYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: sos\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.sos# Python\nmodel.get_component(\"your_decision\").obj.sosYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the SOS-based value of this Decision to the model.\n\ndetails: value\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.value# Python\nmodel.get_component(\"your_decision\").obj.valueYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the value of this Decision to the model:textvalue cdot textcostmath\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Node-pages-manual___reference-api","page":"API","title":"IESopt.Node","text":"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, ...). \n\ndetails: Basic Examples\nA Node that represents an electrical bus:bus:\n type: Node\n carrier: electricityA Node that represents a simplified hydrogen storage:store:\n type: Node\n carrier: hydrogen\n has_state: true\n state_lb: 0\n state_ub: 50\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Node. All connecting components need to respect that.\nhas_state no true, false - false 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.\nstate_lb no numeric, col@file, decision:value energy -infty Lower bound of the internal state, requires has_state = true.\nstate_ub no numeric, col@file, decision:value energy +infty Upper bound of the internal state, requires has_state = true.\nstate_cyclic no eq, geq, or disabled - eq 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\").\nstate_initial no numeric energy - Sets the initial state. Must be used in combination with state_cyclic = disabled.\nstate_final no numeric energy - Sets the final state. Must be used in combination with state_cyclic = disabled.\nstate_percentage_loss no in 0 1 - 0 Per Snapshot percentage loss of state (loosing 1% should be set as 0.01).\nnodal_balance no enforce, destroy, or create - enforce 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.\nsum_window_size no integer - - TODO.\nsum_window_step no integer - 1 TODO.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: pf_theta\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.pf_theta# Python\nmodel.get_component(\"your_node\").var.pf_thetaYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: state\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.state# Python\nmodel.get_component(\"your_node\").var.stateYou can find the full implementation and all details here: 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:textstate_1 = textstate_initialmath\n\nExpressions\n\ndetails: injection\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_node\").exp.injection# Python\nmodel.get_component(\"your_node\").exp.injectionYou can find the full implementation and all details here: 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 textinjection_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\".\n\nConstraints\n\ndetails: last_state\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.last_state# Python\nmodel.get_component(\"your_node\").con.last_stateYou can find the full implementation and all details here: 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.beginaligned\n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t geq textstate_lb \n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t leq textstate_ub \nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise.note: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\ndetails: nodalbalance\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.nodalbalance# Python\nmodel.get_component(\"your_node\").con.nodalbalanceYou can find the full implementation and all details here: 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 == truebeginaligned\n textstate_t = textstate_t-1 cdot textfactor^omega_t-1 + textinjection_t-1 cdot omega_t-1 qquad forall t in T setminus 1 \n \n textstate_1 = textstate_end cdot textfactor^omega_end + textinjection_end cdot omega_end\nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise. textinjection_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 == falsebeginaligned\n textinjection_t = 0 qquad forall t in T \nendalignedmathThis 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. \n\ndetails: state_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.state_bounds# Python\nmodel.get_component(\"your_node\").con.state_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the node's state to the model, if node.has_state == true.beginaligned\n textstate_t geq textstate_lb qquad forall t in T \n textstate_t leq textstate_ub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Profile-pages-manual___reference-api","page":"API","title":"IESopt.Profile","text":"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.\n\ndetails: Basic Examples\nA Profile that depicts a fixed electricity demand:demand_XY:\n type: Profile\n carrier: electricity\n node_from: grid\n value: demand_XY@input_fileA Profile that handles cost of fuel:fuel_gas:\n type: Profile\n carrier: gas\n node_to: country_gas_grid\n mode: create\n cost: 100.0A Profile that handles CO2 emission costs:co2_cost:\n type: Profile\n carrier: co2\n node_from: total_co2\n mode: destroy\n cost: 150.0A Profile that handles selling electricity:sell_electricity:\n type: Profile\n carrier: electricity\n node_from: internal_grid_node\n mode: destroy\n cost: -30.0\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Profile. Must match the Carrier of the Node that this connects to.\nvalue no numeric, col@file power - The concrete value of this Profile - either static or as time series. Only applicable if mode: fixed.\nnode_from no string - - Name of the Node that this Profile draws energy from. Exactly one of node_from and node_to must be set.\nnode_to no string - - Name of the Node that this Profile feeds energy to. Exactly one of node_from and node_to must be set.\nmode no - - fixed 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).\nlb no numeric power -infty The lower bound of the range of this Profile (must be used together with mode: ranged).\nub no numeric power +infty The upper bound of the range of this Profile (must be used together with mode: ranged).\ncost no numeric monetary per energy 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: aux_value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_profile\").var.aux_value# Python\nmodel.get_component(\"your_profile\").var.aux_valueYou can find the full implementation and all details here: 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:fixed: The value is already handled by the constant term of profile.exp.value and NO variable is constructed.\ncreate, 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 detailled 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 Nodes that the profile is connected to, resulting inbeginaligned\n textprofilenode_fromtextinjection_t = textprofilenode_fromtextinjection_t - textvalue_t qquad forall t in T \n textprofilenode_totextinjection_t = textprofilenode_totextinjection_t + textvalue_t qquad forall t in T\nendalignedmath\n\nExpressions\n\ndetails: value\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_profile\").exp.value# Python\nmodel.get_component(\"your_profile\").exp.valueYou can find the full implementation and all details here: IESopt.jl.Cosntruct 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 intialized based on profile.value.\n\nConstraints\n\ndetails: value_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_profile\").con.value_bounds# Python\nmodel.get_component(\"your_profile\").con.value_boundsYou can find the full implementation and all details here: 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 === :destroybeginaligned\n textaux_value_t geq 0 qquad forall t in T\nendalignedmathif profile.mode === :rangedbeginaligned\n textvalue_t geq textlb_t qquad forall t in T \n textvalue_t leq textub_t qquad forall t in T\nendalignedmathHere, lb and ub can be left empty, which drops the respective constraint.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_profile\").obj.cost# Python\nmodel.get_component(\"your_profile\").obj.costYou can find the full implementation and all details here: 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_tin T textvalue_t cdot textprofilecost_t cdot omega_tmathHere omega_t is the weight of Snapshot t, and textvalue_t actually refers to the value of profile.exp.value[t] (and not only on the maybe non-existing variable).\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Unit-pages-manual___reference-api","page":"API","title":"IESopt.Unit","text":"A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.\n\ndetails: Basic Examples\nA Unit that represents a basic gas turbine:gas_turbine:\n type: Unit\n inputs: {gas: gas_grid}\n outputs: {electricity: node, co2: total_co2}\n conversion: 1 gas -> 0.4 electricity + 0.2 co2\n capacity: 10 out:electricityA Unit that represents a basic wind turbine:wind_turbine:\n type: Unit\n outputs: {electricity: node}\n conversion: ~ -> 1 electricity\n capacity: 10 out:electricity\n availability_factor: wind_factor@input_data\n marginal_cost: 1.7 per out:electricityA Unit that represents a basic heat pump, utilizing a varying COP:heatpump:\n type: Unit\n inputs: {electricity: grid}\n outputs: {heat: heat_system}\n conversion: 1 electricity -> cop@inputfile heat\n capacity: 10 in:electricity\n\nParameters\n\nName Mandatory Values Unit Default Description\nconversion yes string - - 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).\ncapacity yes value dir:carrier - - 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\").\noutputs yes dict - - Dictionary specifying the output \"ports\" of this Unit. Refer to the basic examples for the general syntax.\ninputs no dict - - 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.\navailability no numeric power +infty 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 50 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.\navailability_factor no in 0 1 - 1 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.\nadapt_min_to_availability no true, false - false 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.\nmarginal_cost no value per dir:carrier monetary per energy 0 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.\nenable_ramp_up no true, false - false Enables calculation of upward ramps. Ramping is based on the carrier specified in capacity.\nenable_ramp_down no true, false - false Enables calculation of downward ramps. Ramping is based on the carrier specified in capacity.\nramp_up_cost no numeric monetary per power 0 Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.\nramp_down_cost no numeric monetary per power 0 Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.\nramp_up_limit no in 0 1 - 1 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.\nramp_down_limit no in 0 1 - 1 Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.\nmin_on_time no numeric hours 0 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.\nmin_off_time no numeric hours 0 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.\non_time_before no numeric hours 0 Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.\noff_time_before no numeric hours 0 Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.\nis_on_before no numeric - 1 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.\nunit_commitment no off, linear, binary, integer - off 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).\nunit_count no numeric - 1 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, ...).\nmin_conversion no in 0 1 - - 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.\nconversion_at_min no string - - 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.\nstartup_cost no numeric monetary per start 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: conversion\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion# Python\nmodel.get_component(\"your_unit\").var.conversionYou can find the full implementation and all details here: 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.info: Info\nThis 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).\n\ndetails: conversion_connect\nNo documentation found.IESopt._unit_var_conversion_connect! is a Function.tip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion_connect# Python\nmodel.get_component(\"your_unit\").var.conversion_connectYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: ison\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ison# Python\nmodel.get_component(\"your_unit\").var.isonYou can find the full implementation and all details here: 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 textison leq textunitcount, with textunitcount 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].\n\ndetails: ramp\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ramp# Python\nmodel.get_component(\"your_unit\").var.rampYou can find the full implementation and all details here: 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!\n\ndetails: startup\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.startup# Python\nmodel.get_component(\"your_unit\").var.startupYou can find the full implementation and all details here: 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 textstartup leq textunitcount, with textunitcount 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.\n\nExpressions\n\nConstraints\n\ndetails: conversion_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.conversion_bounds# Python\nmodel.get_component(\"your_unit\").con.conversion_boundsYou can find the full implementation and all details here: 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)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T \n textconversion_t leq textavailability_t qquad forall t in T\nendalignedmathThis effectively results in textconversion_t leq min(textcapacity_textonline t textavailability_t).if !isnothing(unit.availability_factor)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t cdot textavailability_textfactor t qquad forall t in T\nendalignedmathinfo: Info\nIf 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:beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T\nendalignedmath\n\ndetails: ison\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ison# Python\nmodel.get_component(\"your_unit\").con.isonYou can find the full implementation and all details here: IESopt.jl.Construct the upper bound for var_ison, based on unit.unit_count, if it is handled by an external Decision.\n\ndetails: min_onoff_time\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.min_onoff_time# Python\nmodel.get_component(\"your_unit\").con.min_onoff_timeYou can find the full implementation and all details here: IESopt.jl.Add the constraints modeling min on- or off-time of a Unit to the model.This constructs the constraintsbeginalign\n sum_t = t^t + textmin_on_time ison_t = textmin_on_time cdot (ison_t - ison_t-1) qquad forall t in T \n sum_t = t^t + textmin_off_time (1 - ison_t) = textmin_off_time cdot (ison_t-1 - ison_t) qquad forall t in T\nendalign\n\nrespecting on_time_before and off_time_before and is_on_before See the code for more details\n\n info Aggregated units\n This is currently not fully adapted to account for Units with unit_count 1math\n\ndetails: ramp\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp# Python\nmodel.get_component(\"your_unit\").con.rampYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t geq textconversion_t - textconversion_t-1 qquad forall t in T \n textramp_textdown t geq textconversion_t-1 - textconversion_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0info: Info\nThis 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.\n\ndetails: ramp_limit\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp_limit# Python\nmodel.get_component(\"your_unit\").con.ramp_limitYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t leq textramplimit_textup cdot textcapacity_textmax cdot omega_t qquad forall t in T \n textramp_textdown t leq textramplimit_textdown cdot textcapacity_textmax cdot omega_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0\n\ndetails: startup\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.startup# Python\nmodel.get_component(\"your_unit\").con.startupYou can find the full implementation and all details here: 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:beginaligned\n textstartup_textup t geq textison_t - textison_t-1 qquad forall t in T\nendalignedmathThis 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\n\nObjectives\n\ndetails: marginal_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.marginal_cost# Python\nmodel.get_component(\"your_unit\").obj.marginal_costYou can find the full implementation and all details here: IESopt.jl.Add the (potential) cost of this unit's conversion (unit.marginal_cost) to the global objective function.sum_t in T textconversion_t cdot textmarginalcost_t cdot omega_tmath\n\ndetails: ramp_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.ramp_cost# Python\nmodel.get_component(\"your_unit\").obj.ramp_costYou can find the full implementation and all details here: 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 textramp_textup t cdot textrampcost_textup + textramp_textdown t cdot textrampcost_textdownmath\n\ndetails: startup_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.startup_cost# Python\nmodel.get_component(\"your_unit\").obj.startup_costYou can find the full implementation and all details here: 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 textstartup_t cdot textstartupcostmath\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#Functions","page":"API","title":"Functions","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"run\ngenerate!\noptimize!","category":"page"},{"location":"pages/manual___reference/api/#IESopt.run","page":"API","title":"IESopt.run","text":"run(filename::String; verbosity=nothing, kwargs...)\n\nBuild, optimize, and return a model.\n\nArguments\n\nfilename::String: The path to the top-level configuration file.\nverbosity: The verbosity level to use. Supports true (= verbose mode), \"warning\" (= warnings and above), and false (suppressing logs).\n\nIf verbosity = true, the verbosity setting of the solver defaults to true as well, otherwise it defaults to false (the verbosity setting of the solver can also be directly controled using the verbosity_solve setting in the top-level config file).\n\nKeyword Arguments\n\nKeyword arguments are passed to the generate! function.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.generate!","page":"API","title":"IESopt.generate!","text":"generate!(filename::String)\n\nBuilds and returns a model using the IESopt framework.\n\nThis loads the configuration file specified by filename. Requires full specification of the solver entry in config.\n\n\n\n\n\ngenerate!(model::JuMP.Model, filename::String)\n\nBuilds a model using the IESopt framework, \"into\" the provided model.\n\nThis loads the configuration file specified by filename. Be careful when creating your model in any other way than in the provided examples, as this can conflict with IESopt internals (especially for model/optimizer combinations that do not support bridges). Returns the model for convenience, even though it is modified in place.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.optimize!","page":"API","title":"IESopt.optimize!","text":"optimize!(model::JuMP.Model; save_results::Bool=true, kwargs...)\n\nUse JuMP.optimize! to optimize the given model, optionally serializing the model afterwards for later use.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"component","category":"page"},{"location":"pages/manual___reference/api/#IESopt.component","page":"API","title":"IESopt.component","text":"function component(model::JuMP.Model, component_name::String)\n\nGet the component component_name from model.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"compute_IIS","category":"page"},{"location":"pages/manual___reference/api/#IESopt.compute_IIS","page":"API","title":"IESopt.compute_IIS","text":"function compute_IIS(model::JuMP.Model; filename::String = \"\")\n\nCompute 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.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"overview\npack\nunpack","category":"page"},{"location":"pages/manual___reference/api/#IESopt.overview","page":"API","title":"IESopt.overview","text":"overview(file::String)\n\nExtracts the most important information from an IESopt model file, and returns it as a dictionary.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.pack","page":"API","title":"IESopt.pack","text":"pack(file::String; out::String=\"\", method=:store)\n\nPacks the IESopt model specified by the top-level config file file into single file.\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.unpack","page":"API","title":"IESopt.unpack","text":"unpack(file::String; out::String=\"\", force_overwrite::Bool=false)\n\nUnpacks the IESopt model specified by file.\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#Python","page":"API","title":"Python","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"To be added.","category":"page"},{"location":"pages/dev_docs/#Developer-Documentation","page":"Developer Documentation","title":"Developer Documentation","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"The following sections provide rough guidelines on how to work with IESopt, and mostly IESopt.jl, improving the documentation, testing, and implementing new features.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"info: Helping out\nLooking for things to contribute, with a low entry barrier (besides any open issue)? Check for To be added (especially in the documentation), or TODO (especially in the code).","category":"page"},{"location":"pages/dev_docs/#Getting-started","page":"Developer Documentation","title":"Getting started","text":"","category":"section"},{"location":"pages/dev_docs/#General","page":"Developer Documentation","title":"General","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Install Julia.\nInstall VSCode, and some extensions (this step is optional, but highly recommended).\nClone/fork the repository.\nHappy coding (... see below)!","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"If you are new to Julia, or not entirely sure how everything works - let's talk. We are happy to help you get started, and to guide you through the process. Stuff like Revise.jl can be a huge help, and we can show you how to use it. Further, if you are coming from, e.g., a standard Python background, the advantages of a dynamic REPL-driven development may be new to you.","category":"page"},{"location":"pages/dev_docs/#Tips-and-tricks","page":"Developer Documentation","title":"Tips and tricks","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Check out Modern Julia Workflows.\nCheck out the Julia Discourse.\nRead up details on Revise usage.","category":"page"},{"location":"pages/dev_docs/#Architecture","page":"Developer Documentation","title":"Architecture","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"See ARCHITECTURE.md for more information.","category":"page"},{"location":"pages/dev_docs/#Coding-conventions","page":"Developer Documentation","title":"Coding conventions","text":"","category":"section"},{"location":"pages/dev_docs/#Branches","page":"Developer Documentation","title":"Branches","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"We mainly use a \"feature branch workflow\", similar to \"trunk based development\". We strive to keep the main branch as clean as possible (docs and tests should build and pass), and work on a separate development (trunk) branch. For larger changes, consider starting new feature branches. Where possible we use PRs (or merge requests) to get changes into the main branch, while doing a (light) code review for each other.","category":"page"},{"location":"pages/dev_docs/#Naming-conventions","page":"Developer Documentation","title":"Naming conventions","text":"","category":"section"},{"location":"pages/dev_docs/#Julia","page":"Developer Documentation","title":"Julia","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"We make use of the following naming conventions, which slightly differ from the Julia naming conventions, but are similar to other large projects out there:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Types and similar items use CamelCase, e.g., MyType.\nFunctions and variables use snake_case, e.g., my_function(...).\nFunctions that modify their arguments should end with an exclamation mark, e.g., optimize!(...).\nFunctions and variables should actually make use of underscores, whenever reasonable (and not only when absolutely necessary), e.g., set_to_zero!(...) (not settozero!(...) like the Julia naming conventions may suggest).\nConstants are written in UPPERCASE, e.g., MY_CONSTANT.","category":"page"},{"location":"pages/dev_docs/#Python","page":"Developer Documentation","title":"Python","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"To be added (black with --line-length 88, ruff, standard naming conventions, ...).","category":"page"},{"location":"pages/dev_docs/#Conventional-commits","page":"Developer Documentation","title":"Conventional commits","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Refer to the Conventional Commits specification for a detailed explanation. In short, we use the following format:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"feat: implemented new feature X\nfix: fixed the bug X\nrefactor: refactored the code X\ndocs: updated the documentation X\ntest: added a new test for X\nchore: updated the dependencies X","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"As indicated we use docs, but test (and not tests), which can be remembered by looking at the folder names: docs/ and test/.","category":"page"},{"location":"pages/dev_docs/#Version-numbers","page":"Developer Documentation","title":"Version numbers","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Documenter.jl (as of May, 22nd, 2024) aggressively states: \"Documenter, like any good Julia package, follows semantic versioning (SemVer).\"","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Unfortunately, semantic versioning may not be as well suited as one might think for a package like IESopt.jl. Some reasons are:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"While the (Julia) API has been stable for a long time (in a sense of: backwards-compatible), we consider the YAML configuration syntax as main part of IESopt's \"API\". This syntax has changed multiple times, and will likely change in the future. Maintaining full backwards compatibility for this is not feasible all the time. This induces a need for a major version bump, even though the Julia API has not changed.\nA mere bug fix, even a small one, in IESopt.jl may very likely induce changed results of any model run. A user could see vastly different results between v1.3.10 and v1.3.11, even though the changes are minimal. This envolves not taking patch updates lightly, which is not the case in many other packages.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"However, as indicated, the use of semantic versioning is still \"expected\" by large parts of the Julia community, and not doing so may make it harder for some users, and/or some interactions with other packages. So...","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"IESopt.jl makes use of semantic versioning!\nYou are advised to consider the above points when deciding on version bumps.\nAdvise users and make sure you properly document changes.\nExpect rising major version numbers.","category":"page"},{"location":"pages/dev_docs/#Working-with-VSCode","page":"Developer Documentation","title":"Working with VSCode","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"The following set of extensions may be helpful, either for development or documentation purposes:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Julia\nLive Preview\nMarkdown Julia\nMarkdown Preview GitHub Styling\nmarkdownlint\nRainbow CSV","category":"page"},{"location":"pages/dev_docs/#Improving-the-documentation","page":"Developer Documentation","title":"Improving the documentation","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"# TODO: refactor to new syntax: `julia --project=. -e 'include(\"docs/liveserver.jl\")'`","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Docstrings of public entries of IESopt.jl are taken from the code, see src/.... Besides that, the documentation is contained in the docs/src/... folder, and built based on docs/make.jl, using Documenter.jl.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"info: Diátaxis\nConsider checking out the excellent \"project\" Diátaxis, by Daniele Procida. We try to adhere to the principles outlined there, and you may find them useful as well. For a quick intro, you may consider starting here: The difference between a tutorial and how-to guide.","category":"page"},{"location":"pages/dev_docs/#Setup","page":"Developer Documentation","title":"Setup","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Make sure that you","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"have a working installation of Julia (otherwise go to julialang.org and install it; we recommend sticking to Juliaup if asked), and\nhave a terminal of your choice launched at IESopt.jl/.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Then, run the following command once to set up the environment used for the documentation:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"julia --project=docs -e 'import Pkg; Pkg.instantiate()'","category":"page"},{"location":"pages/dev_docs/#Building-the-documentation","page":"Developer Documentation","title":"Building the documentation","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Launch an interactive web server that shows you the documentation while you are working on it:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"julia --project=docs -e 'using LiveServer; servedocs(; launch_browser=true)'","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Note: While the above is your best choice in 95% of all cases, you can also manually build the documentation usingjulia --project=docs docs/make.jlwhich may be useful if you modify source files (which LiveServer.jl currently does not track in a convenient way). Note however that this will not automatically reload the documentation in your browser (but may in VSCode if you right-click the index.html file and select Preview, using the Live Preview extension), and may fail to properly account for image/... paths.","category":"page"},{"location":"pages/dev_docs/#Code-formatting","page":"Developer Documentation","title":"Code formatting","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"We provide a custom .JuliaFormatter.toml file that should be used to format the code. The easiest way to use it is to:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Add JuliaFormatter to your Julia base environment by running ] add JuliaFormatter in the package mode of your Julia REPL (without an active IESopt environment).\nRun using JuliaFormatter in the Julia REPL (this now works even if you activated the IESopt environment).\nRun format(\".\") in the Julia REPL to format all files in your current directory. This takes a bit of compile time, but after the first run, it should be fairly fast.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Make sure you checked the formatting, before finalizing your changes or opening a PR. If you forgot to include formatting in your actual commits (we all do...), and cannot reasonably ammend them, add all formatting changes at the end in a single commit with the message:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"git commit -m \"chore: formatting\"","category":"page"},{"location":"pages/dev_docs/#Testing","page":"Developer Documentation","title":"Testing","text":"","category":"section"},{"location":"pages/dev_docs/#Running-tests-locally","page":"Developer Documentation","title":"Running tests locally","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Launch a new Julia REPL (hit Alt+J and then Alt+O in VSCode), enter Package mode (by pressing ] in your REPL, now showing (IESopt) pkg>), and then execute all tests by running:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"(IESopt) pkg> test","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Templates:-Part-I","page":"Templates: Part I","title":"Templates: Part I","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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).","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#The-basic-structure","page":"Templates: Part I","title":"The basic structure","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Possiblities for that could be:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"CustomHeatPump, if you do not have any more details\nGroundSourceHeatPump, if we want to implement a ground-source heat pump with different parameters/features than the standard one\nFooHeatPump, if you need it specifically for a project called \"Foo\"","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"info: Naming conventions\nTemplates follow a naming convention similar to PascalCase:The name must start with an upper-case letter\nIt must consist of at least two letters\nNumbers and special characters are not allowed","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"parameters:\n p_nom: null\n electricity_from: null\n heat_from: null\n heat_to: null\n cop: null","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"p_nom: The nominal power of the heat pump, which will be specified on the electricity input side\nelectricity_from: The Node that this heat pump is connected to for electricity input\nheat_from: The Node that this heat pump is connected to for heat input\nheat_to: The Node that this heat pump is connected to for heat output\ncop: The coefficient of performance of the heat pump","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"component:\n type: Unit\n inputs: {electricity: , heat: }\n outputs: {heat: }\n conversion: 1 electricity + ( - 1) heat -> heat\n capacity: in:electricity","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"# other parts of the configuration file\n# ...\n\ncomponents:\n # some other components\n # ...\n\n heat_pump:\n template: CustomHeatPump\n parameters:\n p_nom: 10\n electricity_from: electricity\n heat_from: ambient\n heat_to: heating\n cop: 3","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Accounting-for-different-configurations","page":"Templates: Part I","title":"Accounting for different configurations","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"# # Custom Heat Pump\n\n# A (custom) heat pump that consumes electricity and heat, and produces heat.\n\n# ## Parameters\n# - `p_nom`: The nominal power (electricity) of the heat pump.\n# - `electricity_from`: The `Node` that this heat pump is connected to for electricity input.\n# - `heat_from`: The `Node` that this heat pump is connected to for heat input.\n# - `heat_to`: The `Node` that this heat pump is connected to for heat output.\n# - `cop`: The coefficient of performance of the heat pump.\n\n# ## Components\n# _to be added_\n\n# ## Usage\n# _to be added_\n\n# ## Details\n# _to be added_\n\nparameters:\n # ...","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"info: Docstring format\nAll 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Let's continue with accounting for different configurations. We will cover the following steps:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Making the heat_from parameter optional\nExtending the template to allow for sizing the heat pump (an investment decision)\nHandling more complex COP configurations","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Optional-parameter-and-sizing-decision","page":"Templates: Part I","title":"Optional parameter and sizing decision","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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\":","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.\nprepare: 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).\nfinalize: 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Let's start by adding the functions entry (which we suggest doing at the end of the file):","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"# ... the whole docstring ...\n\nparameters:\n # ...\n\ncomponent:\n # ...\n\nfunctions:\n validate: |\n # ... we will put the validation code here ...\n prepare: |\n # ... we will put the preparation code here ...\n finalize: |\n # ... we will put the finalization code here ...","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Validation","page":"Templates: Part I","title":"Validation","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"The validation function is used to check if the parameters are valid. Add the following code to the validate section:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"functions:\n validate: |\n # Check if `p_nom` is non-negative.\n @check get(\"p_nom\") isa Number\n @check get(\"p_nom\") >= 0\n\n # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`.\n @check get(\"electricity_from\") isa String\n @check get(\"heat_from\") isa String || isnothing(get(\"heat_from\"))\n @check get(\"heat_to\") isa String\n\n # Check if `cop` is positive.\n @check get(\"cop\") isa Number\n @check get(\"cop\") > 0\n # ... the rest of the template ...","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Let's go through this step by step:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"You can start comments (as separate line or inline) with #, as you would in Python.\nYou can use get(\"some_param\") to access the value of a parameter.\nYou 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.\nYou 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).\nData 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).\nLogical operators are similar to Python, so || is like or, and && is like and.\nIf all checks pass, the template is considered valid, and the model can be built.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Preparation","page":"Templates: Part I","title":"Preparation","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"parameters:\n p_nom: null\n p_nom_max: null\n electricity_from: null\n heat_from: null\n heat_to: null\n cop: null\n _inputs: null\n _conversion: null\n _capacity: null\n _invest: null","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"One step at a time. We added the following parameters:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.\n_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.\n_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).\n_conversion: This is another internal parameter, which we will use to store the conversion formula.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"components:\n unit:\n type: Unit\n inputs: <_inputs>\n outputs: {heat: }\n conversion: <_conversion>\n capacity: <_capacity> in:electricity\n \n decision:\n type: Decision\n enabled: <_invest>\n lb: \n ub: ","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"So ... a lot of changes. Let's go through them step by step:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"We changed component to components, because we now have multiple components.\nWe added a unit component, which is the actual heat pump. We replaced the fixed values with the internal parameters.\nWe 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"info: Naming the components\nThe 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Onto the actual functionality. Let's add the prepare function, and some additional validation code:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"functions:\n validate: |\n # ... the previous validation code ...\n\n # Check if `p_nom_max` is either `nothing` or at least `p_nom`.\n @check isnothing(get(\"p_nom_max\")) || (get(\"p_nom_max\") isa Number && get(\"p_nom_max\") >= get(\"p_nom\"))\n prepare: |\n # Determine if investment should be enabled, and set the parameter (used to enable `decision`).\n invest = !isnothing(get(\"p_nom_max\")) && get(\"p_nom_max\") > get(\"p_nom\")\n self = get(\"self\")\n\n set(\"_invest\", invest)\n if invest\n # Set the capacity to the size of the decision variable.\n set(\"_capacity\", \"$(self).decision:value\")\n else\n # Set the capacity to the value of `p_nom`.\n set(\"_capacity\", get(\"p_nom\"))\n end\n\n # Prepare some helper variables to make the code afterwards more readable.\n elec_from = get(\"electricity_from\")\n heat_from = get(\"heat_from\")\n cop = get(\"cop\")\n\n # Handle the optional `heat_from` parameter.\n if isnothing(heat_from)\n # If `heat_from` is not specified, we just use electricity as input.\n set(\"_inputs\", \"{electricity: $(elec_from)}\")\n set(\"_conversion\", \"1 electricity -> $(cop) heat\")\n else\n # If `heat_from` is specified, we now have to account for two inputs.\n set(\"_inputs\", \"{electricity: $(elec_from), heat: $(heat_from)}\")\n set(\"_conversion\", \"1 electricity + $(cop - 1) heat -> $(cop) heat\")\n end","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Once again, let's go through this step by step:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Complex-COP-configurations","page":"Templates: Part I","title":"Complex COP configurations","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#The-finalize-function","page":"Templates: Part I","title":"The finalize function","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Finalizing-the-docstring","page":"Templates: Part I","title":"Finalizing the docstring","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Conclusion","page":"Templates: Part I","title":"Conclusion","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"details: Complete template YAML\n# # Custom Heat Pump\n\n# A (custom) heat pump that consumes electricity and heat, and produces heat.\n\n# ## Parameters\n# - `p_nom`: The nominal power (electricity) of the heat pump.\n# - `electricity_from`: The `Node` that this heat pump is connected to for electricity input.\n# - `heat_from`: The `Node` that this heat pump is connected to for heat input.\n# - `heat_to`: The `Node` that this heat pump is connected to for heat output.\n# - `cop`: The coefficient of performance of the heat pump.\n\n# ## Components\n# _to be added_\n\n# ## Usage\n# _to be added_\n\n# ## Details\n# _to be added_\n\nparameters:\n p_nom: null\n p_nom_max: null\n electricity_from: null\n heat_from: null\n heat_to: null\n cop: null\n _inputs: null\n _conversion: null\n _capacity: null\n _invest: null\n\ncomponents:\n unit:\n type: Unit\n inputs: <_inputs>\n outputs: {heat: }\n conversion: <_conversion>\n capacity: <_capacity> in:electricity\n \n decision:\n type: Decision\n enabled: <_invest>\n lb: \n ub: \n\nfunctions:\n validate: |\n # Check if `p_nom` is non-negative.\n @check get(\"p_nom\") isa Number\n @check get(\"p_nom\") >= 0\n\n # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`.\n @check get(\"electricity_from\") isa String\n @check get(\"heat_from\") isa String || isnothing(get(\"heat_from\"))\n @check get(\"heat_to\") isa String\n\n # Check if `cop` is positive.\n @check get(\"cop\") isa Number\n @check get(\"cop\") > 0\n\n # Check if `p_nom_max` is either `nothing` or at least `p_nom`.\n @check isnothing(get(\"p_nom_max\")) || (get(\"p_nom_max\") isa Number && get(\"p_nom_max\") >= get(\"p_nom\"))\n prepare: |\n # Determine if investment should be enabled, and set the parameter (used to enable `decision`).\n invest = !isnothing(get(\"p_nom_max\")) && get(\"p_nom_max\") > get(\"p_nom\")\n self = get(\"self\")\n\n set(\"_invest\", invest)\n if invest\n # Set the capacity to the size of the decision variable.\n set(\"_capacity\", \"$(self).decision:value\")\n else\n # Set the capacity to the value of `p_nom`.\n set(\"_capacity\", get(\"p_nom\"))\n end\n\n # Prepare some helper variables to make the code afterwards more readable.\n elec_from = get(\"electricity_from\")\n heat_from = get(\"heat_from\")\n cop = get(\"cop\")\n\n # Handle the optional `heat_from` parameter.\n if isnothing(heat_from)\n # If `heat_from` is not specified, we just use electricity as input.\n set(\"_inputs\", \"{electricity: $(elec_from)}\")\n set(\"_conversion\", \"1 electricity -> $(cop) heat\")\n else\n # If `heat_from` is specified, we now have to account for two inputs.\n set(\"_inputs\", \"{electricity: $(elec_from), heat: $(heat_from)}\")\n set(\"_conversion\", \"1 electricity + $(cop - 1) heat -> $(cop) heat\")\n end","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Next-steps","page":"Templates: Part I","title":"Next steps","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/user_guide/general/#General","page":"General","title":"General","text":"","category":"section"},{"location":"pages/user_guide/general/","page":"General","title":"General","text":"To be added.","category":"page"},{"location":"pages/manual___reference/yaml/#YAML","page":"YAML","title":"YAML","text":"","category":"section"},{"location":"pages/manual___reference/yaml/","page":"YAML","title":"YAML","text":"To be added (based on docstrings from, e.g., the configs).","category":"page"},{"location":"pages/user_guide/sectors/electricity/#Electricity","page":"Electricity","title":"Electricity","text":"","category":"section"},{"location":"pages/user_guide/sectors/electricity/","page":"Electricity","title":"Electricity","text":"To be added.","category":"page"},{"location":"pages/user_guide/sectors/gas/#Gas","page":"Gas","title":"Gas","text":"","category":"section"},{"location":"pages/user_guide/sectors/gas/","page":"Gas","title":"Gas","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/#Custom-Components","page":"Custom Components","title":"Custom Components","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/","page":"Custom Components","title":"Custom Components","text":"To be added.","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#Templates","page":"Templates","title":"Templates","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"\"Core Templates\" are pre- or user-defined templates, that group and/or reparameterize IESopt.jl CoreComponents, or even other Core Templates. They are used to define more complex building blocks, that can be used in the optimization model.","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#General-structure","page":"Templates","title":"General structure","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"A Core Template is defined by a YAML file, ending in .iesopt.template.yaml, that may contain the following entries:","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"parameters (optional): a dictionary of parameters that can be used to reparameterize the template\nfunctions (optional): validate, prepare, and finalize functions, containing Julia code\nfiles (optional): files can be loaded here \"on demand\" similar to the top-level config","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"Further exactly one of the following entries is required:","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"components: a dictionary of components that are part of the template, or\ncomponent: a single component that is part of the template","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#Example:-A-simple-template","page":"Templates","title":"Example: A simple template","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"parameters:\n some_custom_param: null\n another_one: 100.0\n one_more: heat\n\ncomponents:\n a_node:\n type: Node\n\n b_node:\n type: Node","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (more details).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#manual_templates_validate","page":"Templates","title":"Validate","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"validation: |\n @check parameters[\"carrier\"] isa String\n @check parameters[\"carrier\"] in [\"heat\", \"electricity\"]","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (more examples).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#manual_templates_prepare","page":"Templates","title":"Prepare","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#manual_templates_finalize","page":"Templates","title":"Finalize","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/tutorials/setup/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"To be added.","category":"page"},{"location":"pages/tutorials/setup/#Python","page":"Installation","title":"Python","text":"","category":"section"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"To be added.","category":"page"},{"location":"pages/tutorials/setup/#Julia","page":"Installation","title":"Julia","text":"","category":"section"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"To be added.","category":"page"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"Add something here.","category":"page"},{"location":"pages/user_guide/custom_functionality/addons/#Addons","page":"Addons","title":"Addons","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/addons/","page":"Addons","title":"Addons","text":"To be added.","category":"page"},{"location":"pages/manual___reference/templates/#Templates","page":"Templates","title":"Templates","text":"","category":"section"},{"location":"pages/manual___reference/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Templates:-Part-II","page":"Templates: Part II","title":"Templates: Part II","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Catching-errors","page":"Templates: Part II","title":"Catching errors","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Separate-.jl-files","page":"Templates: Part II","title":"Separate .jl files","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Some-examples","page":"Templates: Part II","title":"Some examples","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/#HeatPump","page":"Templates: Part II","title":"HeatPump","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#BESS","page":"Templates: Part II","title":"BESS","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#PV","page":"Templates: Part II","title":"PV","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#CHP","page":"Templates: Part II","title":"CHP","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/references/#Overview","page":"References","title":"Overview","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"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 Contributing section.","category":"page"},{"location":"pages/references/#Publications","page":"References","title":"Publications","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"using IESopt, Markdown\npath = abspath(dirname(pathof(IESopt)), \"..\", \"docs\", \"src\", \"pages\", \"references\", \"publications\")\n\nref_str = \"\"\nitems = []\nfor file in readdir(path; join=true)\n endswith(file, \".md\") || continue\n raw_content = read(file, String)\n \n fulltitle = \"\"\n header = \"\"\n content = \"\"\n for line in eachline(IOBuffer(raw_content))\n if startswith(line, \"# \")\n fulltitle = line[3:end]\n if length(fulltitle) > 92\n header = \"$(fulltitle[1:88]) ...\"\n else\n header = fulltitle\n end\n else\n content = \"$(content)\\n$(line)\"\n end\n end\n\n push!(items, \"\"\"\n !!! details \"$(header)\"\n _**$(fulltitle)**_\n $(replace(content, \"\\n\" => \"\\n \"))\n \"\"\")\nend\n\nMarkdown.parse(join(items, \"\\n\"))","category":"page"},{"location":"pages/references/#Projects","page":"References","title":"Projects","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"using IESopt, Markdown\npath = abspath(dirname(pathof(IESopt)), \"..\", \"docs\", \"src\", \"pages\", \"references\", \"projects\")\n\nref_str = \"\"\nitems = []\nfor file in readdir(path; join=true)\n endswith(file, \".md\") || continue\n raw_content = read(file, String)\n \n fulltitle = \"\"\n header = \"\"\n content = \"\"\n for line in eachline(IOBuffer(raw_content))\n if startswith(line, \"# \")\n fulltitle = line[3:end]\n if length(fulltitle) > 92\n header = \"$(fulltitle[1:88]) ...\"\n else\n header = fulltitle\n end\n else\n content = \"$(content)\\n$(line)\"\n end\n end\n\n push!(items, \"\"\"\n !!! details \"$(header)\"\n _**$(fulltitle)**_\n $(replace(content, \"\\n\" => \"\\n \"))\n \"\"\")\nend\n\nMarkdown.parse(join(items, \"\\n\"))","category":"page"},{"location":"pages/references/#Contributing","page":"References","title":"Contributing","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"To contribute a new reference, either","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"fork the IESopt repository, and directly add to the above list, or\nopen an issue with the reference details.","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"See the template below for the structure of a reference.","category":"page"},{"location":"pages/references/#Publication-Template","page":"References","title":"Publication Template","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"Please stick to APA format here, and always include a link as badge (if possible a DOI, if not other links are okay too).","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"# Title of the Publication\n\n[![CITATION](url-of-your-badge)](link-to-doi-or-pure-or-other)\n\n> _**Abstract --**_ Put your abstract text here.\n\n> _**Keywords --**_ Put some, Keywords, In this, List\n\n!!! details \"Expand: Show citation\"\n > Add your (APA styled!) citation here\n","category":"page"},{"location":"pages/references/#Project-Template","page":"References","title":"Project Template","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"To be added.","category":"page"},{"location":"pages/references/#Creating-citation-badges","page":"References","title":"Creating citation badges","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"You can use shields.io to create badges, or use standardized ones that you already have (e.g., from Zenodo), otherwhise stick to the ones provided below.","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"Pure: (publications.ait.ac.at)[![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-none?style=social)](ADDYOURLINKHERE)","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"DOI:[![CITATION](https://img.shields.io/badge/DOI-10.XXXX%2Fname.YYYY.ZZZZZZ-none?style=social)](https://doi.org/10.XXXX/name.YYYY.ZZZZZZ)","category":"page"},{"location":"pages/tutorials/results/#Result-extraction","page":"Result extraction","title":"Result extraction","text":"","category":"section"},{"location":"pages/tutorials/results/","page":"Result extraction","title":"Result extraction","text":"To be added.","category":"page"},{"location":"pages/changelog/#Changelog","page":"Changelog","title":"Changelog","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"All notable changes to this project will be documented in this file.","category":"page"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"pages/changelog/#[1.0.5]-2024-09-11","page":"Changelog","title":"[1.0.5] - 2024-09-11","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Bug fixes in custom objective building, and better exception handling for log files.","category":"page"},{"location":"pages/changelog/#[1.0.4]-2024-07-26","page":"Changelog","title":"[1.0.4] - 2024-07-26","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Migrate full core component parameter docstrings.","category":"page"},{"location":"pages/changelog/#[1.0.3]-2024-06-18","page":"Changelog","title":"[1.0.3] - 2024-06-18","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Relax version requirements on IESoptLib to include all v0.2.z versions.","category":"page"},{"location":"pages/changelog/#[1.0.2]-2024-06-10","page":"Changelog","title":"[1.0.2] - 2024-06-10","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Fix solver setup for various workflows.","category":"page"},{"location":"pages/changelog/#Changed","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"IESoptLib and HiGHS are again required dependencies.","category":"page"},{"location":"pages/changelog/#[1.0.1]-2024-06-09","page":"Changelog","title":"[1.0.1] - 2024-06-09","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Added extensions to properly handle loading IESoptLib and various solvers.","category":"page"},{"location":"pages/changelog/#Changed-2","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"IESoptLib and HiGHS are no longer required dependencies.","category":"page"},{"location":"pages/changelog/#Fixed","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Dynamic loading of weakdeps now works properly.","category":"page"},{"location":"pages/changelog/#[1.0.0]-2024-06-01","page":"Changelog","title":"[1.0.0] - 2024-06-01","text":"","category":"section"},{"location":"pages/changelog/#Added","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Initial public release of IESopt.jl","category":"page"},{"location":"pages/tutorials/first_model/#First-steps","page":"First steps","title":"First steps","text":"","category":"section"},{"location":"pages/tutorials/first_model/#Need-help?","page":"First steps","title":"Need help?","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added.","category":"page"},{"location":"pages/tutorials/first_model/#Overview","page":"First steps","title":"Overview","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"IESopt (the framework) consists of various sub-projects. It is a component-based optimization framework, where each component can be seen as block containing some predefined functionality. There are five \"core components\": Connection, Decision, Node, Profile, and Unit. These will be used to define arbitrary energy system models, similar to how a general commodity flow model works. Furthermore, they can be combined to create more complicated (non-core) components.","category":"page"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"In their most basic form, core components can be described as:","category":"page"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, ...\nA 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.\nA Node represents a basic intersection/hub for energy flows. This can for example be some sort of bus (for electrical systems).\nA Profile allows representing exogenous functionality with a support for time series data.\nA Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.","category":"page"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"For most models, the Units will pack the most raw functionality, while the other components represent the structure of the overall model.","category":"page"},{"location":"pages/tutorials/first_model/#Your-first-model","page":"First steps","title":"Your first model","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Model-config","page":"First steps","title":"Model config","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Energy-carriers","page":"First steps","title":"Energy carriers","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Model-components","page":"First steps","title":"Model components","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Final-config-file","page":"First steps","title":"Final config file","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Running-the-optimization","page":"First steps","title":"Running the optimization","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Extracting-results","page":"First steps","title":"Extracting results","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#General-result-structure","page":"First steps","title":"General result structure","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Changing-the-model-config","page":"First steps","title":"Changing the model config","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Adapting-components","page":"First steps","title":"Adapting components","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Analyzing-the-results","page":"First steps","title":"Analyzing the results","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Extracting-results-directly-into-pd.DataFrames","page":"First steps","title":"Extracting results directly into pd.DataFrames","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Final-thoughts","page":"First steps","title":"Final thoughts","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/user_guide/sectors/heat/#Heat","page":"Heat","title":"Heat","text":"","category":"section"},{"location":"pages/user_guide/sectors/heat/","page":"Heat","title":"Heat","text":"To be added.","category":"page"},{"location":"pages/manual___reference/core_components/#Model-formulation","page":"Model formulation","title":"Model formulation","text":"","category":"section"},{"location":"pages/manual___reference/core_components/","page":"Model formulation","title":"Model formulation","text":"This page collects all information about the core components of IESopt, their properties (and default values), their internal mathematical formulations, and some examples of how to use them. See the API for a complete list of all available functions and types.","category":"page"},{"location":"pages/manual___reference/core_components/","page":"Model formulation","title":"Model formulation","text":"CurrentModule = IESopt\nCollapsedDocStrings = true","category":"page"},{"location":"pages/manual___reference/core_components/","page":"Model formulation","title":"Model formulation","text":"Connection\nDecision\nNode\nProfile\nUnit","category":"page"},{"location":"pages/manual___reference/core_components/#IESopt.Connection","page":"Model formulation","title":"IESopt.Connection","text":"A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, ...\n\nParameters\n\nName Mandatory Values Unit Default Description\nnode_from yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\nnode_to yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\ncarrier no string - - 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.\ncapacity no numeric, col@file, decision:value power +infty 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.\nlb no numeric, col@file, decision:value power -infty Lower bound of this Connection's flow.\nub no numeric, col@file, decision:value power +infty Upper bound of this Connection's flow.\ncost no numeric monetary (per energy) - 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.\nloss no in 0 1 - 0 Fractional loss when transfering energy. 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: flow\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_connection\").var.flow# Python\nmodel.get_component(\"your_connection\").var.flowYou can find the full implementation and all details here: 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 inbeginaligned\n textconnectionnode_fromtextinjection_t = textconnectionnode_fromtextinjection_t - textflow_t qquad forall t in T \n textconnectionnode_totextinjection_t = textconnectionnode_totextinjection_t + textflow_t qquad forall t in T\nendalignedmathFor \"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].\n\nExpressions\n\ndetails: pf_flow\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_connection\").exp.pf_flow# Python\nmodel.get_component(\"your_connection\").exp.pf_flowYou can find the full implementation and all details here: 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, ...).\n\nConstraints\n\ndetails: flow_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_connection\").con.flow_bounds# Python\nmodel.get_component(\"your_connection\").con.flow_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the flow (related to connection) to the model.Specifiying capacity will lead to symmetric bounds (textlb = -capacity and textub = capacity), while asymmetric bounds can be set by explicitly specifiying lb and ub.note: Note\nUsage of etdf is currently not fully tested, and not documented.Upper and lower bounds can be \"infinite\" (by not setting them) resulting in the repective 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,\nif connection.exp.pf_flow is available, it equals this\nelse it equal connection.var.flowThis flow is then constrained:beginaligned\n textflow_t geq textlb qquad forall t in T \n textflow_t leq textub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_connection\").obj.cost# Python\nmodel.get_component(\"your_connection\").obj.costYou can find the full implementation and all details here: 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 textflow_t cdot textcost_t cdot omega_tmathHere omega_t is the weight of Snapshot t.note: Costs for flows in both directions\nIf 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.\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Decision","page":"Model formulation","title":"IESopt.Decision","text":"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.\n\nParameters\n\nName Mandatory Values Unit Default Description\nlb no numeric - 0 Minimum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\nub no numeric - +infty Maximum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\ncost no numeric monetary (per value) 0 Cost that the decision value induces, given as cost cdot value.\nfixed_value no numeric - - 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.\nfixed_cost no - monetary - 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.\nmode no linear, binary, integer, sos1, sos2, fixed - linear 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.\nsos no list - - TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).\nbuild_priority no numeric - 1000 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: fixed\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.fixed# Python\nmodel.get_component(\"your_decision\").var.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.sos# Python\nmodel.get_component(\"your_decision\").var.sosYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.value# Python\nmodel.get_component(\"your_decision\").var.valueYou can find the full implementation and all details here: 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.\n\nExpressions\n\nConstraints\n\ndetails: fixed\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.fixed# Python\nmodel.get_component(\"your_decision\").con.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos1\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos1# Python\nmodel.get_component(\"your_decision\").con.sos1You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos2\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos2# Python\nmodel.get_component(\"your_decision\").con.sos2You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos_value\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos_value# Python\nmodel.get_component(\"your_decision\").con.sos_valueYou can find the full implementation and all details here: IESopt.jl.to be added\n\nObjectives\n\ndetails: fixed\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.fixed# Python\nmodel.get_component(\"your_decision\").obj.fixedYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: sos\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.sos# Python\nmodel.get_component(\"your_decision\").obj.sosYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the SOS-based value of this Decision to the model.\n\ndetails: value\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.value# Python\nmodel.get_component(\"your_decision\").obj.valueYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the value of this Decision to the model:textvalue cdot textcostmath\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Node","page":"Model formulation","title":"IESopt.Node","text":"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, ...). \n\ndetails: Basic Examples\nA Node that represents an electrical bus:bus:\n type: Node\n carrier: electricityA Node that represents a simplified hydrogen storage:store:\n type: Node\n carrier: hydrogen\n has_state: true\n state_lb: 0\n state_ub: 50\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Node. All connecting components need to respect that.\nhas_state no true, false - false 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.\nstate_lb no numeric, col@file, decision:value energy -infty Lower bound of the internal state, requires has_state = true.\nstate_ub no numeric, col@file, decision:value energy +infty Upper bound of the internal state, requires has_state = true.\nstate_cyclic no eq, geq, or disabled - eq 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\").\nstate_initial no numeric energy - Sets the initial state. Must be used in combination with state_cyclic = disabled.\nstate_final no numeric energy - Sets the final state. Must be used in combination with state_cyclic = disabled.\nstate_percentage_loss no in 0 1 - 0 Per Snapshot percentage loss of state (loosing 1% should be set as 0.01).\nnodal_balance no enforce, destroy, or create - enforce 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.\nsum_window_size no integer - - TODO.\nsum_window_step no integer - 1 TODO.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: pf_theta\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.pf_theta# Python\nmodel.get_component(\"your_node\").var.pf_thetaYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: state\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.state# Python\nmodel.get_component(\"your_node\").var.stateYou can find the full implementation and all details here: 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:textstate_1 = textstate_initialmath\n\nExpressions\n\ndetails: injection\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_node\").exp.injection# Python\nmodel.get_component(\"your_node\").exp.injectionYou can find the full implementation and all details here: 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 textinjection_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\".\n\nConstraints\n\ndetails: last_state\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.last_state# Python\nmodel.get_component(\"your_node\").con.last_stateYou can find the full implementation and all details here: 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.beginaligned\n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t geq textstate_lb \n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t leq textstate_ub \nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise.note: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\ndetails: nodalbalance\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.nodalbalance# Python\nmodel.get_component(\"your_node\").con.nodalbalanceYou can find the full implementation and all details here: 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 == truebeginaligned\n textstate_t = textstate_t-1 cdot textfactor^omega_t-1 + textinjection_t-1 cdot omega_t-1 qquad forall t in T setminus 1 \n \n textstate_1 = textstate_end cdot textfactor^omega_end + textinjection_end cdot omega_end\nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise. textinjection_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 == falsebeginaligned\n textinjection_t = 0 qquad forall t in T \nendalignedmathThis 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. \n\ndetails: state_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.state_bounds# Python\nmodel.get_component(\"your_node\").con.state_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the node's state to the model, if node.has_state == true.beginaligned\n textstate_t geq textstate_lb qquad forall t in T \n textstate_t leq textstate_ub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Profile","page":"Model formulation","title":"IESopt.Profile","text":"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.\n\ndetails: Basic Examples\nA Profile that depicts a fixed electricity demand:demand_XY:\n type: Profile\n carrier: electricity\n node_from: grid\n value: demand_XY@input_fileA Profile that handles cost of fuel:fuel_gas:\n type: Profile\n carrier: gas\n node_to: country_gas_grid\n mode: create\n cost: 100.0A Profile that handles CO2 emission costs:co2_cost:\n type: Profile\n carrier: co2\n node_from: total_co2\n mode: destroy\n cost: 150.0A Profile that handles selling electricity:sell_electricity:\n type: Profile\n carrier: electricity\n node_from: internal_grid_node\n mode: destroy\n cost: -30.0\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Profile. Must match the Carrier of the Node that this connects to.\nvalue no numeric, col@file power - The concrete value of this Profile - either static or as time series. Only applicable if mode: fixed.\nnode_from no string - - Name of the Node that this Profile draws energy from. Exactly one of node_from and node_to must be set.\nnode_to no string - - Name of the Node that this Profile feeds energy to. Exactly one of node_from and node_to must be set.\nmode no - - fixed 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).\nlb no numeric power -infty The lower bound of the range of this Profile (must be used together with mode: ranged).\nub no numeric power +infty The upper bound of the range of this Profile (must be used together with mode: ranged).\ncost no numeric monetary per energy 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: aux_value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_profile\").var.aux_value# Python\nmodel.get_component(\"your_profile\").var.aux_valueYou can find the full implementation and all details here: 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:fixed: The value is already handled by the constant term of profile.exp.value and NO variable is constructed.\ncreate, 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 detailled 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 Nodes that the profile is connected to, resulting inbeginaligned\n textprofilenode_fromtextinjection_t = textprofilenode_fromtextinjection_t - textvalue_t qquad forall t in T \n textprofilenode_totextinjection_t = textprofilenode_totextinjection_t + textvalue_t qquad forall t in T\nendalignedmath\n\nExpressions\n\ndetails: value\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_profile\").exp.value# Python\nmodel.get_component(\"your_profile\").exp.valueYou can find the full implementation and all details here: IESopt.jl.Cosntruct 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 intialized based on profile.value.\n\nConstraints\n\ndetails: value_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_profile\").con.value_bounds# Python\nmodel.get_component(\"your_profile\").con.value_boundsYou can find the full implementation and all details here: 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 === :destroybeginaligned\n textaux_value_t geq 0 qquad forall t in T\nendalignedmathif profile.mode === :rangedbeginaligned\n textvalue_t geq textlb_t qquad forall t in T \n textvalue_t leq textub_t qquad forall t in T\nendalignedmathHere, lb and ub can be left empty, which drops the respective constraint.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_profile\").obj.cost# Python\nmodel.get_component(\"your_profile\").obj.costYou can find the full implementation and all details here: 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_tin T textvalue_t cdot textprofilecost_t cdot omega_tmathHere omega_t is the weight of Snapshot t, and textvalue_t actually refers to the value of profile.exp.value[t] (and not only on the maybe non-existing variable).\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Unit","page":"Model formulation","title":"IESopt.Unit","text":"A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.\n\ndetails: Basic Examples\nA Unit that represents a basic gas turbine:gas_turbine:\n type: Unit\n inputs: {gas: gas_grid}\n outputs: {electricity: node, co2: total_co2}\n conversion: 1 gas -> 0.4 electricity + 0.2 co2\n capacity: 10 out:electricityA Unit that represents a basic wind turbine:wind_turbine:\n type: Unit\n outputs: {electricity: node}\n conversion: ~ -> 1 electricity\n capacity: 10 out:electricity\n availability_factor: wind_factor@input_data\n marginal_cost: 1.7 per out:electricityA Unit that represents a basic heat pump, utilizing a varying COP:heatpump:\n type: Unit\n inputs: {electricity: grid}\n outputs: {heat: heat_system}\n conversion: 1 electricity -> cop@inputfile heat\n capacity: 10 in:electricity\n\nParameters\n\nName Mandatory Values Unit Default Description\nconversion yes string - - 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).\ncapacity yes value dir:carrier - - 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\").\noutputs yes dict - - Dictionary specifying the output \"ports\" of this Unit. Refer to the basic examples for the general syntax.\ninputs no dict - - 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.\navailability no numeric power +infty 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 50 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.\navailability_factor no in 0 1 - 1 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.\nadapt_min_to_availability no true, false - false 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.\nmarginal_cost no value per dir:carrier monetary per energy 0 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.\nenable_ramp_up no true, false - false Enables calculation of upward ramps. Ramping is based on the carrier specified in capacity.\nenable_ramp_down no true, false - false Enables calculation of downward ramps. Ramping is based on the carrier specified in capacity.\nramp_up_cost no numeric monetary per power 0 Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.\nramp_down_cost no numeric monetary per power 0 Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.\nramp_up_limit no in 0 1 - 1 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.\nramp_down_limit no in 0 1 - 1 Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.\nmin_on_time no numeric hours 0 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.\nmin_off_time no numeric hours 0 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.\non_time_before no numeric hours 0 Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.\noff_time_before no numeric hours 0 Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.\nis_on_before no numeric - 1 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.\nunit_commitment no off, linear, binary, integer - off 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).\nunit_count no numeric - 1 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, ...).\nmin_conversion no in 0 1 - - 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.\nconversion_at_min no string - - 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.\nstartup_cost no numeric monetary per start 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: conversion\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion# Python\nmodel.get_component(\"your_unit\").var.conversionYou can find the full implementation and all details here: 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.info: Info\nThis 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).\n\ndetails: conversion_connect\nNo documentation found.IESopt._unit_var_conversion_connect! is a Function.tip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion_connect# Python\nmodel.get_component(\"your_unit\").var.conversion_connectYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: ison\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ison# Python\nmodel.get_component(\"your_unit\").var.isonYou can find the full implementation and all details here: 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 textison leq textunitcount, with textunitcount 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].\n\ndetails: ramp\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ramp# Python\nmodel.get_component(\"your_unit\").var.rampYou can find the full implementation and all details here: 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!\n\ndetails: startup\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.startup# Python\nmodel.get_component(\"your_unit\").var.startupYou can find the full implementation and all details here: 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 textstartup leq textunitcount, with textunitcount 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.\n\nExpressions\n\nConstraints\n\ndetails: conversion_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.conversion_bounds# Python\nmodel.get_component(\"your_unit\").con.conversion_boundsYou can find the full implementation and all details here: 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)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T \n textconversion_t leq textavailability_t qquad forall t in T\nendalignedmathThis effectively results in textconversion_t leq min(textcapacity_textonline t textavailability_t).if !isnothing(unit.availability_factor)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t cdot textavailability_textfactor t qquad forall t in T\nendalignedmathinfo: Info\nIf 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:beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T\nendalignedmath\n\ndetails: ison\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ison# Python\nmodel.get_component(\"your_unit\").con.isonYou can find the full implementation and all details here: IESopt.jl.Construct the upper bound for var_ison, based on unit.unit_count, if it is handled by an external Decision.\n\ndetails: min_onoff_time\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.min_onoff_time# Python\nmodel.get_component(\"your_unit\").con.min_onoff_timeYou can find the full implementation and all details here: IESopt.jl.Add the constraints modeling min on- or off-time of a Unit to the model.This constructs the constraintsbeginalign\n sum_t = t^t + textmin_on_time ison_t = textmin_on_time cdot (ison_t - ison_t-1) qquad forall t in T \n sum_t = t^t + textmin_off_time (1 - ison_t) = textmin_off_time cdot (ison_t-1 - ison_t) qquad forall t in T\nendalign\n\nrespecting on_time_before and off_time_before and is_on_before See the code for more details\n\n info Aggregated units\n This is currently not fully adapted to account for Units with unit_count 1math\n\ndetails: ramp\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp# Python\nmodel.get_component(\"your_unit\").con.rampYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t geq textconversion_t - textconversion_t-1 qquad forall t in T \n textramp_textdown t geq textconversion_t-1 - textconversion_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0info: Info\nThis 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.\n\ndetails: ramp_limit\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp_limit# Python\nmodel.get_component(\"your_unit\").con.ramp_limitYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t leq textramplimit_textup cdot textcapacity_textmax cdot omega_t qquad forall t in T \n textramp_textdown t leq textramplimit_textdown cdot textcapacity_textmax cdot omega_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0\n\ndetails: startup\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.startup# Python\nmodel.get_component(\"your_unit\").con.startupYou can find the full implementation and all details here: 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:beginaligned\n textstartup_textup t geq textison_t - textison_t-1 qquad forall t in T\nendalignedmathThis 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\n\nObjectives\n\ndetails: marginal_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.marginal_cost# Python\nmodel.get_component(\"your_unit\").obj.marginal_costYou can find the full implementation and all details here: IESopt.jl.Add the (potential) cost of this unit's conversion (unit.marginal_cost) to the global objective function.sum_t in T textconversion_t cdot textmarginalcost_t cdot omega_tmath\n\ndetails: ramp_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.ramp_cost# Python\nmodel.get_component(\"your_unit\").obj.ramp_costYou can find the full implementation and all details here: 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 textramp_textup t cdot textrampcost_textup + textramp_textdown t cdot textrampcost_textdownmath\n\ndetails: startup_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.startup_cost# Python\nmodel.get_component(\"your_unit\").obj.startup_costYou can find the full implementation and all details here: 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 textstartup_t cdot textstartupcostmath\n\n\n\n\n\n","category":"type"},{"location":"#IESopt.jl","page":"Home","title":"IESopt.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Integrated Energy System Optimization framework written in Julia.","category":"page"},{"location":"#Introduction","page":"Home","title":"Introduction","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"IESopt.jl is the core of the IESopt framework, developed at AIT Austrian Institute of Technology GmbH. It is written in Julia, and uses JuMP package to construct the underlying mathematical optimization models, and to interface with various solvers. The model is designed to be modular, and allows for easy extension and customization.","category":"page"},{"location":"","page":"Home","title":"Home","text":"# TODO: add \"The main functionalities of IESopt.jl are: ...\" itemize here.\n# TODO: add gitter here\n# TODO: cleanup the \"About\" section, and integrate it here","category":"page"},{"location":"","page":"Home","title":"Home","text":"Check out the following GitHub repositories for more information:","category":"page"},{"location":"","page":"Home","title":"Home","text":"IESopt.jl, the core model (a Julia package).\niesopt-py, the Python interface.","category":"page"},{"location":"","page":"Home","title":"Home","text":"danger: Moving to open-source\nWe are currently working (hard) on getting IESopt fully open-source on GitHub, which requires some clean-up of (potentially) confindential left-overs (e.g., from projects). Meanwhile, a lot of internals are changing (after staying fixed for a long time), and the documentation needs to be checked page-by-page. If you are trying to get started before we manage to fix everything, get in touch with us directly - we'll help you set up everything you need. The documentation currently consists of mostly structured pages, with the content being added as soon as possible.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"details: Using Python\nTo be added.","category":"page"},{"location":"","page":"Home","title":"Home","text":"details: Using Julia\nTo be added.","category":"page"},{"location":"#Citation","page":"Home","title":"Citation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"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:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Style: APA7\nStrö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\nStyle: IEEE\n[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\nBibTeX:\n@misc{iesopt,\n author = {Strömer, Stefan and Schwabeneder, Daniel and contributors},\n title = {{IES}opt: Integrated Energy System Optimization},\n organization = {AIT Austrian Institute of Technology GmbH},\n url = {https://github.com/ait-energy/IESopt},\n type = {Software},\n year = {2021-2024},\n}","category":"page"},{"location":"","page":"Home","title":"Home","text":"# ## About\n\n# ### Overview\n\n# IESopt, _Integrated Energy System Optimization_, is a general purpose energy system optimization framework, developed at the [Center for Energy](https://www.ait.ac.at/en/about-the-ait/center/center-for-energy), at [AIT Austrian Institute of Technology GmbH](https://www.ait.ac.at/), mainly developed and maintained by the unit [Integrated Energy Systems](https://www.ait.ac.at/en/research-topics/integrated-energy-systems).\n\n# ### Feature summary\n\n# What IESopt is, may be, and is not:\n\n# - YES\n# - IESopt is a general purpose energy system (optimization) model. It supports multiple solvers (using `JuMP.jl` to interface with them) as well as a standardized way to build up models using \"core components\".\n# - MAYBE\n# - IESopt.jl is not branded as JuMP extension. It plays nicely with JuMP, and some extensions, but we currently do not see it as a fully fledged JuMP extension. That, e.g., entails that `copy_extension_data` is not implemented at the moment, so `copy_model` is not supported. This is a deliberate choice, and may be changed in the - near or far - future.\n# - NO (really ...)\n# - A full energy system model, in the sense of \"containing data\". There are a lot of good, and open, data sources out there from other teams, consider using them.","category":"page"}] +[{"location":"pages/tutorials/next_steps/#Next-steps","page":"Next steps","title":"Next steps","text":"","category":"section"},{"location":"pages/tutorials/next_steps/","page":"Next steps","title":"Next steps","text":"To be added.","category":"page"},{"location":"pages/user_guide/solvers/#Solvers","page":"Solvers","title":"Solvers","text":"","category":"section"},{"location":"pages/user_guide/solvers/#Recommended-Configurations","page":"Solvers","title":"Recommended Configurations","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"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).","category":"page"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"More informations can be found at:","category":"page"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"HiGHS:\nhttps://ergo-code.github.io/HiGHS/stable/options/definitions/\nGurobi:\nhttps://www.gurobi.com/wp-content/uploads/2022-10-ParisAdvancedAlgorithms.pdf\nhttps://www.gurobi.com/documentation/current/refman/parameters.html\nCPLEX:\nhttps://www.ibm.com/docs/en/icos/22.1.1?topic=cplex-list-parameters","category":"page"},{"location":"pages/user_guide/solvers/#HiGHS","page":"Solvers","title":"HiGHS","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: highs\n attributes:\n threads: 4\n solver: \"ipm\"\n run_crossover: \"off\"\n small_matrix_value: 1e-6\n large_matrix_value: 1e9\n primal_feasibility_tolerance: 1e-5\n dual_feasibility_tolerance: 1e-5\n ipm_optimality_tolerance: 1e-4\n parallel: \"on\"\n random_seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#Gurobi","page":"Solvers","title":"Gurobi","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: gurobi\n attributes:\n Method: 2\n Crossover: 0\n BarConvTol: 1.e-6\n Seed: 123\n AggFill: 0\n PreDual: 0\n GURO_PAR_BARDENSETHRESH: 200\n Threads: 8\n Seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#Gurobi-(NumFocus)","page":"Solvers","title":"Gurobi (NumFocus)","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"For models with \"challenging\" numerical properties, the following can be useful:","category":"page"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: gurobi\n attributes:\n NumericFocus: 3\n Method: 2\n Crossover: 0\n BarHomogeneous: 1\n BarConvTol: 1.e-5\n FeasibilityTol: 1.e-4\n OptimalityTol: 1.e-4\n ObjScale: -0.5\n Threads: 8\n Seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#Gurobi-(fallback)","page":"Solvers","title":"Gurobi (fallback)","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: gurobi\n attributes:\n Crossover: 0\n Method: 2\n BarHomogeneous: 1\n BarConvTol: 1.e-5\n FeasibilityTol: 1.e-5\n OptimalityTol: 1.e-5\n Threads: 8\n Seed: 1234","category":"page"},{"location":"pages/user_guide/solvers/#CPLEX","page":"Solvers","title":"CPLEX","text":"","category":"section"},{"location":"pages/user_guide/solvers/","page":"Solvers","title":"Solvers","text":"solver:\n name: cplex\n attributes:\n threads: 4\n lpmethod: 4\n solutiontype: 2\n barrier_convergetol: 1.e-5\n feasopt_tolerance: 1.e-6","category":"page"},{"location":"pages/manual___reference/api/#API","page":"API","title":"API","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"This contains the following raw documentation entries:","category":"page"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"Pages = [\"api.md\"]\nDepth = 2:4","category":"page"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"","category":"page"},{"location":"pages/manual___reference/api/#Julia","page":"API","title":"Julia","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"CurrentModule = IESopt\nCollapsedDocStrings = true","category":"page"},{"location":"pages/manual___reference/api/#Modules","page":"API","title":"Modules","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"IESopt","category":"page"},{"location":"pages/manual___reference/api/#IESopt.IESopt","page":"API","title":"IESopt.IESopt","text":"IESopt\n\nA general purpose solver agnostic energy system optimization framework.\n\n\n\n\n\n","category":"module"},{"location":"pages/manual___reference/api/#Types","page":"API","title":"Types","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"Carrier\nSnapshot","category":"page"},{"location":"pages/manual___reference/api/#IESopt.Carrier","page":"API","title":"IESopt.Carrier","text":"struct Carrier\n name::String\n unit::Union{String, Nothing}\nend\n\nRepresents a single (energy) carrier with a given name.\n\nThis 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.\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Snapshot","page":"API","title":"IESopt.Snapshot","text":"struct Snapshot\n name\n id\n weight\nend\n\nRepresent a specific timestamp, that can be tied to timeseries values.\n\nEach 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/...).\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"Connection\nDecision\nNode\nProfile\nUnit","category":"page"},{"location":"pages/manual___reference/api/#IESopt.Connection-pages-manual___reference-api","page":"API","title":"IESopt.Connection","text":"A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, ...\n\nParameters\n\nName Mandatory Values Unit Default Description\nnode_from yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\nnode_to yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\ncarrier no string - - 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.\ncapacity no numeric, col@file, decision:value power +infty 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.\nlb no numeric, col@file, decision:value power -infty Lower bound of this Connection's flow.\nub no numeric, col@file, decision:value power +infty Upper bound of this Connection's flow.\ncost no numeric monetary (per energy) - 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.\nloss no in 0 1 - 0 Fractional loss when transfering energy. 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: flow\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_connection\").var.flow# Python\nmodel.get_component(\"your_connection\").var.flowYou can find the full implementation and all details here: 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 inbeginaligned\n textconnectionnode_fromtextinjection_t = textconnectionnode_fromtextinjection_t - textflow_t qquad forall t in T \n textconnectionnode_totextinjection_t = textconnectionnode_totextinjection_t + textflow_t qquad forall t in T\nendalignedmathFor \"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].\n\nExpressions\n\ndetails: pf_flow\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_connection\").exp.pf_flow# Python\nmodel.get_component(\"your_connection\").exp.pf_flowYou can find the full implementation and all details here: 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, ...).\n\nConstraints\n\ndetails: flow_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_connection\").con.flow_bounds# Python\nmodel.get_component(\"your_connection\").con.flow_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the flow (related to connection) to the model.Specifiying capacity will lead to symmetric bounds (textlb = -capacity and textub = capacity), while asymmetric bounds can be set by explicitly specifiying lb and ub.note: Note\nUsage of etdf is currently not fully tested, and not documented.Upper and lower bounds can be \"infinite\" (by not setting them) resulting in the repective 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,\nif connection.exp.pf_flow is available, it equals this\nelse it equal connection.var.flowThis flow is then constrained:beginaligned\n textflow_t geq textlb qquad forall t in T \n textflow_t leq textub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_connection\").obj.cost# Python\nmodel.get_component(\"your_connection\").obj.costYou can find the full implementation and all details here: 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 textflow_t cdot textcost_t cdot omega_tmathHere omega_t is the weight of Snapshot t.note: Costs for flows in both directions\nIf 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.\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Decision-pages-manual___reference-api","page":"API","title":"IESopt.Decision","text":"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.\n\nParameters\n\nName Mandatory Values Unit Default Description\nlb no numeric - 0 Minimum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\nub no numeric - +infty Maximum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\ncost no numeric monetary (per value) 0 Cost that the decision value induces, given as cost cdot value.\nfixed_value no numeric - - 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.\nfixed_cost no - monetary - 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.\nmode no linear, binary, integer, sos1, sos2, fixed - linear 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.\nsos no list - - TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).\nbuild_priority no numeric - 1000 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: fixed\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.fixed# Python\nmodel.get_component(\"your_decision\").var.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.sos# Python\nmodel.get_component(\"your_decision\").var.sosYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.value# Python\nmodel.get_component(\"your_decision\").var.valueYou can find the full implementation and all details here: 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.\n\nExpressions\n\nConstraints\n\ndetails: fixed\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.fixed# Python\nmodel.get_component(\"your_decision\").con.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos1\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos1# Python\nmodel.get_component(\"your_decision\").con.sos1You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos2\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos2# Python\nmodel.get_component(\"your_decision\").con.sos2You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos_value\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos_value# Python\nmodel.get_component(\"your_decision\").con.sos_valueYou can find the full implementation and all details here: IESopt.jl.to be added\n\nObjectives\n\ndetails: fixed\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.fixed# Python\nmodel.get_component(\"your_decision\").obj.fixedYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: sos\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.sos# Python\nmodel.get_component(\"your_decision\").obj.sosYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the SOS-based value of this Decision to the model.\n\ndetails: value\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.value# Python\nmodel.get_component(\"your_decision\").obj.valueYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the value of this Decision to the model:textvalue cdot textcostmath\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Node-pages-manual___reference-api","page":"API","title":"IESopt.Node","text":"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, ...). \n\ndetails: Basic Examples\nA Node that represents an electrical bus:bus:\n type: Node\n carrier: electricityA Node that represents a simplified hydrogen storage:store:\n type: Node\n carrier: hydrogen\n has_state: true\n state_lb: 0\n state_ub: 50\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Node. All connecting components need to respect that.\nhas_state no true, false - false 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.\nstate_lb no numeric, col@file, decision:value energy -infty Lower bound of the internal state, requires has_state = true.\nstate_ub no numeric, col@file, decision:value energy +infty Upper bound of the internal state, requires has_state = true.\nstate_cyclic no eq, geq, or disabled - eq 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\").\nstate_initial no numeric energy - Sets the initial state. Must be used in combination with state_cyclic = disabled.\nstate_final no numeric energy - Sets the final state. Must be used in combination with state_cyclic = disabled.\nstate_percentage_loss no in 0 1 - 0 Per Snapshot percentage loss of state (loosing 1% should be set as 0.01).\nnodal_balance no enforce, destroy, or create - enforce 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.\nsum_window_size no integer - - TODO.\nsum_window_step no integer - 1 TODO.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: pf_theta\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.pf_theta# Python\nmodel.get_component(\"your_node\").var.pf_thetaYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: state\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.state# Python\nmodel.get_component(\"your_node\").var.stateYou can find the full implementation and all details here: 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:textstate_1 = textstate_initialmath\n\nExpressions\n\ndetails: injection\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_node\").exp.injection# Python\nmodel.get_component(\"your_node\").exp.injectionYou can find the full implementation and all details here: 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 textinjection_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\".\n\nConstraints\n\ndetails: last_state\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.last_state# Python\nmodel.get_component(\"your_node\").con.last_stateYou can find the full implementation and all details here: 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.beginaligned\n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t geq textstate_lb \n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t leq textstate_ub \nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise.note: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\ndetails: nodalbalance\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.nodalbalance# Python\nmodel.get_component(\"your_node\").con.nodalbalanceYou can find the full implementation and all details here: 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 == truebeginaligned\n textstate_t = textstate_t-1 cdot textfactor^omega_t-1 + textinjection_t-1 cdot omega_t-1 qquad forall t in T setminus 1 \n \n textstate_1 = textstate_end cdot textfactor^omega_end + textinjection_end cdot omega_end\nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise. textinjection_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 == falsebeginaligned\n textinjection_t = 0 qquad forall t in T \nendalignedmathThis 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. \n\ndetails: state_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.state_bounds# Python\nmodel.get_component(\"your_node\").con.state_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the node's state to the model, if node.has_state == true.beginaligned\n textstate_t geq textstate_lb qquad forall t in T \n textstate_t leq textstate_ub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Profile-pages-manual___reference-api","page":"API","title":"IESopt.Profile","text":"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.\n\ndetails: Basic Examples\nA Profile that depicts a fixed electricity demand:demand_XY:\n type: Profile\n carrier: electricity\n node_from: grid\n value: demand_XY@input_fileA Profile that handles cost of fuel:fuel_gas:\n type: Profile\n carrier: gas\n node_to: country_gas_grid\n mode: create\n cost: 100.0A Profile that handles CO2 emission costs:co2_cost:\n type: Profile\n carrier: co2\n node_from: total_co2\n mode: destroy\n cost: 150.0A Profile that handles selling electricity:sell_electricity:\n type: Profile\n carrier: electricity\n node_from: internal_grid_node\n mode: destroy\n cost: -30.0\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Profile. Must match the Carrier of the Node that this connects to.\nvalue no numeric, col@file power - The concrete value of this Profile - either static or as time series. Only applicable if mode: fixed.\nnode_from no string - - Name of the Node that this Profile draws energy from. Exactly one of node_from and node_to must be set.\nnode_to no string - - Name of the Node that this Profile feeds energy to. Exactly one of node_from and node_to must be set.\nmode no - - fixed 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).\nlb no numeric power -infty The lower bound of the range of this Profile (must be used together with mode: ranged).\nub no numeric power +infty The upper bound of the range of this Profile (must be used together with mode: ranged).\ncost no numeric monetary per energy 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: aux_value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_profile\").var.aux_value# Python\nmodel.get_component(\"your_profile\").var.aux_valueYou can find the full implementation and all details here: 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:fixed: The value is already handled by the constant term of profile.exp.value and NO variable is constructed.\ncreate, 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 detailled 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 Nodes that the profile is connected to, resulting inbeginaligned\n textprofilenode_fromtextinjection_t = textprofilenode_fromtextinjection_t - textvalue_t qquad forall t in T \n textprofilenode_totextinjection_t = textprofilenode_totextinjection_t + textvalue_t qquad forall t in T\nendalignedmath\n\nExpressions\n\ndetails: value\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_profile\").exp.value# Python\nmodel.get_component(\"your_profile\").exp.valueYou can find the full implementation and all details here: IESopt.jl.Cosntruct 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 intialized based on profile.value.\n\nConstraints\n\ndetails: value_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_profile\").con.value_bounds# Python\nmodel.get_component(\"your_profile\").con.value_boundsYou can find the full implementation and all details here: 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 === :destroybeginaligned\n textaux_value_t geq 0 qquad forall t in T\nendalignedmathif profile.mode === :rangedbeginaligned\n textvalue_t geq textlb_t qquad forall t in T \n textvalue_t leq textub_t qquad forall t in T\nendalignedmathHere, lb and ub can be left empty, which drops the respective constraint.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_profile\").obj.cost# Python\nmodel.get_component(\"your_profile\").obj.costYou can find the full implementation and all details here: 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_tin T textvalue_t cdot textprofilecost_t cdot omega_tmathHere omega_t is the weight of Snapshot t, and textvalue_t actually refers to the value of profile.exp.value[t] (and not only on the maybe non-existing variable).\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#IESopt.Unit-pages-manual___reference-api","page":"API","title":"IESopt.Unit","text":"A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.\n\ndetails: Basic Examples\nA Unit that represents a basic gas turbine:gas_turbine:\n type: Unit\n inputs: {gas: gas_grid}\n outputs: {electricity: node, co2: total_co2}\n conversion: 1 gas -> 0.4 electricity + 0.2 co2\n capacity: 10 out:electricityA Unit that represents a basic wind turbine:wind_turbine:\n type: Unit\n outputs: {electricity: node}\n conversion: ~ -> 1 electricity\n capacity: 10 out:electricity\n availability_factor: wind_factor@input_data\n marginal_cost: 1.7 per out:electricityA Unit that represents a basic heat pump, utilizing a varying COP:heatpump:\n type: Unit\n inputs: {electricity: grid}\n outputs: {heat: heat_system}\n conversion: 1 electricity -> cop@inputfile heat\n capacity: 10 in:electricity\n\nParameters\n\nName Mandatory Values Unit Default Description\nconversion yes string - - 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).\ncapacity yes value dir:carrier - - 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\").\noutputs yes dict - - Dictionary specifying the output \"ports\" of this Unit. Refer to the basic examples for the general syntax.\ninputs no dict - - 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.\navailability no numeric power +infty 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.\navailability_factor no in 0 1 - 1 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.\nadapt_min_to_availability no true, false - false 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.\nmarginal_cost no value per dir:carrier monetary per energy 0 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.\nenable_ramp_up no true, false - false Enables calculation of upward ramps. Ramping is based on the carrier specified in capacity.\nenable_ramp_down no true, false - false Enables calculation of downward ramps. Ramping is based on the carrier specified in capacity.\nramp_up_cost no numeric monetary per power 0 Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.\nramp_down_cost no numeric monetary per power 0 Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.\nramp_up_limit no in 0 1 - 1 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.\nramp_down_limit no in 0 1 - 1 Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.\nmin_on_time no numeric hours 0 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.\nmin_off_time no numeric hours 0 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.\non_time_before no numeric hours 0 Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.\noff_time_before no numeric hours 0 Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.\nis_on_before no numeric - 1 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.\nunit_commitment no off, linear, binary, integer - off 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).\nunit_count no numeric - 1 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, ...).\nmin_conversion no in 0 1 - - 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.\nconversion_at_min no string - - 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.\nstartup_cost no numeric monetary per start 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: conversion\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion# Python\nmodel.get_component(\"your_unit\").var.conversionYou can find the full implementation and all details here: 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.info: Info\nThis 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).\n\ndetails: conversion_connect\nNo documentation found.IESopt._unit_var_conversion_connect! is a Function.tip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion_connect# Python\nmodel.get_component(\"your_unit\").var.conversion_connectYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: ison\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ison# Python\nmodel.get_component(\"your_unit\").var.isonYou can find the full implementation and all details here: 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 textison leq textunitcount, with textunitcount 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].\n\ndetails: ramp\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ramp# Python\nmodel.get_component(\"your_unit\").var.rampYou can find the full implementation and all details here: 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!\n\ndetails: startup\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.startup# Python\nmodel.get_component(\"your_unit\").var.startupYou can find the full implementation and all details here: 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 textstartup leq textunitcount, with textunitcount 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.\n\nExpressions\n\nConstraints\n\ndetails: conversion_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.conversion_bounds# Python\nmodel.get_component(\"your_unit\").con.conversion_boundsYou can find the full implementation and all details here: 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)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T \n textconversion_t leq textavailability_t qquad forall t in T\nendalignedmathThis effectively results in textconversion_t leq min(textcapacity_textonline t textavailability_t).if !isnothing(unit.availability_factor)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t cdot textavailability_textfactor t qquad forall t in T\nendalignedmathinfo: Info\nIf 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:beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T\nendalignedmath\n\ndetails: ison\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ison# Python\nmodel.get_component(\"your_unit\").con.isonYou can find the full implementation and all details here: IESopt.jl.Construct the upper bound for var_ison, based on unit.unit_count, if it is handled by an external Decision.\n\ndetails: min_onoff_time\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.min_onoff_time# Python\nmodel.get_component(\"your_unit\").con.min_onoff_timeYou can find the full implementation and all details here: IESopt.jl.Add the constraints modeling min on- or off-time of a Unit to the model.This constructs the constraintsbeginalign\n sum_t = t^t + textmin_on_time ison_t = textmin_on_time cdot (ison_t - ison_t-1) qquad forall t in T \n sum_t = t^t + textmin_off_time (1 - ison_t) = textmin_off_time cdot (ison_t-1 - ison_t) qquad forall t in T\nendalign\n\nrespecting on_time_before and off_time_before and is_on_before See the code for more details\n\n info Aggregated units\n This is currently not fully adapted to account for Units with unit_count 1math\n\ndetails: ramp\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp# Python\nmodel.get_component(\"your_unit\").con.rampYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t geq textconversion_t - textconversion_t-1 qquad forall t in T \n textramp_textdown t geq textconversion_t-1 - textconversion_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0info: Info\nThis 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.\n\ndetails: ramp_limit\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp_limit# Python\nmodel.get_component(\"your_unit\").con.ramp_limitYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t leq textramplimit_textup cdot textcapacity_textmax cdot omega_t qquad forall t in T \n textramp_textdown t leq textramplimit_textdown cdot textcapacity_textmax cdot omega_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0\n\ndetails: startup\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.startup# Python\nmodel.get_component(\"your_unit\").con.startupYou can find the full implementation and all details here: 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:beginaligned\n textstartup_textup t geq textison_t - textison_t-1 qquad forall t in T\nendalignedmathThis 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\n\nObjectives\n\ndetails: marginal_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.marginal_cost# Python\nmodel.get_component(\"your_unit\").obj.marginal_costYou can find the full implementation and all details here: IESopt.jl.Add the (potential) cost of this unit's conversion (unit.marginal_cost) to the global objective function.sum_t in T textconversion_t cdot textmarginalcost_t cdot omega_tmath\n\ndetails: ramp_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.ramp_cost# Python\nmodel.get_component(\"your_unit\").obj.ramp_costYou can find the full implementation and all details here: 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 textramp_textup t cdot textrampcost_textup + textramp_textdown t cdot textrampcost_textdownmath\n\ndetails: startup_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.startup_cost# Python\nmodel.get_component(\"your_unit\").obj.startup_costYou can find the full implementation and all details here: 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 textstartup_t cdot textstartupcostmath\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/api/#Functions","page":"API","title":"Functions","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"run\ngenerate!\noptimize!","category":"page"},{"location":"pages/manual___reference/api/#IESopt.run","page":"API","title":"IESopt.run","text":"run(filename::String; verbosity=nothing, kwargs...)\n\nBuild, optimize, and return a model.\n\nArguments\n\nfilename::String: The path to the top-level configuration file.\nverbosity: The verbosity level to use. Supports true (= verbose mode), \"warning\" (= warnings and above), and false (suppressing logs).\n\nIf verbosity = true, the verbosity setting of the solver defaults to true as well, otherwise it defaults to false (the verbosity setting of the solver can also be directly controled using the verbosity_solve setting in the top-level config file).\n\nKeyword Arguments\n\nKeyword arguments are passed to the generate! function.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.generate!","page":"API","title":"IESopt.generate!","text":"generate!(filename::String)\n\nBuilds and returns a model using the IESopt framework.\n\nThis loads the configuration file specified by filename. Requires full specification of the solver entry in config.\n\n\n\n\n\ngenerate!(model::JuMP.Model, filename::String)\n\nBuilds a model using the IESopt framework, \"into\" the provided model.\n\nThis loads the configuration file specified by filename. Be careful when creating your model in any other way than in the provided examples, as this can conflict with IESopt internals (especially for model/optimizer combinations that do not support bridges). Returns the model for convenience, even though it is modified in place.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.optimize!","page":"API","title":"IESopt.optimize!","text":"optimize!(model::JuMP.Model; save_results::Bool=true, kwargs...)\n\nUse JuMP.optimize! to optimize the given model, optionally serializing the model afterwards for later use.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"component","category":"page"},{"location":"pages/manual___reference/api/#IESopt.component","page":"API","title":"IESopt.component","text":"function component(model::JuMP.Model, component_name::String)\n\nGet the component component_name from model.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"compute_IIS","category":"page"},{"location":"pages/manual___reference/api/#IESopt.compute_IIS","page":"API","title":"IESopt.compute_IIS","text":"function compute_IIS(model::JuMP.Model; filename::String = \"\")\n\nCompute 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.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"overview\npack\nunpack","category":"page"},{"location":"pages/manual___reference/api/#IESopt.overview","page":"API","title":"IESopt.overview","text":"overview(file::String)\n\nExtracts the most important information from an IESopt model file, and returns it as a dictionary.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.pack","page":"API","title":"IESopt.pack","text":"pack(file::String; out::String=\"\", method=:store)\n\nPacks the IESopt model specified by the top-level config file file into single file.\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#IESopt.unpack","page":"API","title":"IESopt.unpack","text":"unpack(file::String; out::String=\"\", force_overwrite::Bool=false)\n\nUnpacks the IESopt model specified by file.\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"pages/manual___reference/api/#Python","page":"API","title":"Python","text":"","category":"section"},{"location":"pages/manual___reference/api/","page":"API","title":"API","text":"To be added.","category":"page"},{"location":"pages/dev_docs/#Developer-Documentation","page":"Developer Documentation","title":"Developer Documentation","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"The following sections provide rough guidelines on how to work with IESopt, and mostly IESopt.jl, improving the documentation, testing, and implementing new features.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"info: Helping out\nLooking for things to contribute, with a low entry barrier (besides any open issue)? Check for To be added (especially in the documentation), or TODO (especially in the code).","category":"page"},{"location":"pages/dev_docs/#Getting-started","page":"Developer Documentation","title":"Getting started","text":"","category":"section"},{"location":"pages/dev_docs/#General","page":"Developer Documentation","title":"General","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Install Julia.\nInstall VSCode, and some extensions (this step is optional, but highly recommended).\nClone/fork the repository.\nHappy coding (... see below)!","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"If you are new to Julia, or not entirely sure how everything works - let's talk. We are happy to help you get started, and to guide you through the process. Stuff like Revise.jl can be a huge help, and we can show you how to use it. Further, if you are coming from, e.g., a standard Python background, the advantages of a dynamic REPL-driven development may be new to you.","category":"page"},{"location":"pages/dev_docs/#Tips-and-tricks","page":"Developer Documentation","title":"Tips and tricks","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Check out Modern Julia Workflows.\nCheck out the Julia Discourse.\nRead up details on Revise usage.","category":"page"},{"location":"pages/dev_docs/#Architecture","page":"Developer Documentation","title":"Architecture","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"See ARCHITECTURE.md for more information.","category":"page"},{"location":"pages/dev_docs/#Coding-conventions","page":"Developer Documentation","title":"Coding conventions","text":"","category":"section"},{"location":"pages/dev_docs/#Branches","page":"Developer Documentation","title":"Branches","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"We mainly use a \"feature branch workflow\", similar to \"trunk based development\". We strive to keep the main branch as clean as possible (docs and tests should build and pass), and work on a separate development (trunk) branch. For larger changes, consider starting new feature branches. Where possible we use PRs (or merge requests) to get changes into the main branch, while doing a (light) code review for each other.","category":"page"},{"location":"pages/dev_docs/#Naming-conventions","page":"Developer Documentation","title":"Naming conventions","text":"","category":"section"},{"location":"pages/dev_docs/#Julia","page":"Developer Documentation","title":"Julia","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"We make use of the following naming conventions, which slightly differ from the Julia naming conventions, but are similar to other large projects out there:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Types and similar items use CamelCase, e.g., MyType.\nFunctions and variables use snake_case, e.g., my_function(...).\nFunctions that modify their arguments should end with an exclamation mark, e.g., optimize!(...).\nFunctions and variables should actually make use of underscores, whenever reasonable (and not only when absolutely necessary), e.g., set_to_zero!(...) (not settozero!(...) like the Julia naming conventions may suggest).\nConstants are written in UPPERCASE, e.g., MY_CONSTANT.","category":"page"},{"location":"pages/dev_docs/#Python","page":"Developer Documentation","title":"Python","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"To be added (black with --line-length 88, ruff, standard naming conventions, ...).","category":"page"},{"location":"pages/dev_docs/#Conventional-commits","page":"Developer Documentation","title":"Conventional commits","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Refer to the Conventional Commits specification for a detailed explanation. In short, we use the following format:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"feat: implemented new feature X\nfix: fixed the bug X\nrefactor: refactored the code X\ndocs: updated the documentation X\ntest: added a new test for X\nchore: updated the dependencies X","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"As indicated we use docs, but test (and not tests), which can be remembered by looking at the folder names: docs/ and test/.","category":"page"},{"location":"pages/dev_docs/#Version-numbers","page":"Developer Documentation","title":"Version numbers","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Documenter.jl (as of May, 22nd, 2024) aggressively states: \"Documenter, like any good Julia package, follows semantic versioning (SemVer).\"","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Unfortunately, semantic versioning may not be as well suited as one might think for a package like IESopt.jl. Some reasons are:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"While the (Julia) API has been stable for a long time (in a sense of: backwards-compatible), we consider the YAML configuration syntax as main part of IESopt's \"API\". This syntax has changed multiple times, and will likely change in the future. Maintaining full backwards compatibility for this is not feasible all the time. This induces a need for a major version bump, even though the Julia API has not changed.\nA mere bug fix, even a small one, in IESopt.jl may very likely induce changed results of any model run. A user could see vastly different results between v1.3.10 and v1.3.11, even though the changes are minimal. This envolves not taking patch updates lightly, which is not the case in many other packages.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"However, as indicated, the use of semantic versioning is still \"expected\" by large parts of the Julia community, and not doing so may make it harder for some users, and/or some interactions with other packages. So...","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"IESopt.jl makes use of semantic versioning!\nYou are advised to consider the above points when deciding on version bumps.\nAdvise users and make sure you properly document changes.\nExpect rising major version numbers.","category":"page"},{"location":"pages/dev_docs/#Working-with-VSCode","page":"Developer Documentation","title":"Working with VSCode","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"The following set of extensions may be helpful, either for development or documentation purposes:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Julia\nLive Preview\nMarkdown Julia\nMarkdown Preview GitHub Styling\nmarkdownlint\nRainbow CSV","category":"page"},{"location":"pages/dev_docs/#Improving-the-documentation","page":"Developer Documentation","title":"Improving the documentation","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"# TODO: refactor to new syntax: `julia --project=. -e 'include(\"docs/liveserver.jl\")'`","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Docstrings of public entries of IESopt.jl are taken from the code, see src/.... Besides that, the documentation is contained in the docs/src/... folder, and built based on docs/make.jl, using Documenter.jl.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"info: Diátaxis\nConsider checking out the excellent \"project\" Diátaxis, by Daniele Procida. We try to adhere to the principles outlined there, and you may find them useful as well. For a quick intro, you may consider starting here: The difference between a tutorial and how-to guide.","category":"page"},{"location":"pages/dev_docs/#Setup","page":"Developer Documentation","title":"Setup","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Make sure that you","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"have a working installation of Julia (otherwise go to julialang.org and install it; we recommend sticking to Juliaup if asked), and\nhave a terminal of your choice launched at IESopt.jl/.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Then, run the following command once to set up the environment used for the documentation:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"julia --project=docs -e 'import Pkg; Pkg.instantiate()'","category":"page"},{"location":"pages/dev_docs/#Building-the-documentation","page":"Developer Documentation","title":"Building the documentation","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Launch an interactive web server that shows you the documentation while you are working on it:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"julia --project=docs -e 'using LiveServer; servedocs(; launch_browser=true)'","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Note: While the above is your best choice in 95% of all cases, you can also manually build the documentation usingjulia --project=docs docs/make.jlwhich may be useful if you modify source files (which LiveServer.jl currently does not track in a convenient way). Note however that this will not automatically reload the documentation in your browser (but may in VSCode if you right-click the index.html file and select Preview, using the Live Preview extension), and may fail to properly account for image/... paths.","category":"page"},{"location":"pages/dev_docs/#Code-formatting","page":"Developer Documentation","title":"Code formatting","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"We provide a custom .JuliaFormatter.toml file that should be used to format the code. The easiest way to use it is to:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Add JuliaFormatter to your Julia base environment by running ] add JuliaFormatter in the package mode of your Julia REPL (without an active IESopt environment).\nRun using JuliaFormatter in the Julia REPL (this now works even if you activated the IESopt environment).\nRun format(\".\") in the Julia REPL to format all files in your current directory. This takes a bit of compile time, but after the first run, it should be fairly fast.","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Make sure you checked the formatting, before finalizing your changes or opening a PR. If you forgot to include formatting in your actual commits (we all do...), and cannot reasonably ammend them, add all formatting changes at the end in a single commit with the message:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"git commit -m \"chore: formatting\"","category":"page"},{"location":"pages/dev_docs/#Testing","page":"Developer Documentation","title":"Testing","text":"","category":"section"},{"location":"pages/dev_docs/#Running-tests-locally","page":"Developer Documentation","title":"Running tests locally","text":"","category":"section"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"Launch a new Julia REPL (hit Alt+J and then Alt+O in VSCode), enter Package mode (by pressing ] in your REPL, now showing (IESopt) pkg>), and then execute all tests by running:","category":"page"},{"location":"pages/dev_docs/","page":"Developer Documentation","title":"Developer Documentation","text":"(IESopt) pkg> test","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Templates:-Part-I","page":"Templates: Part I","title":"Templates: Part I","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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).","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#The-basic-structure","page":"Templates: Part I","title":"The basic structure","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Possiblities for that could be:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"CustomHeatPump, if you do not have any more details\nGroundSourceHeatPump, if we want to implement a ground-source heat pump with different parameters/features than the standard one\nFooHeatPump, if you need it specifically for a project called \"Foo\"","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"info: Naming conventions\nTemplates follow a naming convention similar to PascalCase:The name must start with an upper-case letter\nIt must consist of at least two letters\nNumbers and special characters are not allowed","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"parameters:\n p_nom: null\n electricity_from: null\n heat_from: null\n heat_to: null\n cop: null","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"p_nom: The nominal power of the heat pump, which will be specified on the electricity input side\nelectricity_from: The Node that this heat pump is connected to for electricity input\nheat_from: The Node that this heat pump is connected to for heat input\nheat_to: The Node that this heat pump is connected to for heat output\ncop: The coefficient of performance of the heat pump","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"component:\n type: Unit\n inputs: {electricity: , heat: }\n outputs: {heat: }\n conversion: 1 electricity + ( - 1) heat -> heat\n capacity: in:electricity","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"# other parts of the configuration file\n# ...\n\ncomponents:\n # some other components\n # ...\n\n heat_pump:\n template: CustomHeatPump\n parameters:\n p_nom: 10\n electricity_from: electricity\n heat_from: ambient\n heat_to: heating\n cop: 3","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Accounting-for-different-configurations","page":"Templates: Part I","title":"Accounting for different configurations","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"# # Custom Heat Pump\n\n# A (custom) heat pump that consumes electricity and heat, and produces heat.\n\n# ## Parameters\n# - `p_nom`: The nominal power (electricity) of the heat pump.\n# - `electricity_from`: The `Node` that this heat pump is connected to for electricity input.\n# - `heat_from`: The `Node` that this heat pump is connected to for heat input.\n# - `heat_to`: The `Node` that this heat pump is connected to for heat output.\n# - `cop`: The coefficient of performance of the heat pump.\n\n# ## Components\n# _to be added_\n\n# ## Usage\n# _to be added_\n\n# ## Details\n# _to be added_\n\nparameters:\n # ...","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"info: Docstring format\nAll 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Let's continue with accounting for different configurations. We will cover the following steps:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Making the heat_from parameter optional\nExtending the template to allow for sizing the heat pump (an investment decision)\nHandling more complex COP configurations","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Optional-parameter-and-sizing-decision","page":"Templates: Part I","title":"Optional parameter and sizing decision","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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\":","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.\nprepare: 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).\nfinalize: 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Let's start by adding the functions entry (which we suggest doing at the end of the file):","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"# ... the whole docstring ...\n\nparameters:\n # ...\n\ncomponent:\n # ...\n\nfunctions:\n validate: |\n # ... we will put the validation code here ...\n prepare: |\n # ... we will put the preparation code here ...\n finalize: |\n # ... we will put the finalization code here ...","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Validation","page":"Templates: Part I","title":"Validation","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"The validation function is used to check if the parameters are valid. Add the following code to the validate section:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"functions:\n validate: |\n # Check if `p_nom` is non-negative.\n @check get(\"p_nom\") isa Number\n @check get(\"p_nom\") >= 0\n\n # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`.\n @check get(\"electricity_from\") isa String\n @check get(\"heat_from\") isa String || isnothing(get(\"heat_from\"))\n @check get(\"heat_to\") isa String\n\n # Check if `cop` is positive.\n @check get(\"cop\") isa Number\n @check get(\"cop\") > 0\n # ... the rest of the template ...","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Let's go through this step by step:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"You can start comments (as separate line or inline) with #, as you would in Python.\nYou can use get(\"some_param\") to access the value of a parameter.\nYou 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.\nYou 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).\nData 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).\nLogical operators are similar to Python, so || is like or, and && is like and.\nIf all checks pass, the template is considered valid, and the model can be built.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Preparation","page":"Templates: Part I","title":"Preparation","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"parameters:\n p_nom: null\n p_nom_max: null\n electricity_from: null\n heat_from: null\n heat_to: null\n cop: null\n _inputs: null\n _conversion: null\n _capacity: null\n _invest: null","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"One step at a time. We added the following parameters:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.\n_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.\n_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).\n_conversion: This is another internal parameter, which we will use to store the conversion formula.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"components:\n unit:\n type: Unit\n inputs: <_inputs>\n outputs: {heat: }\n conversion: <_conversion>\n capacity: <_capacity> in:electricity\n \n decision:\n type: Decision\n enabled: <_invest>\n lb: \n ub: ","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"So ... a lot of changes. Let's go through them step by step:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"We changed component to components, because we now have multiple components.\nWe added a unit component, which is the actual heat pump. We replaced the fixed values with the internal parameters.\nWe 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"info: Naming the components\nThe 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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Onto the actual functionality. Let's add the prepare function, and some additional validation code:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"functions:\n validate: |\n # ... the previous validation code ...\n\n # Check if `p_nom_max` is either `nothing` or at least `p_nom`.\n @check isnothing(get(\"p_nom_max\")) || (get(\"p_nom_max\") isa Number && get(\"p_nom_max\") >= get(\"p_nom\"))\n prepare: |\n # Determine if investment should be enabled, and set the parameter (used to enable `decision`).\n invest = !isnothing(get(\"p_nom_max\")) && get(\"p_nom_max\") > get(\"p_nom\")\n self = get(\"self\")\n\n set(\"_invest\", invest)\n if invest\n # Set the capacity to the size of the decision variable.\n set(\"_capacity\", \"$(self).decision:value\")\n else\n # Set the capacity to the value of `p_nom`.\n set(\"_capacity\", get(\"p_nom\"))\n end\n\n # Prepare some helper variables to make the code afterwards more readable.\n elec_from = get(\"electricity_from\")\n heat_from = get(\"heat_from\")\n cop = get(\"cop\")\n\n # Handle the optional `heat_from` parameter.\n if isnothing(heat_from)\n # If `heat_from` is not specified, we just use electricity as input.\n set(\"_inputs\", \"{electricity: $(elec_from)}\")\n set(\"_conversion\", \"1 electricity -> $(cop) heat\")\n else\n # If `heat_from` is specified, we now have to account for two inputs.\n set(\"_inputs\", \"{electricity: $(elec_from), heat: $(heat_from)}\")\n set(\"_conversion\", \"1 electricity + $(cop - 1) heat -> $(cop) heat\")\n end","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"Once again, let's go through this step by step:","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Complex-COP-configurations","page":"Templates: Part I","title":"Complex COP configurations","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#The-finalize-function","page":"Templates: Part I","title":"The finalize function","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Finalizing-the-docstring","page":"Templates: Part I","title":"Finalizing the docstring","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Conclusion","page":"Templates: Part I","title":"Conclusion","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"details: Complete template YAML\n# # Custom Heat Pump\n\n# A (custom) heat pump that consumes electricity and heat, and produces heat.\n\n# ## Parameters\n# - `p_nom`: The nominal power (electricity) of the heat pump.\n# - `electricity_from`: The `Node` that this heat pump is connected to for electricity input.\n# - `heat_from`: The `Node` that this heat pump is connected to for heat input.\n# - `heat_to`: The `Node` that this heat pump is connected to for heat output.\n# - `cop`: The coefficient of performance of the heat pump.\n\n# ## Components\n# _to be added_\n\n# ## Usage\n# _to be added_\n\n# ## Details\n# _to be added_\n\nparameters:\n p_nom: null\n p_nom_max: null\n electricity_from: null\n heat_from: null\n heat_to: null\n cop: null\n _inputs: null\n _conversion: null\n _capacity: null\n _invest: null\n\ncomponents:\n unit:\n type: Unit\n inputs: <_inputs>\n outputs: {heat: }\n conversion: <_conversion>\n capacity: <_capacity> in:electricity\n \n decision:\n type: Decision\n enabled: <_invest>\n lb: \n ub: \n\nfunctions:\n validate: |\n # Check if `p_nom` is non-negative.\n @check get(\"p_nom\") isa Number\n @check get(\"p_nom\") >= 0\n\n # Check if the `Node` parameters are `String`s, where `heat_from` may also be `nothing`.\n @check get(\"electricity_from\") isa String\n @check get(\"heat_from\") isa String || isnothing(get(\"heat_from\"))\n @check get(\"heat_to\") isa String\n\n # Check if `cop` is positive.\n @check get(\"cop\") isa Number\n @check get(\"cop\") > 0\n\n # Check if `p_nom_max` is either `nothing` or at least `p_nom`.\n @check isnothing(get(\"p_nom_max\")) || (get(\"p_nom_max\") isa Number && get(\"p_nom_max\") >= get(\"p_nom\"))\n prepare: |\n # Determine if investment should be enabled, and set the parameter (used to enable `decision`).\n invest = !isnothing(get(\"p_nom_max\")) && get(\"p_nom_max\") > get(\"p_nom\")\n self = get(\"self\")\n\n set(\"_invest\", invest)\n if invest\n # Set the capacity to the size of the decision variable.\n set(\"_capacity\", \"$(self).decision:value\")\n else\n # Set the capacity to the value of `p_nom`.\n set(\"_capacity\", get(\"p_nom\"))\n end\n\n # Prepare some helper variables to make the code afterwards more readable.\n elec_from = get(\"electricity_from\")\n heat_from = get(\"heat_from\")\n cop = get(\"cop\")\n\n # Handle the optional `heat_from` parameter.\n if isnothing(heat_from)\n # If `heat_from` is not specified, we just use electricity as input.\n set(\"_inputs\", \"{electricity: $(elec_from)}\")\n set(\"_conversion\", \"1 electricity -> $(cop) heat\")\n else\n # If `heat_from` is specified, we now have to account for two inputs.\n set(\"_inputs\", \"{electricity: $(elec_from), heat: $(heat_from)}\")\n set(\"_conversion\", \"1 electricity + $(cop - 1) heat -> $(cop) heat\")\n end","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/#Next-steps","page":"Templates: Part I","title":"Next steps","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_1/","page":"Templates: Part I","title":"Templates: Part I","text":"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.","category":"page"},{"location":"pages/user_guide/general/#General","page":"General","title":"General","text":"","category":"section"},{"location":"pages/user_guide/general/","page":"General","title":"General","text":"To be added.","category":"page"},{"location":"pages/manual___reference/yaml/#YAML","page":"YAML","title":"YAML","text":"","category":"section"},{"location":"pages/manual___reference/yaml/","page":"YAML","title":"YAML","text":"To be added (based on docstrings from, e.g., the configs).","category":"page"},{"location":"pages/user_guide/sectors/electricity/#Electricity","page":"Electricity","title":"Electricity","text":"","category":"section"},{"location":"pages/user_guide/sectors/electricity/","page":"Electricity","title":"Electricity","text":"To be added.","category":"page"},{"location":"pages/user_guide/sectors/gas/#Gas","page":"Gas","title":"Gas","text":"","category":"section"},{"location":"pages/user_guide/sectors/gas/","page":"Gas","title":"Gas","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/#Custom-Components","page":"Custom Components","title":"Custom Components","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/","page":"Custom Components","title":"Custom Components","text":"To be added.","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#Templates","page":"Templates","title":"Templates","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"\"Core Templates\" are pre- or user-defined templates, that group and/or reparameterize IESopt.jl CoreComponents, or even other Core Templates. They are used to define more complex building blocks, that can be used in the optimization model.","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#General-structure","page":"Templates","title":"General structure","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"A Core Template is defined by a YAML file, ending in .iesopt.template.yaml, that may contain the following entries:","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"parameters (optional): a dictionary of parameters that can be used to reparameterize the template\nfunctions (optional): validate, prepare, and finalize functions, containing Julia code\nfiles (optional): files can be loaded here \"on demand\" similar to the top-level config","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"Further exactly one of the following entries is required:","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"components: a dictionary of components that are part of the template, or\ncomponent: a single component that is part of the template","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#Example:-A-simple-template","page":"Templates","title":"Example: A simple template","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"parameters:\n some_custom_param: null\n another_one: 100.0\n one_more: heat\n\ncomponents:\n a_node:\n type: Node\n\n b_node:\n type: Node","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (more details).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#manual_templates_validate","page":"Templates","title":"Validate","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"validation: |\n @check parameters[\"carrier\"] isa String\n @check parameters[\"carrier\"] in [\"heat\", \"electricity\"]","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (more examples).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#manual_templates_prepare","page":"Templates","title":"Prepare","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/user_guide/custom_functionality/templates/#manual_templates_finalize","page":"Templates","title":"Finalize","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/tutorials/setup/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"To be added.","category":"page"},{"location":"pages/tutorials/setup/#Python","page":"Installation","title":"Python","text":"","category":"section"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"To be added.","category":"page"},{"location":"pages/tutorials/setup/#Julia","page":"Installation","title":"Julia","text":"","category":"section"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"To be added.","category":"page"},{"location":"pages/tutorials/setup/","page":"Installation","title":"Installation","text":"Add something here.","category":"page"},{"location":"pages/user_guide/custom_functionality/addons/#Addons","page":"Addons","title":"Addons","text":"","category":"section"},{"location":"pages/user_guide/custom_functionality/addons/","page":"Addons","title":"Addons","text":"To be added.","category":"page"},{"location":"pages/manual___reference/templates/#Templates","page":"Templates","title":"Templates","text":"","category":"section"},{"location":"pages/manual___reference/templates/","page":"Templates","title":"Templates","text":"To be added (explanation).","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Templates:-Part-II","page":"Templates: Part II","title":"Templates: Part II","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Catching-errors","page":"Templates: Part II","title":"Catching errors","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Separate-.jl-files","page":"Templates: Part II","title":"Separate .jl files","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#Some-examples","page":"Templates: Part II","title":"Some examples","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/#HeatPump","page":"Templates: Part II","title":"HeatPump","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#BESS","page":"Templates: Part II","title":"BESS","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#PV","page":"Templates: Part II","title":"PV","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/tutorials/creating_new_components/templates_2/#CHP","page":"Templates: Part II","title":"CHP","text":"","category":"section"},{"location":"pages/tutorials/creating_new_components/templates_2/","page":"Templates: Part II","title":"Templates: Part II","text":"To be added.","category":"page"},{"location":"pages/references/#Overview","page":"References","title":"Overview","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"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 Contributing section.","category":"page"},{"location":"pages/references/#Publications","page":"References","title":"Publications","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"using IESopt, Markdown\npath = abspath(dirname(pathof(IESopt)), \"..\", \"docs\", \"src\", \"pages\", \"references\", \"publications\")\n\nref_str = \"\"\nitems = []\nfor file in readdir(path; join=true)\n endswith(file, \".md\") || continue\n raw_content = read(file, String)\n \n fulltitle = \"\"\n header = \"\"\n content = \"\"\n for line in eachline(IOBuffer(raw_content))\n if startswith(line, \"# \")\n fulltitle = line[3:end]\n if length(fulltitle) > 92\n header = \"$(fulltitle[1:88]) ...\"\n else\n header = fulltitle\n end\n else\n content = \"$(content)\\n$(line)\"\n end\n end\n\n push!(items, \"\"\"\n !!! details \"$(header)\"\n _**$(fulltitle)**_\n $(replace(content, \"\\n\" => \"\\n \"))\n \"\"\")\nend\n\nMarkdown.parse(join(items, \"\\n\"))","category":"page"},{"location":"pages/references/#Projects","page":"References","title":"Projects","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"using IESopt, Markdown\npath = abspath(dirname(pathof(IESopt)), \"..\", \"docs\", \"src\", \"pages\", \"references\", \"projects\")\n\nref_str = \"\"\nitems = []\nfor file in readdir(path; join=true)\n endswith(file, \".md\") || continue\n raw_content = read(file, String)\n \n fulltitle = \"\"\n header = \"\"\n content = \"\"\n for line in eachline(IOBuffer(raw_content))\n if startswith(line, \"# \")\n fulltitle = line[3:end]\n if length(fulltitle) > 92\n header = \"$(fulltitle[1:88]) ...\"\n else\n header = fulltitle\n end\n else\n content = \"$(content)\\n$(line)\"\n end\n end\n\n push!(items, \"\"\"\n !!! details \"$(header)\"\n _**$(fulltitle)**_\n $(replace(content, \"\\n\" => \"\\n \"))\n \"\"\")\nend\n\nMarkdown.parse(join(items, \"\\n\"))","category":"page"},{"location":"pages/references/#Contributing","page":"References","title":"Contributing","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"To contribute a new reference, either","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"fork the IESopt repository, and directly add to the above list, or\nopen an issue with the reference details.","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"See the template below for the structure of a reference.","category":"page"},{"location":"pages/references/#Publication-Template","page":"References","title":"Publication Template","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"Please stick to APA format here, and always include a link as badge (if possible a DOI, if not other links are okay too).","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"# Title of the Publication\n\n[![CITATION](url-of-your-badge)](link-to-doi-or-pure-or-other)\n\n> _**Abstract --**_ Put your abstract text here.\n\n> _**Keywords --**_ Put some, Keywords, In this, List\n\n!!! details \"Expand: Show citation\"\n > Add your (APA styled!) citation here\n","category":"page"},{"location":"pages/references/#Project-Template","page":"References","title":"Project Template","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"To be added.","category":"page"},{"location":"pages/references/#Creating-citation-badges","page":"References","title":"Creating citation badges","text":"","category":"section"},{"location":"pages/references/","page":"References","title":"References","text":"You can use shields.io to create badges, or use standardized ones that you already have (e.g., from Zenodo), otherwhise stick to the ones provided below.","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"Pure: (publications.ait.ac.at)[![CITATION](https://img.shields.io/badge/PURE-publications.ait.ac.at-none?style=social)](ADDYOURLINKHERE)","category":"page"},{"location":"pages/references/","page":"References","title":"References","text":"DOI:[![CITATION](https://img.shields.io/badge/DOI-10.XXXX%2Fname.YYYY.ZZZZZZ-none?style=social)](https://doi.org/10.XXXX/name.YYYY.ZZZZZZ)","category":"page"},{"location":"pages/tutorials/results/#Result-extraction","page":"Result extraction","title":"Result extraction","text":"","category":"section"},{"location":"pages/tutorials/results/","page":"Result extraction","title":"Result extraction","text":"To be added.","category":"page"},{"location":"pages/changelog/#Changelog","page":"Changelog","title":"Changelog","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"All notable changes to this project will be documented in this file.","category":"page"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"pages/changelog/#[1.0.5]-2024-09-11","page":"Changelog","title":"[1.0.5] - 2024-09-11","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Bug fixes in custom objective building, and better exception handling for log files.","category":"page"},{"location":"pages/changelog/#[1.0.4]-2024-07-26","page":"Changelog","title":"[1.0.4] - 2024-07-26","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Migrate full core component parameter docstrings.","category":"page"},{"location":"pages/changelog/#[1.0.3]-2024-06-18","page":"Changelog","title":"[1.0.3] - 2024-06-18","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Relax version requirements on IESoptLib to include all v0.2.z versions.","category":"page"},{"location":"pages/changelog/#[1.0.2]-2024-06-10","page":"Changelog","title":"[1.0.2] - 2024-06-10","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Fix solver setup for various workflows.","category":"page"},{"location":"pages/changelog/#Changed","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"IESoptLib and HiGHS are again required dependencies.","category":"page"},{"location":"pages/changelog/#[1.0.1]-2024-06-09","page":"Changelog","title":"[1.0.1] - 2024-06-09","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Added extensions to properly handle loading IESoptLib and various solvers.","category":"page"},{"location":"pages/changelog/#Changed-2","page":"Changelog","title":"Changed","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"IESoptLib and HiGHS are no longer required dependencies.","category":"page"},{"location":"pages/changelog/#Fixed","page":"Changelog","title":"Fixed","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Dynamic loading of weakdeps now works properly.","category":"page"},{"location":"pages/changelog/#[1.0.0]-2024-06-01","page":"Changelog","title":"[1.0.0] - 2024-06-01","text":"","category":"section"},{"location":"pages/changelog/#Added","page":"Changelog","title":"Added","text":"","category":"section"},{"location":"pages/changelog/","page":"Changelog","title":"Changelog","text":"Initial public release of IESopt.jl","category":"page"},{"location":"pages/tutorials/first_model/#First-steps","page":"First steps","title":"First steps","text":"","category":"section"},{"location":"pages/tutorials/first_model/#Need-help?","page":"First steps","title":"Need help?","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added.","category":"page"},{"location":"pages/tutorials/first_model/#Overview","page":"First steps","title":"Overview","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"IESopt (the framework) consists of various sub-projects. It is a component-based optimization framework, where each component can be seen as block containing some predefined functionality. There are five \"core components\": Connection, Decision, Node, Profile, and Unit. These will be used to define arbitrary energy system models, similar to how a general commodity flow model works. Furthermore, they can be combined to create more complicated (non-core) components.","category":"page"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"In their most basic form, core components can be described as:","category":"page"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, ...\nA 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.\nA Node represents a basic intersection/hub for energy flows. This can for example be some sort of bus (for electrical systems).\nA Profile allows representing exogenous functionality with a support for time series data.\nA Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.","category":"page"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"For most models, the Units will pack the most raw functionality, while the other components represent the structure of the overall model.","category":"page"},{"location":"pages/tutorials/first_model/#Your-first-model","page":"First steps","title":"Your first model","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Model-config","page":"First steps","title":"Model config","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Energy-carriers","page":"First steps","title":"Energy carriers","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Model-components","page":"First steps","title":"Model components","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Final-config-file","page":"First steps","title":"Final config file","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Running-the-optimization","page":"First steps","title":"Running the optimization","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Extracting-results","page":"First steps","title":"Extracting results","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#General-result-structure","page":"First steps","title":"General result structure","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Changing-the-model-config","page":"First steps","title":"Changing the model config","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Adapting-components","page":"First steps","title":"Adapting components","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Analyzing-the-results","page":"First steps","title":"Analyzing the results","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Extracting-results-directly-into-pd.DataFrames","page":"First steps","title":"Extracting results directly into pd.DataFrames","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/tutorials/first_model/#Final-thoughts","page":"First steps","title":"Final thoughts","text":"","category":"section"},{"location":"pages/tutorials/first_model/","page":"First steps","title":"First steps","text":"To be added (translate from internal version).","category":"page"},{"location":"pages/user_guide/sectors/heat/#Heat","page":"Heat","title":"Heat","text":"","category":"section"},{"location":"pages/user_guide/sectors/heat/","page":"Heat","title":"Heat","text":"To be added.","category":"page"},{"location":"pages/manual___reference/core_components/#Model-formulation","page":"Model formulation","title":"Model formulation","text":"","category":"section"},{"location":"pages/manual___reference/core_components/","page":"Model formulation","title":"Model formulation","text":"This page collects all information about the core components of IESopt, their properties (and default values), their internal mathematical formulations, and some examples of how to use them. See the API for a complete list of all available functions and types.","category":"page"},{"location":"pages/manual___reference/core_components/","page":"Model formulation","title":"Model formulation","text":"CurrentModule = IESopt\nCollapsedDocStrings = true","category":"page"},{"location":"pages/manual___reference/core_components/","page":"Model formulation","title":"Model formulation","text":"Connection\nDecision\nNode\nProfile\nUnit","category":"page"},{"location":"pages/manual___reference/core_components/#IESopt.Connection","page":"Model formulation","title":"IESopt.Connection","text":"A Connection is used to model arbitrary flows of energy between Nodes. It allows for limits, costs, delays, ...\n\nParameters\n\nName Mandatory Values Unit Default Description\nnode_from yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\nnode_to yes string - - This Connection models a flow from node_from to node_to (both are Nodes).\ncarrier no string - - 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.\ncapacity no numeric, col@file, decision:value power +infty 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.\nlb no numeric, col@file, decision:value power -infty Lower bound of this Connection's flow.\nub no numeric, col@file, decision:value power +infty Upper bound of this Connection's flow.\ncost no numeric monetary (per energy) - 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.\nloss no in 0 1 - 0 Fractional loss when transfering energy. 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: flow\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_connection\").var.flow# Python\nmodel.get_component(\"your_connection\").var.flowYou can find the full implementation and all details here: 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 inbeginaligned\n textconnectionnode_fromtextinjection_t = textconnectionnode_fromtextinjection_t - textflow_t qquad forall t in T \n textconnectionnode_totextinjection_t = textconnectionnode_totextinjection_t + textflow_t qquad forall t in T\nendalignedmathFor \"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].\n\nExpressions\n\ndetails: pf_flow\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_connection\").exp.pf_flow# Python\nmodel.get_component(\"your_connection\").exp.pf_flowYou can find the full implementation and all details here: 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, ...).\n\nConstraints\n\ndetails: flow_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_connection\").con.flow_bounds# Python\nmodel.get_component(\"your_connection\").con.flow_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the flow (related to connection) to the model.Specifiying capacity will lead to symmetric bounds (textlb = -capacity and textub = capacity), while asymmetric bounds can be set by explicitly specifiying lb and ub.note: Note\nUsage of etdf is currently not fully tested, and not documented.Upper and lower bounds can be \"infinite\" (by not setting them) resulting in the repective 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,\nif connection.exp.pf_flow is available, it equals this\nelse it equal connection.var.flowThis flow is then constrained:beginaligned\n textflow_t geq textlb qquad forall t in T \n textflow_t leq textub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_connection\").obj.cost# Python\nmodel.get_component(\"your_connection\").obj.costYou can find the full implementation and all details here: 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 textflow_t cdot textcost_t cdot omega_tmathHere omega_t is the weight of Snapshot t.note: Costs for flows in both directions\nIf 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.\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Decision","page":"Model formulation","title":"IESopt.Decision","text":"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.\n\nParameters\n\nName Mandatory Values Unit Default Description\nlb no numeric - 0 Minimum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\nub no numeric - +infty Maximum size of the decision value (considered for each \"unit\" if count allows multiple \"units\").\ncost no numeric monetary (per value) 0 Cost that the decision value induces, given as cost cdot value.\nfixed_value no numeric - - 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.\nfixed_cost no - monetary - 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.\nmode no linear, binary, integer, sos1, sos2, fixed - linear 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.\nsos no list - - TODO (meanwhile, refer to the SOS or PiecewiseLinearCost example).\nbuild_priority no numeric - 1000 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: fixed\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.fixed# Python\nmodel.get_component(\"your_decision\").var.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.sos# Python\nmodel.get_component(\"your_decision\").var.sosYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_decision\").var.value# Python\nmodel.get_component(\"your_decision\").var.valueYou can find the full implementation and all details here: 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.\n\nExpressions\n\nConstraints\n\ndetails: fixed\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.fixed# Python\nmodel.get_component(\"your_decision\").con.fixedYou can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos1\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos1# Python\nmodel.get_component(\"your_decision\").con.sos1You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos2\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos2# Python\nmodel.get_component(\"your_decision\").con.sos2You can find the full implementation and all details here: IESopt.jl.to be added\n\ndetails: sos_value\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_decision\").con.sos_value# Python\nmodel.get_component(\"your_decision\").con.sos_valueYou can find the full implementation and all details here: IESopt.jl.to be added\n\nObjectives\n\ndetails: fixed\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.fixed# Python\nmodel.get_component(\"your_decision\").obj.fixedYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: sos\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.sos# Python\nmodel.get_component(\"your_decision\").obj.sosYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the SOS-based value of this Decision to the model.\n\ndetails: value\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_decision\").obj.value# Python\nmodel.get_component(\"your_decision\").obj.valueYou can find the full implementation and all details here: IESopt.jl.Add the cost defined by the value of this Decision to the model:textvalue cdot textcostmath\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Node","page":"Model formulation","title":"IESopt.Node","text":"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, ...). \n\ndetails: Basic Examples\nA Node that represents an electrical bus:bus:\n type: Node\n carrier: electricityA Node that represents a simplified hydrogen storage:store:\n type: Node\n carrier: hydrogen\n has_state: true\n state_lb: 0\n state_ub: 50\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Node. All connecting components need to respect that.\nhas_state no true, false - false 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.\nstate_lb no numeric, col@file, decision:value energy -infty Lower bound of the internal state, requires has_state = true.\nstate_ub no numeric, col@file, decision:value energy +infty Upper bound of the internal state, requires has_state = true.\nstate_cyclic no eq, geq, or disabled - eq 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\").\nstate_initial no numeric energy - Sets the initial state. Must be used in combination with state_cyclic = disabled.\nstate_final no numeric energy - Sets the final state. Must be used in combination with state_cyclic = disabled.\nstate_percentage_loss no in 0 1 - 0 Per Snapshot percentage loss of state (loosing 1% should be set as 0.01).\nnodal_balance no enforce, destroy, or create - enforce 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.\nsum_window_size no integer - - TODO.\nsum_window_step no integer - 1 TODO.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: pf_theta\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.pf_theta# Python\nmodel.get_component(\"your_node\").var.pf_thetaYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: state\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_node\").var.state# Python\nmodel.get_component(\"your_node\").var.stateYou can find the full implementation and all details here: 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:textstate_1 = textstate_initialmath\n\nExpressions\n\ndetails: injection\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_node\").exp.injection# Python\nmodel.get_component(\"your_node\").exp.injectionYou can find the full implementation and all details here: 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 textinjection_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\".\n\nConstraints\n\ndetails: last_state\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.last_state# Python\nmodel.get_component(\"your_node\").con.last_stateYou can find the full implementation and all details here: 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.beginaligned\n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t geq textstate_lb \n textstate_end cdot textfactor^omega_t + textinjection_end cdot omega_t leq textstate_ub \nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise.note: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\ndetails: nodalbalance\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.nodalbalance# Python\nmodel.get_component(\"your_node\").con.nodalbalanceYou can find the full implementation and all details here: 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 == truebeginaligned\n textstate_t = textstate_t-1 cdot textfactor^omega_t-1 + textinjection_t-1 cdot omega_t-1 qquad forall t in T setminus 1 \n \n textstate_1 = textstate_end cdot textfactor^omega_end + textinjection_end cdot omega_end\nendalignedmathHere omega_t is the weight of Snapshot t, and textfactor is either 1.0 (if there are now percentage losses configured), or (1.0 - node.state_percentage_loss) otherwise. textinjection_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 == falsebeginaligned\n textinjection_t = 0 qquad forall t in T \nendalignedmathThis 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. \n\ndetails: state_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_node\").con.state_bounds# Python\nmodel.get_component(\"your_node\").con.state_boundsYou can find the full implementation and all details here: IESopt.jl.Add the constraint defining the bounds of the node's state to the model, if node.has_state == true.beginaligned\n textstate_t geq textstate_lb qquad forall t in T \n textstate_t leq textstate_ub qquad forall t in T\nendalignedmathnote: Constraint safety\nThe lower and upper bound constraint are subject to penalized slacks.\n\nObjectives\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Profile","page":"Model formulation","title":"IESopt.Profile","text":"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.\n\ndetails: Basic Examples\nA Profile that depicts a fixed electricity demand:demand_XY:\n type: Profile\n carrier: electricity\n node_from: grid\n value: demand_XY@input_fileA Profile that handles cost of fuel:fuel_gas:\n type: Profile\n carrier: gas\n node_to: country_gas_grid\n mode: create\n cost: 100.0A Profile that handles CO2 emission costs:co2_cost:\n type: Profile\n carrier: co2\n node_from: total_co2\n mode: destroy\n cost: 150.0A Profile that handles selling electricity:sell_electricity:\n type: Profile\n carrier: electricity\n node_from: internal_grid_node\n mode: destroy\n cost: -30.0\n\nParameters\n\nName Mandatory Values Unit Default Description\ncarrier yes string - - Carrier of this Profile. Must match the Carrier of the Node that this connects to.\nvalue no numeric, col@file power - The concrete value of this Profile - either static or as time series. Only applicable if mode: fixed.\nnode_from no string - - Name of the Node that this Profile draws energy from. Exactly one of node_from and node_to must be set.\nnode_to no string - - Name of the Node that this Profile feeds energy to. Exactly one of node_from and node_to must be set.\nmode no - - fixed 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).\nlb no numeric power -infty The lower bound of the range of this Profile (must be used together with mode: ranged).\nub no numeric power +infty The upper bound of the range of this Profile (must be used together with mode: ranged).\ncost no numeric monetary per energy 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: aux_value\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_profile\").var.aux_value# Python\nmodel.get_component(\"your_profile\").var.aux_valueYou can find the full implementation and all details here: 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:fixed: The value is already handled by the constant term of profile.exp.value and NO variable is constructed.\ncreate, 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 detailled 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 Nodes that the profile is connected to, resulting inbeginaligned\n textprofilenode_fromtextinjection_t = textprofilenode_fromtextinjection_t - textvalue_t qquad forall t in T \n textprofilenode_totextinjection_t = textprofilenode_totextinjection_t + textvalue_t qquad forall t in T\nendalignedmath\n\nExpressions\n\ndetails: value\ntip: How to?\nAccess this expression by using:# Julia\ncomponent(model, \"your_profile\").exp.value# Python\nmodel.get_component(\"your_profile\").exp.valueYou can find the full implementation and all details here: IESopt.jl.Cosntruct 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 intialized based on profile.value.\n\nConstraints\n\ndetails: value_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_profile\").con.value_bounds# Python\nmodel.get_component(\"your_profile\").con.value_boundsYou can find the full implementation and all details here: 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 === :destroybeginaligned\n textaux_value_t geq 0 qquad forall t in T\nendalignedmathif profile.mode === :rangedbeginaligned\n textvalue_t geq textlb_t qquad forall t in T \n textvalue_t leq textub_t qquad forall t in T\nendalignedmathHere, lb and ub can be left empty, which drops the respective constraint.\n\nObjectives\n\ndetails: cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_profile\").obj.cost# Python\nmodel.get_component(\"your_profile\").obj.costYou can find the full implementation and all details here: 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_tin T textvalue_t cdot textprofilecost_t cdot omega_tmathHere omega_t is the weight of Snapshot t, and textvalue_t actually refers to the value of profile.exp.value[t] (and not only on the maybe non-existing variable).\n\n\n\n\n\n","category":"type"},{"location":"pages/manual___reference/core_components/#IESopt.Unit","page":"Model formulation","title":"IESopt.Unit","text":"A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.\n\ndetails: Basic Examples\nA Unit that represents a basic gas turbine:gas_turbine:\n type: Unit\n inputs: {gas: gas_grid}\n outputs: {electricity: node, co2: total_co2}\n conversion: 1 gas -> 0.4 electricity + 0.2 co2\n capacity: 10 out:electricityA Unit that represents a basic wind turbine:wind_turbine:\n type: Unit\n outputs: {electricity: node}\n conversion: ~ -> 1 electricity\n capacity: 10 out:electricity\n availability_factor: wind_factor@input_data\n marginal_cost: 1.7 per out:electricityA Unit that represents a basic heat pump, utilizing a varying COP:heatpump:\n type: Unit\n inputs: {electricity: grid}\n outputs: {heat: heat_system}\n conversion: 1 electricity -> cop@inputfile heat\n capacity: 10 in:electricity\n\nParameters\n\nName Mandatory Values Unit Default Description\nconversion yes string - - 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).\ncapacity yes value dir:carrier - - 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\").\noutputs yes dict - - Dictionary specifying the output \"ports\" of this Unit. Refer to the basic examples for the general syntax.\ninputs no dict - - 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.\navailability no numeric power +infty 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.\navailability_factor no in 0 1 - 1 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.\nadapt_min_to_availability no true, false - false 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.\nmarginal_cost no value per dir:carrier monetary per energy 0 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.\nenable_ramp_up no true, false - false Enables calculation of upward ramps. Ramping is based on the carrier specified in capacity.\nenable_ramp_down no true, false - false Enables calculation of downward ramps. Ramping is based on the carrier specified in capacity.\nramp_up_cost no numeric monetary per power 0 Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.\nramp_down_cost no numeric monetary per power 0 Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.\nramp_up_limit no in 0 1 - 1 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.\nramp_down_limit no in 0 1 - 1 Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.\nmin_on_time no numeric hours 0 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.\nmin_off_time no numeric hours 0 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.\non_time_before no numeric hours 0 Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.\noff_time_before no numeric hours 0 Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.\nis_on_before no numeric - 1 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.\nunit_commitment no off, linear, binary, integer - off 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).\nunit_count no numeric - 1 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, ...).\nmin_conversion no in 0 1 - - 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.\nconversion_at_min no string - - 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.\nstartup_cost no numeric monetary per start 0 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.\nbuild_priority no numeric - 0 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.\n\nDetailed Model Reference\n\nVariables\n\ndetails: conversion\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion# Python\nmodel.get_component(\"your_unit\").var.conversionYou can find the full implementation and all details here: 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.info: Info\nThis 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).\n\ndetails: conversion_connect\nNo documentation found.IESopt._unit_var_conversion_connect! is a Function.tip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.conversion_connect# Python\nmodel.get_component(\"your_unit\").var.conversion_connectYou can find the full implementation and all details here: IESopt.jl.\n\ndetails: ison\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ison# Python\nmodel.get_component(\"your_unit\").var.isonYou can find the full implementation and all details here: 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 textison leq textunitcount, with textunitcount 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].\n\ndetails: ramp\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.ramp# Python\nmodel.get_component(\"your_unit\").var.rampYou can find the full implementation and all details here: 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!\n\ndetails: startup\ntip: How to?\nAccess this variable by using:# Julia\ncomponent(model, \"your_unit\").var.startup# Python\nmodel.get_component(\"your_unit\").var.startupYou can find the full implementation and all details here: 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 textstartup leq textunitcount, with textunitcount 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.\n\nExpressions\n\nConstraints\n\ndetails: conversion_bounds\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.conversion_bounds# Python\nmodel.get_component(\"your_unit\").con.conversion_boundsYou can find the full implementation and all details here: 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)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T \n textconversion_t leq textavailability_t qquad forall t in T\nendalignedmathThis effectively results in textconversion_t leq min(textcapacity_textonline t textavailability_t).if !isnothing(unit.availability_factor)beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t cdot textavailability_textfactor t qquad forall t in T\nendalignedmathinfo: Info\nIf 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:beginaligned\n textconversion_t geq textcapacity_textmin t qquad forall t in T \n textconversion_t leq textcapacity_textonline t qquad forall t in T\nendalignedmath\n\ndetails: ison\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ison# Python\nmodel.get_component(\"your_unit\").con.isonYou can find the full implementation and all details here: IESopt.jl.Construct the upper bound for var_ison, based on unit.unit_count, if it is handled by an external Decision.\n\ndetails: min_onoff_time\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.min_onoff_time# Python\nmodel.get_component(\"your_unit\").con.min_onoff_timeYou can find the full implementation and all details here: IESopt.jl.Add the constraints modeling min on- or off-time of a Unit to the model.This constructs the constraintsbeginalign\n sum_t = t^t + textmin_on_time ison_t = textmin_on_time cdot (ison_t - ison_t-1) qquad forall t in T \n sum_t = t^t + textmin_off_time (1 - ison_t) = textmin_off_time cdot (ison_t-1 - ison_t) qquad forall t in T\nendalign\n\nrespecting on_time_before and off_time_before and is_on_before See the code for more details\n\n info Aggregated units\n This is currently not fully adapted to account for Units with unit_count 1math\n\ndetails: ramp\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp# Python\nmodel.get_component(\"your_unit\").con.rampYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t geq textconversion_t - textconversion_t-1 qquad forall t in T \n textramp_textdown t geq textconversion_t-1 - textconversion_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0info: Info\nThis 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.\n\ndetails: ramp_limit\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.ramp_limit# Python\nmodel.get_component(\"your_unit\").con.ramp_limitYou can find the full implementation and all details here: 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:beginaligned\n textramp_textup t leq textramplimit_textup cdot textcapacity_textmax cdot omega_t qquad forall t in T \n textramp_textdown t leq textramplimit_textdown cdot textcapacity_textmax cdot omega_t qquad forall t in T\nendalignedmathThis 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\nramp_up[1] = ramp_down[1] = 0\n\ndetails: startup\ntip: How to?\nAccess this constraint by using:# Julia\ncomponent(model, \"your_unit\").con.startup# Python\nmodel.get_component(\"your_unit\").con.startupYou can find the full implementation and all details here: 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:beginaligned\n textstartup_textup t geq textison_t - textison_t-1 qquad forall t in T\nendalignedmathThis 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\n\nObjectives\n\ndetails: marginal_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.marginal_cost# Python\nmodel.get_component(\"your_unit\").obj.marginal_costYou can find the full implementation and all details here: IESopt.jl.Add the (potential) cost of this unit's conversion (unit.marginal_cost) to the global objective function.sum_t in T textconversion_t cdot textmarginalcost_t cdot omega_tmath\n\ndetails: ramp_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.ramp_cost# Python\nmodel.get_component(\"your_unit\").obj.ramp_costYou can find the full implementation and all details here: 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 textramp_textup t cdot textrampcost_textup + textramp_textdown t cdot textrampcost_textdownmath\n\ndetails: startup_cost\ntip: How to?\nAccess this objective by using:# Julia\ncomponent(model, \"your_unit\").obj.startup_cost# Python\nmodel.get_component(\"your_unit\").obj.startup_costYou can find the full implementation and all details here: 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 textstartup_t cdot textstartupcostmath\n\n\n\n\n\n","category":"type"},{"location":"#IESopt.jl","page":"Home","title":"IESopt.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Integrated Energy System Optimization framework written in Julia.","category":"page"},{"location":"#Introduction","page":"Home","title":"Introduction","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"IESopt.jl is the core of the IESopt framework, developed at AIT Austrian Institute of Technology GmbH. It is written in Julia, and uses JuMP package to construct the underlying mathematical optimization models, and to interface with various solvers. The model is designed to be modular, and allows for easy extension and customization.","category":"page"},{"location":"","page":"Home","title":"Home","text":"# TODO: add \"The main functionalities of IESopt.jl are: ...\" itemize here.\n# TODO: add gitter here\n# TODO: cleanup the \"About\" section, and integrate it here","category":"page"},{"location":"","page":"Home","title":"Home","text":"Check out the following GitHub repositories for more information:","category":"page"},{"location":"","page":"Home","title":"Home","text":"IESopt.jl, the core model (a Julia package).\niesopt-py, the Python interface.","category":"page"},{"location":"","page":"Home","title":"Home","text":"danger: Moving to open-source\nWe are currently working (hard) on getting IESopt fully open-source on GitHub, which requires some clean-up of (potentially) confindential left-overs (e.g., from projects). Meanwhile, a lot of internals are changing (after staying fixed for a long time), and the documentation needs to be checked page-by-page. If you are trying to get started before we manage to fix everything, get in touch with us directly - we'll help you set up everything you need. The documentation currently consists of mostly structured pages, with the content being added as soon as possible.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"details: Using Python\nTo be added.","category":"page"},{"location":"","page":"Home","title":"Home","text":"details: Using Julia\nTo be added.","category":"page"},{"location":"#Citation","page":"Home","title":"Citation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"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:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Style: APA7\nStrö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\nStyle: IEEE\n[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\nBibTeX:\n@misc{iesopt,\n author = {Strömer, Stefan and Schwabeneder, Daniel and contributors},\n title = {{IES}opt: Integrated Energy System Optimization},\n organization = {AIT Austrian Institute of Technology GmbH},\n url = {https://github.com/ait-energy/IESopt},\n type = {Software},\n year = {2021-2024},\n}","category":"page"},{"location":"","page":"Home","title":"Home","text":"# ## About\n\n# ### Overview\n\n# IESopt, _Integrated Energy System Optimization_, is a general purpose energy system optimization framework, developed at the [Center for Energy](https://www.ait.ac.at/en/about-the-ait/center/center-for-energy), at [AIT Austrian Institute of Technology GmbH](https://www.ait.ac.at/), mainly developed and maintained by the unit [Integrated Energy Systems](https://www.ait.ac.at/en/research-topics/integrated-energy-systems).\n\n# ### Feature summary\n\n# What IESopt is, may be, and is not:\n\n# - YES\n# - IESopt is a general purpose energy system (optimization) model. It supports multiple solvers (using `JuMP.jl` to interface with them) as well as a standardized way to build up models using \"core components\".\n# - MAYBE\n# - IESopt.jl is not branded as JuMP extension. It plays nicely with JuMP, and some extensions, but we currently do not see it as a fully fledged JuMP extension. That, e.g., entails that `copy_extension_data` is not implemented at the moment, so `copy_model` is not supported. This is a deliberate choice, and may be changed in the - near or far - future.\n# - NO (really ...)\n# - A full energy system model, in the sense of \"containing data\". There are a lot of good, and open, data sources out there from other teams, consider using them.","category":"page"}] }