Background
This section gives a brief overview of the concept behind COMANDO, the problem formulations that can be addressed with it, as well as its usage.
Concept
The aim of COMANDO is to enable object-oriented modeling of energy systems and their constituting components for the purpose of creating optimization problems related to the system’s design and operation. Unlike other energy system modeling frameworks, COMANDO does not impose restrictions to maintain the resulting optimization problems in a particular class such as linear programming (LP) or mixed-integer linear programming (MILP). Instead, component and system models may contain a wide range of nonlinear expressions, including nonconvex and nonsmooth functions. Furthermore ordinary differential equations describing system dynamics can be included directly, without manual discretization. In this way COMANDO allows users to create component and system models in a natural fashion, without having to obscure them with optimization-specific implementation details.
By specifying the connections between components, different component models can be combined into a system model. Based on a finished system model, different optimization problems can be created by specifying objective functions, the operational scenarios and their time-structure to be considered, as well as associated parameter data.
To bring the resulting problem formulations into a form that solvers understand, certain reformulations may be necessary. COMANDO provides routines for substitution and reformulation of expressions, as well as for automatic approximation via linearization and time-discretization. Once an acceptable problem formulation is created, it can be translated to different algebraic modeling languages (AMLs) or be passed directly to an appropriate solver for solution.
The details of the different steps of this process are explained in the following.
Usage
The usage of COMANDO can be split into three phases
Modeling phase
Component model creation
System model creation
Problem formulation phase
problem generation
objective selection
time-structure selection
scenario-structure selection
providing data
problem reformulation
time discretization
linearization
…
Problem solution phase
via a solver interface (e.g., GUROBI, BARON)
via an algebraic modeling language (AML) interface (e.g., GAMS, PYOMO)
via custom algorithm
initialization
decomposition
…
Modeling phase
In the modeling phase the system behavior is specified in terms of the component behavior and system structure.
Components
In COMANDO a component is the basic building block of an energy system. It represents a model of a generic real-world component, specified via a collection of mathematical expressions that are given in a symbolic form.
Take for example the following simple description of some generic component C:
It contains different symbols that represent different design and operational quantities of C and equations that correlate these symbols with each other. Here, we may assume that \(C_{\mathrm{out,max}}\) is a design quantity, while the symbols \(C_{\mathrm{out}}\), \(C_{\mathrm{in}}\), \(C_{\eta}\), and \(C_{\mathrm{pl}}\) are operational quantities and \(C_0\), \(C_1\), and \(C_2\) are some numerical coefficients.
We can create a COMANDO model of this component by deciding which role the different symbols play for some design or operation process. In COMANDO a distinction is made between quantities that are given as input data and those that are to be determined by the use of the model, i.e., by formulating some optimization problem incorporating the model and solving that problem.
Symbols that act as placeholder for input data can be modeled as a comando.Parameter
, while those that are to be determined can be modeled as a comando.Variable
or a comando.VariableVector
, depending on whether they represent design- or an operational-related quantity.
Note
Objects of type Variable
/ VariableVector
always represent scalar / vector values while those of type Parameter
may represent both, depending on the data that is assigned to them.
In the context of design and operation, design quantities are always modeled by symbols or composed expressions that represent scalar values, while operational quantities are always modeled by symbolss or composed expressions that represent vector values, with each entry representing the value of the corresponding quantity for a certain operational scenario and timestep.
We may for example model the coefficients \(C_0\), \(C_1\), and \(C_2\) as objects C_0, C_1, and C_2 of type Parameter
, \(C_{\mathrm{out,max}}\) as a Variable
C_out_max and \(C_{\mathrm{out}}\), \(C_{\mathrm{in}}\) as objects C_out, C_in of type VariableVector
.
For the definition of component COMANDO provides the Component
class.
This class defines methods to create and add the corresponding symbols to Component
instances:
# Define a new type of component
class C(comando.Component):
"""A generic component."""
def __init__(self, label):
"""Initialize the component."""
super().__init__(label) # Initialize the parent class (Component)
# Create parameters
C_coeffs = [self.make_parameter(i) for i in range(3)]
# Create design and operational variables
C_out_max = self.make_design_variable('out_max')
C_out = self.make_operational_variable('out')
C_in = self.make_operational_variable('in')
# Expressions formed with the defined symbols
C_pl = self.add_expression(C_out / C_out_max, 'part_load')
C_eta = sum(C_i * C_pl ** i for i, C_i in enumerate(C_coeffs))
# Add constraints
self.add_eq_constraint(C_out, C_in * C_eta, 'conversion')
self.add_le_constraint(C_out, C_out_max, 'output_limit')
# Add connectors
self.add_input('INPUT', C_in)
self.add_output('OUTPUT', C_out)
# Create an instance of our new class
c = comando.Component('C')
c_pl = c.get_expression('part_load') # Access previously defined expression
Note how instead of introducing additional VariableVector
instances for \(C_{\eta}\) and \(C_{\mathrm{pl}}\), we directly used their definitions and create expressions for these quantities using overloaded Python functions!
The expression for C_pl is considered interesting and is therefore stored in C under the name ‘part_load’.
At a later point, this expression can be accessed via the Component.get_expression()
method.
We may have similarly defined C_out as an expression, but instead we decided to make it an operational variable and introduce the correlation between input and output as an equality constraint. Similarly, we introduced an inequality constraint specifying that the component’s output must be smaller than it’s maximum value.
It is also possible to declare operational variables to be ‘states’, i.e., quantities whose time-derivative is given by some algebraic expression. This allows the consideration of dynamic effects.
Todo
Examples for Component.declare_state()
and Component.make_state()
Another thing to notice are the ‘INPUT’ and ‘OUTPUT’ connectors that can be used to connect the component to others within a comando.System
model.
Components may also assign individual algebraic expressions to connectors, allowing them to be interfaced with other components.
Connectors may be specified as inputs, outputs or bidirectional connectors.
The former two restrict the value of the corresponding expression to be nonnegative and nonpositive, respectively, while the latter does not impose additional restrictions.
Systems
Systems are modeled as collections of interconnected components, i.e., a system
model consists of a set of components and the specification of how their
connectors are connected.
For this purpose COMANDO provides the comando.System
class.
As the System
class inherits from the Component
class, systems can define additional expressions, constraints and connectors and be incorporated as subsystems within other systems.
Todo
Example for system creation
Problem formulation phase
Given a system model, COMANDO can currently be used to create a comando.Problem
object, representing a mathematical optimization problem (OP) of the form:
Where \(\dv\) and \(\ov\) are the vectors of design- and operation-variables, respectively.
Problem generation
\(F_I\) and \(F_{II}\) are user-specified scalar and indexed expressions corresponding to one-time and momentary costs, \(\mathcal{T}_s\) are time horizons for scenarios \(s\) in from the set \(\mathcal{S}\), with corresponding probabilities \(w_s\).
The constraints for the OP are automatically generated from the system model, i.e., all scalar relational expressions are taken as contraints and all indexed relational expressions are taken as constraints, parametrized by t, and s.
The dependence of the objective and constraint functions on time and scenario can be expressed in terms of the parameter values, which are user input. This data can currently be given only in discrete form, i.e., for a discrete time and scenario. The time-steps at which data is available
If any states were defined in the model the corresponding differential
equations are currently discretized by default.
An exception is the use of the pyomo_dae
interface; here the time-continuous representation is passed and discretization via collocation can be performed.
Data for the parameter values and initial guesses for the variable values can be provided based on the user-chosen sets T and S.
Note
The time- and scenario structure is only specified during Problem
creation and is therefor not available during the modeling phase!
While this may be unintuitive at first, it allows for a clean separation between component and system behavior on the one hand, and problem-specific imlementation details on the other.
An important consequence of this is, that certain aspects of component behavior such as, e.g., ramping constraints must either be defined in a more general way (e.g., via limiting the derivative of the ramped quantity), or must be deferred and carried out after Problem
creation (e.g. via appropriate callbacks).
Todo
Example for Problem
creation
Todo
Examples for creating problem-specific constraints such as:
Constraints that restrict cumulative quantities (time-integrals of quantities)
Constraints that only consider a certain time range
Constraints explicitly referencing quantities corresponding to multiple time-points and/or scenarios
Problem reformulation
It is possible to use manual or automated reformulations of the original problem formulation.
Todo
Example reformulations, linearization
module and default discretization scheme in the interfaces.
Problem solution
Given a Problem, the user can chose to pass it directly to a solver capable of handling the corresponding problem type, transform the COMANDO Problem to a representation in an AML (currently Pyomo or GAMS), or work directly with the COMANDO representation in a custom algorithm to preprocess or solve the Problem. Currently available interfaces as well as their installarion requirements are described in Interfaces to solvers and algebraic modeling languages (AMLs).
If a solution is obtained, it is loaded back into the COMANDO Problem after the solver terminates.