Unit
Note
This section of the documentation is auto-generated from the code of the Julia-based core model. Refer to IESopt.jl for any further details (which may require some familiarity with Julia).
If you spot incorrect math-mode rendering, or similar issues, please file an issue, since rendering documentation from Julia to Python is not the easiest task.
Overview
A Unit allows transforming one (or many) forms of energy into another one (or many), given some constraints and costs.
Basic Examples
A Unit that represents a basic gas turbine:
gas_turbine:
type: Unit
inputs: {gas: gas_grid}
outputs: {electricity: node, co2: total_co2}
conversion: 1 gas -> 0.4 electricity + 0.2 co2
capacity: 10 out:electricity
A Unit that represents a basic wind turbine:
wind_turbine:
type: Unit
outputs: {electricity: node}
conversion: ~ -> 1 electricity
capacity: 10 out:electricity
availability_factor: wind_factor@input_data
marginal_cost: 1.7 per out:electricity
A Unit that represents a basic heat pump, utilizing a varying COP:
heatpump:
type: Unit
inputs: {electricity: grid}
outputs: {heat: heat_system}
conversion: 1 electricity -> cop@inputfile heat
capacity: 10 in:electricity
Parameters
conversion
The conversion expression describing how this Unit transforms energy. Specified in the form of “\(\alpha \cdot carrier_1 + \beta \cdot carrier_2\) -> \(\gamma \cdot carrier_3 + \delta \cdot carrier_4\)”. Coefficients allow simple numerical calculations, but are not allowed to include spaces (so e.g. (1.0/9.0) is valid). Coefficients are allowed to be NumericalInputs, resulting in column@data_file being a valid coefficient (this can be used e.g. for time-varying COPs of heatpumps).
mandatory: yes
default: \(-\)
values: string
unit: -
capacity
Maximum capacity of this Unit, to be given in the format X in/out:carrier where X is the amount, in or out (followed by :) specifies whether the limit is to be placed on the in- our output of this Unit, and carrier specifies the respective Carrier. Example: 100 in:electricity (to limit the “input rating”).
mandatory: yes
default: \(-\)
values: value dir:carrier
unit: -
outputs
Dictionary specifying the output “ports” of this Unit. Refer to the basic examples for the general syntax.
mandatory: yes
default: \(-\)
values: dict
unit: -
inputs
Dictionary specifying the input “ports” of this Unit. If not specified (= no explicit input), the conversion has to follow the form of conversion: ~ -> ..., indicating an “open” input. This may, e.g., be used for renewable energy sources, where the primary energy input (e.g., solar) is not explicitly modeled.
mandatory: no
default: \(-\)
values: dict
unit: -
availability
Time series (or fixed value) that limits the available capacity. If, e.g., capacity: 100 out:electricity and availability: 70, the available capacity will only be 70 electricity. Can be used to model non-availability of power plants, e.g., due to maintenance. For time-varying availability of intermittent generators (e.g., wind), it’s recommended (most of the time) to use availability_factor instead.
mandatory: no
default: \(+\infty\)
values: numeric
unit: power
availability_factor
Similar to availability, but given as factor of capacity instead. If, e.g., capacity: 100 out:electricity and availability_factor: 0.7, the available capacity will only be 70 electricity. This is especially useful for intermittent generators, where the availability is not a fixed value, but depends on the weather, and can be passed, e.g., by setting availability_factor: wind@input_data_file.
mandatory: no
default: \(1\)
values:
\in [0, 1]unit: -
adapt_min_to_availability
If true, the minimal partial load will be influenced by the availability. Example: Consider a Unit with capacity: 100 out:electricity, a min_conversion of 0.4, and an availability_factor of 0.5. This entails having 50 electricity available, while the minimal partial load is 40 electricity. This results in the Unit at best operating only closely above the minimal partial load. Furthermore, an availability_factor below 0.4 would result in no feasible generation, besides shutting the Unit off. While this might be the intended mode of operation in many use cases, adapt_min_to_availability can change this: If set to true, this dynamically changes the minimal partial load. In the previous example, that means (100 * 0.5) * 0.4 = 20 electricity (the 50% minimum load are now based on the available 40), changing the overall behaviour (including efficiencies) as well as leading to feasible generations even when the availability_factor is below 0.4.
mandatory: no
default: \(false\)
values:
true,falseunit: -
marginal_cost
Marginal cost of the consumption/generation of one unit of energy of the specified carrier. Has to be given in the format value per dir:carrier, e.g. 3.5 per out:electricity for a marginal cost of 3.5 monetary units per unit of electricity generated.
mandatory: no
default: \(0\)
values:
value per dir:carrierunit: monetary per energy
enable_ramp_up
Enables calculation of upward ramps. Ramping is based on the carrier specified in capacity.
mandatory: no
default: \(false\)
values:
true,falseunit: -
enable_ramp_down
Enables calculation of downward ramps. Ramping is based on the carrier specified in capacity.
mandatory: no
default: \(false\)
values:
true,falseunit: -
ramp_up_cost
Sets the cost of ramping up (increasing in-/output) by 1 unit of the capacity carrier.
mandatory: no
default: \(0\)
values: numeric
unit: monetary per power
ramp_down_cost
Sets the cost of ramping down (decreasing in-/output) by 1 unit of the capacity carrier.
mandatory: no
default: \(0\)
values: numeric
unit: monetary per power
ramp_up_limit
Limits the allowed ramping up based on this factor of the total capacity. If capacity: 100 in:electricity with ramp_up_limit: 0.2, this limits the total increase of usage of electricity (on the input) to 20 units (power) per hour. For example, starting at an input of 35, after one hour the input has to be lesser than or equal to 55. If a Snapshot’s duration is set to, e.g., two hours, this would allow a total increase of 40 units.
mandatory: no
default: \(1\)
values:
\in [0, 1]unit: -
ramp_down_limit
Limits the allowed ramping down based on this factor of the total capacity. See ramp_up_limit.
mandatory: no
default: \(1\)
values:
\in [0, 1]unit: -
min_on_time
Minimum on-time of the Unit. If set, the Unit has to be on for at least this amount of time, after turning on. It is highly recommended to only use this with unit_commitment: binary, unless you know why it’s fine to use with another mode.
mandatory: no
default: \(0\)
values: numeric
unit: hours
min_off_time
Minimum off-time of the Unit. If set, the Unit has to be off for at least this amount of time, after turning off. It is highly recommended to only use this with unit_commitment: binary, unless you know why it’s fine to use with another mode.
mandatory: no
default: \(0\)
values: numeric
unit: hours
on_time_before
Time that this Unit has already been running before the optimization starts. Can be used in combination with min_on_time.
mandatory: no
default: \(0\)
values: numeric
unit: hours
off_time_before
Time that this Unit has already been off before the optimization starts. Can be used in combination with min_off_time.
mandatory: no
default: \(0\)
values: numeric
unit: hours
is_on_before
Number of Units that should be considered to have been running before the optimization starts. Can be used in combination with on_time_before, especially for unit_count greater than 1.
mandatory: no
default: \(1\)
values: numeric
unit: -
unit_commitment
Controls how the unit commitment of this Unit is handled. linear results in the ability to startup parts of the unit (so 0.314159 is a feasible amount of “turned on unit”), while binary restricts the Unit to either be on (converting the conversion_at_min + possible additional conversion above that minimum) or off (converting nothing); integer is needed to consider binary unit commitment for Units with more than 1 “grouped unit” (see unit_count).
mandatory: no
default: \(off\)
values:
off,linear,binary,integerunit: -
unit_count
Number of units aggregated in this Unit. Besides interacting with the mode of unit_commitment, this mainly is responsible for scaling the output (e.g. grouping 47 of the same wind turbine, …).
mandatory: no
default: \(1\)
values: numeric
unit: -
min_conversion
If unit_commitment is not set to off, this specifies the percentage that is considered to be the minimal feasible partial load this Unit can operate at. Operating below that setpoint is not allowed, at that point the conversion_at_min coefficients are used, and above that they are scaled to result in conversion when running at full capacity.
mandatory: no
default: \(-\)
values:
\in [0, 1]unit: -
conversion_at_min
The conversion expression while running on the minimal partial load. Only applicable if unit_commitment is not off and min_conversion is explicitly set. Follows the same form as conversion.
mandatory: no
default: \(-\)
values: string
unit: -
startup_cost
Costs per startup (also applicable if startups are not binary or integer). This is necessary to allow conversion_at_min to have (at least partially) the effect that one expects, if unit_commitment: linear.
mandatory: no
default: \(0\)
values: numeric
unit: monetary per start
build_priority
Priority for the build order of components. Components with higher build_priority are built before. This can be useful for addons, that connect multiple components and rely on specific components being initialized before others.
mandatory: no
default: \(0\)
values: numeric
unit: -
Detailed reference
Expressions
Variables
conversion
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.conversion
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.conversion
Full implementation and all details: unit/var_conversion @ IESopt.jl
Add the variable describing the
unit’s conversion tounit.model.
This can be accessed via
unit.var.conversion[t]; this does not describe the full output of theUnitsince that maybe also include fixed generation based on theisonvariable.
ison
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.ison
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.ison
Full implementation and all details: unit/var_ison @ IESopt.jl
Add the variable describing the current “online” state of the
unittounit.model.
The variable can be further parameterized using the
unit.unit_commitmentsetting (“linear”, “binary”, “integer”). It will automatically enforce the constraints \(0 \leq \text{ison} \leq \text{unitcount}\), with \(\text{unitcount}\) describing the number of units that are aggregated in thisunit(set byunit.unit_count). This can be accessed viaunit.var.ison[t].
ramp
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.ramp
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.ramp
Full implementation and all details: unit/var_ramp @ IESopt.jl
Add the variable describing the per-snapshot ramping to
unit.model.
This adds two variables per snapshot to the model (if the respective setting
unit.enable_ramp_uporunit.enable_ramp_downis activated). Both are preconstructed with a fixed lower bound of0. This describes the amount of change in conversion that occurs during the current snapshot. These can be accessed viaunit.var.ramp_up[t]andunit.var.ramp_down[t].
These variables are only used for ramping costs. The limits are enforced directly on the conversion, which means this variable only exists if costs are specified!
startup
How to access this variable?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").var.startup
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").var.startup
Full implementation and all details: unit/var_startup @ IESopt.jl
Add the variable describing the per-snapshot startup to
unit.model.
This adds a variable per snapshot to the model (if the respective setting
unit.unit_commitmentis activated). The variable can be further parameterized using theunit.unit_commitmentsetting (“linear”, “binary”, “integer”). It will automatically enforce the constraints \(0 \leq \text{startup} \leq \text{unitcount}\), with \(\text{unitcount}\) describing the number of units that are aggregated in thisunit(set byunit.unit_count). This describes the startup that happens during the current snapshot and can be accessed viaunit.var.startup.
Constraints
conversion_bounds
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.conversion_bounds
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.conversion_bounds
Full implementation and all details: unit/con_conversion_bounds @ IESopt.jl
Add the constraint defining the
unit’s conversion bounds tounit.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 theonline_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
unitis handled it constructs the following constraints:
if !isnothing(unit.availability)
This effectively results in \(\text{conversion}_t \leq \min(\text{capacity}_{\text{online}, t}, \text{availability}_t)\).
if !isnothing(unit.availability_factor)
If no kind of availability limiting takes place, the following bounds are enforced:
ison
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.ison
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.ison
Full implementation and all details: unit/con_ison @ IESopt.jl
Construct the upper bound for
var_ison, based onunit.unit_count, if it is handled by an externalDecision.
min_onoff_time
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.min_onoff_time
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.min_onoff_time
Full implementation and all details: unit/con_min_onoff_time @ IESopt.jl
Add the constraints modeling min on- or off-time of a
Unittounit.model.
This constructs the constraints
respecting
on_time_beforeandoff_time_before, andis_on_before. See the code for more details.
Aggregated units
This is currently not fully adapted to account for Units with unit_count > 1.
ramp
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.ramp
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.ramp
Full implementation and all details: unit/con_ramp @ IESopt.jl
Add the auxiliary constraint that enables calculation of per snapshot ramping to
unit.model.
Depending on whether ramps are enabled, none, one, or both of the following constraints are constructed:
This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if:
out[5] = 100andout[4] = 50, thenramp_up[5] = 50andramp_down[5] = 0
ramp_up[1] = ramp_down[1] = 0
ramp_limit
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.ramp_limit
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.ramp_limit
Full implementation and all details: unit/con_ramp_limit @ IESopt.jl
Add the constraint describing the ramping limits of this
unittounit.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 (viaunit.ramp_up_limitandunit.ramp_down_limit), resulting in either or both of:
This does not make use of the ramping variable (that is only used for costs - if there are costs).
This calculates the ramping that happens from the PREVIOUS snapshot to this one. That means that if:
out[5] = 100andout[4] = 50, thenramp_up[5] = 50andramp_down[5] = 0
ramp_up[1] = ramp_down[1] = 0
startup
How to access this constraint?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").con.startup
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").con.startup
Full implementation and all details: unit/con_startup @ IESopt.jl
Add the auxiliary constraint that enables calculation of per snapshot startup to
unit.model.
Depending on whether startup handling is enabled, the following constraint is constructed:
This calculates the startup that happens from the PREVIOUS snapshot to this one. That means that if:
ison[5] = 1andison[4] = 0, thenstartup[5] = 1
Objectives
marginal_cost
How to access this objective?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").obj.marginal_cost
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").obj.marginal_cost
Full implementation and all details: unit/obj_marginal_cost @ IESopt.jl
Add the (potential) cost of this
unit’s conversion (unit.marginal_cost) to the global objective function.
ramp_cost
How to access this objective?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").obj.ramp_cost
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").obj.ramp_cost
Full implementation and all details: unit/obj_ramp_cost @ IESopt.jl
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_costandunit.ramp_down_cost):
startup_cost
How to access this objective?
# Using Julia (`IESopt.jl`):
import IESopt
model = IESopt.run(...) # assuming this is your model
IESopt.get_component(model, "your_unit").obj.startup_cost
# Using Python (`iesopt`):
import iesopt
model = iesopt.run(...) # assuming this is your model
model.get_component("your_unit").obj.startup_cost
Full implementation and all details: unit/obj_startup_cost @ IESopt.jl
Add the (potential) cost of this
unit’s startup behaviour (configured byunit.startup_costifunit.unit_commitment != :off).