The model class (symenergy.core.model)

Initialize model

class symenergy.core.model.Model(nworkers='default', curtailment=False, slot_weight=1, constraint_filt='')[source]

Instantiate a model object. Start from here.

Parameters:
  • slot_weight (float) – default time slot weight (hours); this instantiates a singleton parameter to avoid the definition of individual parameter for each slot; it can be overwritten for individual time slots if the weight parameter is provided
  • constraint_filt (str) – see symenergy.core.model.Model.init_constraint_combinations()
  • curtailment (bool or list of Slots, default False) – Allow for curtailment in each time slot (True) or in a selection of time slots. This generates a symenergy.assets.curtailment.Curtailment instance curt, which defines the positive curtailment power variables curt.p for each of the relevant time slots.
  • nworkers (int or False) – number of workers to be used for parallel model setup and solving; passed to the multiprocessing.Pool initializer; defaults to multiprocessing.cpu_count() - 1; if False, no multiprocessing is used

Add model components

Model.add_slot(name, *args, **kwargs)[source]

Add a time slot to the model.

Parameters:
  • name (str) – name of the time slot
  • load (float) – power demand (units MW, default 0)
  • vre (float) – variable renewable power production (units MW, default 0)
  • weight (float) – Optional time slot duration (units hours, default 1)
  • block (str) – time slot block this slot is assigned to (must be one of the keys of the Model.slot_blocks() attribute)
Model.add_slot_block(name, repetitions)[source]

Add a time slot block to the model.

SlotBlock instances can are optional to define more complex model time structures. Slot instances are assigned to SlotBlock instances.

Parameters:
  • name (str) – name of the slot block
  • repetitions (int) – number of repetitions of the block. For example, a block with repetitions=3 and two slots s1 and s2 corresponds to the pattern s1, s2, s1, s2, s1, s2
Model.add_plant(name, *args, **kwargs)[source]

Add a dispatchable power plant to the model.

Parameters:
  • name (str) – name of the power plant
  • vc0 (float) – Optional constant coefficient of the linear variable cost supply curve
  • vc1 (float) – Optional slope of variable cost supply curve
  • fcom (float) – Optional O&M fixed cost; only relevant if cap_ret is True
  • capacity (float) – Optional available installed capacity; this limits the power output. Power production is unconstrained if this argument is not set.
  • cap_ret (bool) – Optional: power capacity can be retired if True

Example:

>>> from symenery.core import model
>>> m = model.Model()
>>> m.add_slot('t0', load=10)
>>> m.add_slot('t1', load=10)
>>> m.add_plant('n', vc0=1, vc1=2)

The cost supply curve is determined by a linear function of the power production with the cost coefficients vc0` and `vc1. For each time slot, it’s expression is given by

>>> m.plants['n'].vc
{Slot `t0`: w_none*(n_p_t0*vc1_n_none + vc0_n_none),
 Slot `t1`: w_none*(n_p_t1*vc1_n_none + vc0_n_none)}

Consequently, the power plant cost component is quadratic:

>>> m.plants['n'].cc
w_none*(n_p_t0**2*vc1_n_none + 2*n_p_t0*vc0_n_none + n_p_t1**2*vc1_n_none + 2*n_p_t1*vc0_n_none)/2
Model.add_storage(name, *args, **kwargs)[source]

Add generic storage capacity to the model.

The class symenergy.assets.storage.Storage is not initialized directly but exclusively through the symenergy.core.model.Model.add_storage() method.

Parameters:
  • name (str) – storage name
  • eff (float) – storage round-trip efficiency
  • slots_map (dict) – optional specification during which time slots storage can charge/discharge; see below
  • slots (dict) – Model slots. This attribute is passed automatically by the symenergy.core.model.Model.add_storage() method.
  • capacity (float) – Optional storage power capacity; this limits the charging and discharging power. Power is unconstrained if this argument is not set.
  • energy_capacity (float) – Optional storage energy capacity; this limits the stored energy content. Energy is unconstrained if this argument is not set.
  • energy_cost (float) – cost of energy content; technical parameter to break solution symmetries; see below
  • charging_to_energy_factor (str) –

    options:

    • 'sqrt' (defaultl): \(\mathrm{e} = \sqrt{\eta} p_\mathrm{chg} - 1/\sqrt{\eta} p_\mathrm{dch}\)
    • 'eta': \(\mathrm{e} = \eta p_\mathrm{chg} - p_\mathrm{dch}\)
    • '1': \(\mathrm{e} = p_\mathrm{chg} - 1/\eta * p_\mathrm{dch}\)
  • The time slot order follows from the order in which they are added to the model instance (symenergy.core.model.Model.add_slot()).

  • The slots_map parameters defines during which time slots storage can charge or discharge. This allows for significant model simplifications. For example, the dictionary

    `{'chg': ['slot_1', 'slot_2'],
      'dch': ['slot_2']}
    

    allows for charging for slots [‘slot_1’, ‘slot_2’] but limits discharging to ‘slot_2’.

  • The energy_cost value defines a specific cost parameter \(\mathrm{ec}\) applied to the storage energy content during all time slots: \(\sum_{t} e_t \mathrm{ec}\). This serves two purposes:

    • If storage operation is not energy-constrained, an infinite number of solutions with identical total cost exists, corresponding to varying levels of arbitrary storage energy contents (but identical changes in energy content). The energy_cost parameter isolates a single minimum cost solution which corresponds to the case where storage is empty during at least one of the time slots.
    • If two or more storage types are included in the model, the temporal distribution of their operation can be ill-defined. In this case, the relative energy cost values allow to express a preference for shorter time frame operation of one of the storage types.

    The energy cost value should be small enough not to interfere with the other system costs. It defaults to 1e-12.

Model.add_curtailment(slots)[source]

Add curtailment to the model. Must specify the time slots.

This method is only used if curtailment is defined for a subset of time slots. Use the model parameter curtailment=True to enable curtailment globally.

Parameters:slots (list) – list of time slot names, e.g. ['day', 'night']

Model simplifications

Methods which allow for simplifications on the model level.

Model.freeze_parameters(exceptions=None)[source]

Switch from variable to numerical value for all model parameters. This automatically updates the definition of all sympy expressions (total cost, lagrange, …)

Parameters:exceptions (list(str)) – names of parameters to be excluded; corresponds to elements of the list Model.parameters(‘name’)

Example

>>> from symenery.core import model
>>> m = model.Model()
>>> m.add_slot('s0', load=2, vre=3)
>>> m.add_plant('nuc', vc0=1, vc1=0.01)
>>> print(m.tc)

nuc_p_s0*w_none*(nuc_p_s0*vc1_nuc_none + 2*vc0_nuc_none)/2

>>> m.freeze_parameters()
>>> print(m.tc)

nuc_p_s0*(0.005*nuc_p_s0 + 1.0)

Here only the power output variable nuc_p_s0 is left in the equation. All other symbols (all parameters) have been substituted with their respective numerical values.

Excluding parameters through the exceptions argument causes a partial substitution:

>>> m.freeze_parameters(exceptions=['vc0_nuc_none'])
>>> print(m.tc)

nuc_p_s0*(0.005*nuc_p_s0 + 1.0*vc0_nuc_none)

Define constraint combinations and solve model

In order to obtain all solution of the model, it is sufficient to call the method symenergy.core.model.Model.generate_solve. This reads the cached solutions from the corresponding pickle file, if applicable. For more fine-grained control over the solution steps, the individual methods listed below can be called.

Model.generate_solve()[source]

Initialize the constraint combinations, generate the problems, and solve. This calls the following methods:

  • Model.init_constraint_combinations()
  • Model.define_problems()
  • Model.solve_all()
  • Model.filter_invalid_solutions()
  • Model.generate_total_costs()
  • Model.cache.write(Model.df_comb)
Model.init_constraint_combinations(constraint_filt=None)[source]

Generates dataframe model.df_comb with constraint combinations.

  1. Obtains relevant constraint combinations from components (see symenergy.core.component.Component._get_constraint_combinations())
  2. Generates table corresponding to the full cross-product of all component constraint combinations.
  3. Filters constraint combinations according to the model.mutually_exclusive class attribute.

This function initilizes the symenergy.df_comb attribute

Parameters:constraint_filt (str) – pandas.DataFrame.query() string to filter the constraint activation columns of the df_comb dataframe. A list of relevant column names of a model object m can be retrieved through m.constraints('col', is_equality_constraint=False)
Model.define_problems()[source]

For each combination of constraints, define

  • the Lagrange functions (new column lagrange in the df_comb table)
  • the endogenous (dependent) variables and multipliers (new column variabs_multips in the df_comb table)
Model.solve_all()[source]
Model.filter_invalid_solutions()[source]

Analyzes the model result expressions to filter invalid rows.

This method modifies and shortens the Model.df_comb table.

  • Identify empty solutions as returned by the linsolve method
  • Remove empty solutions from Model.df_comb. Invalid solutions are kept in the Model.df_comb_invalid dataframe.
  • Analyze and classify remaining solutions with respect to linear dependencies of solutions (symenergy.core.model.Model._get_mask_linear_dependencies()).
  • Fix results with fixable linear dependencies.
Model.generate_total_costs()[source]

Substitute result variable expressions into total costs.

This adds an additional total cost column 'tc' to the symenergy.core.model.Model.df_comb table. The total cost is calculated by substituting the solutions for all variables into the total cost expression Model.tc (for each constraint combination).

The execution is parallelized. The Model.nworkers attribute defines the number of workers for multiprocessing.

Results analysis

Model.print_results(*args, **kwargs)[source]

Print result expressions for all variables and multipliers for a certain constraint combination index.

Parameters are passed to symenergy.core.model.Model.get_results_dict()

Model.get_results_dict(idx, df=None, slct_var_mlt=None, substitute=None, diff=None, diff_then_subs=True)[source]

Get dictionary with {variable_name: result_expression}.

Apply substitutions or derivatives with respect to a parameter.

Parameters:
  • idx (int) – index of the constraint combination for which the results are to be returned
  • df (df) – DataFrame containing the results and the index; defaults to the model’s df_comb table
  • substitute (dict) – substitutions to be performed prior to expression simplification; main use case: setting the energy cost parameter ec of the storage class to zero.
  • slct_var_mlt (list of str) – list of variable or multiplier names; must be a subset of set(m.variables(‘name’)) | set(map(str, m.constraints(‘mlt’)))
  • diff (name or sympy.symbol.Symbol) – parameter name or symbol for differentiation
  • diff_then_subs (bool) – If True, first differentiate expressions, then substitute values; defaults to True
  • input DataFrame must have the following columns (The) –
  • variabs_multips (*) – for which the results are to be printed
  • result (*) – variabs_multips symbols

Other methods

Other important methods which are not directly called but referred to by others.

Model._get_mask_linear_dependencies()[source]

Solutions of problems containing linear dependencies.

In case of linear dependencies SymPy returns solutions containing variables which we are actually solving for. To fix this, we differentiate between two cases:

  1. No dependencies
  2. All corresponding solutions belong to the same component. Overspecification occurs if the variables of the same component depend on each other but are all zero. E.g. charging, discharging, and stored energy in the case of storage. They are set to zero.
  3. Linear dependent variables belonging to different components. This occurs if the model is underspecified, e.g. if it doesn’t matter which component power is used. Then the solution can be discarded without loss of generality. All cases will still be captured by other constraint combinations.
  4. Different components but same component classes. If multiple idling storage plants are present, their mulitpliers show linear dependencies.
Returns:code_lindep – Series with same length as df_comb with linear dependency codes as defined above
Return type:pandas.Series