Skip to content
jsschl edited this page Jun 7, 2019 · 4 revisions

Overview

The simulator Minimalistic-Discrete-Energy-Simulator (DES) is a lightweight framework to run resource objects in lockstep on. It allows for synchronized time progress on arbitrary resource sets and incorporates queries to ease control and post-processing.

Communities are built bottom up as a collection of resource sets/subsets to enable modular and customizable simulation setups. The entire framework resides within a jupyter notebook, offering a template to start modeling right away.

The core simulator consists of 300 lines python incorporating the base class to derive custom resources from. The structure of the framework can be outlined as follows:

+-----------------------------------------------------------------------------+
|      __  ____      _ ___  ________                                          |
|     /  |/  (_)__  (_) _ \/ __/ __/                                          |
|    / /|_/ / / _ \/ / // / _/_\ \                                            |
|   /_/  /_/_/_//_/_/____/___/___/                                            |
+-----------------------------------------------------------------------------+
|    generate test data                                                       |
|          sample_recs            generate rectangular pattern/profile        |
|          sample_sinus           generate waveform pattern/profile           |
|    query                                                                    |
|          get_rss(_r)            access helper to return resource objects    |
|          get_power              access helper to aggregate resource power   |
|          get_power_grp          aggregates the power of a peer/collection   |
|    parameter                                                                |
|          get_param              calculatesht chosen parameter               |
|          get_params             calculatetime+series parameter              |
|    time rollbacks                                                           |
|          jump_back              rollback time/logs to previous state        |
|    general                                                                  |
|          simulation_init        generates data model / init. resources      |
|          simulation_run         start the simulation                        |
|    store and load                                                           |
|          simulation_store       store the simulation results (mdata)        |
|          simulation_load        load the simulation results into the model  |
|    plot                                                                     |
|          plot_cords    plots    resources by their coordinates              |
|          plot_logs    plots     one log per given resource                  |
|          plot_bar    plots      multiple data series (pyplot wrap)          |
|    conversion helpers                                                       |
|          to_kWh                 convert Ws to kWh                           |
|          to_Ws                  convert kWh to Ws                           |
|          arc_to_deg             convert radians to great circle distance    |
|          deg_to_arc             convert the reverse                         |
|          latlon_to_xyz          convert angluar to cartesian                |
|          xyz_to_latlon          convert reverse                             |
|          gen_coords             generate random coordinate batch            |
|          center_point           calculate median center of point cloud      |
+-----------------------------------------------------------------------------+

Install

There are no installation requirements beside a functional jupyter notebook based on python3.

Then start juptyer and eventually open the template or review the examples.

Workflow

The general workflow is to build the community and parametrize the simulation setup.

  1. Write your basic building blocks, the resources, or use/extend the ones available.
  2. Build the community by forming sets.
  3. Setup the simulation by K and kclock.
  4. Call simulation_init to initialize the data model and perform data import, e.g. load profiles.
  5. Run the simulation simulation_run. Results can then be stored or the JPN exported to html

The details are part of the following sections.

Notation

The notation helps with precise reasoning. The main notation symbol is followed by non-ordered subscripts (if at all). There is no prefix or superscript emphasizing readability, especially important in plain text, e.g. source code.

script explanation
K := total time steps of the simulation
k := the current time step
kclock := time increment per step k
r := resource index/identifier
R := total number of resources
j := entity index inside a collection
J := size of a collection

The following examples use common abbreviations:

examples explanation
k2 simulation time after two steps: k0 to k1 to k2
K · kclock total simulated time in ctime
P_k_res residual power at any k ktime
P_k0_res residual power at 0 ktime
P_K_res, P_res residual power for K kspan, (can be omitted)
P_j_res residual power of any peer j
P_J_res residual power all peers summed
P_k10,j0,load load power of peer j0 at 10 ktime
P_j0,k1,pv photovoltaic power of peer j0 at 1 ktime
P_j0:9,res total residual power of peers [0;8]
P_r0,k1 power of resource r0 at 1 ktime
P_R total power of all devices
E_k10:20 energy sum over 10 kspan from k [10;19]

Sign convention

The sign convention for flow is uniform throughout the system. The default units are watt, seconds or wattseconds (if not stated otherwise).

sign_conv

The figure indicates the sign for the two flow directions and stays the same regardless of external or internal flow. Energy flow into the system is counted positive and energy flow reducing the energy amount is counted negative. Loss is always negative, since it reduces the system’s energy.

Time model

The simulation time is discrete and progresses in K steps at a kclock step size, which multiplied results in K · kclock simulated time. The integer counter k ∈ [0; K − 1] has the current time and is used in formulas to indicate the time at k ktime. The float kclock stores the seconds (or any other unit m/h/d) to connect the simulation time to clock time. Increasing it reduces the time resolution, which is described in the figure.

time_line

The values K and kclock are to be set before calling simulation_init.

Resource models

The resources form the basic building block of the community and can be aggregated into sets to query for common parameters, either after or during simulation control.

sets

The magg aggregates the resources into collections with j being the positional index of the subset. In this case there are two sets (yellow and red). While the yellow set has only one subset, the red set has two subsets, namely j0 and j1 of this respective set, eg households hh.

Available models

There are currently 4 resource types available, sufficient to build photovoltaic households and their battery system.

  • TimeSeries
  • Control
  • Battery
  • Inverter

The functionality is annotated in the template (no code duplication). The constructors are called as follwed:

TimeSeries(r=1, ct='ld', cord1=10, cord2=11) 
         # |        |        |        |y-coordinate
         # |        |        |x-coordinate
         # |        |resource category this object specializes in                        
         # |resource identifier, arbitrary int (but unique across R)            

Control(r=0, ct='ctrl', cord1=0, cord2=510)
      # |        |        |        |y-coordinate
      # |        |        |x-coordinate
      # |        |resource category this time control alias 'ctrl'            
      # |resource identifier, arbitrary int (but unique across R)    

Battery(r=2, ct='es', cord1=x, cord2=y,\
       #         |resource category of type battery
           pivot_soc_dch=0.1,\
       #    |if the SOC undercuts 10% of maximum capacity, the battery refuses discharge.
       #    |   this is helpful for e.g. SOC reservation 

           E_soc=piv*E_soc_max, E_soc_max=E_soc_max*(1-piv),\
       #    |initial SOC         |capacity maximum

           P_ch_const=ch, P_dch_const=dch, loss=tmp, loss_pct=False, eff_rt=0.95)
       #    |                |                |      |              |round-trip efficiency
       #    |                |                |      |const (False) or pct. idle losses
       #    |                |                |idle loss in percent or constant**
       #    |                |maximum discharge power
       #    |maximum charging power

Inverter(r=3, ct='inv', cord1=50, cord2=60, P_nom=3500, CC=popt, nom_cap=2, P_idle=0)
      #  |    |           |          |        |           |      |limit inflow at 2*P_nom 
      #  |    |           |x         |y       |           |efficiency curve parameters***
      #  |    |                               |optional nominal inverter efficiency     
      #  |    |                               |if enforced the inverter throws if higher 
      #  |    |category 'inv' indicating inverter             
      #  |unique resource index              

** The constructor signature has the doc. The battery supports constant discharge interpreting tmp as [W] on loss_pct=False. When set to loss_pct=True the value tmp is the percentage of capacity lost over the simulated time. The battery supports idle losses on low charge/discharge powers, added when undercutting the hardcoded value pivot_P_idle in [W].

*** The three parameters can be curve-fit directly from the efficiency values of your inverter by. The efficiency curve looks like depicted in the figure below. The inverter supports the inverse calculation to get the inflow for the desired outflow (non-trivial since non-linear curve), if by_out flag is set, when setting the power flow with set_P. The constant self-consumption is set by P_idle [W].

def func(x, a, b, c):
    return a*x / (b-x) + c*x
xdata = np.array([0, 10, 100, 1000, 2000, 5000, 1e4])
ydata = np.array([0, 0.1, 0.45, 0.97, 0.94, 0.92, 0.8])
popt, pcov = curve_fit(func, xdata, ydata) 
#results in: popt = np.array([-1.09363625e+00, -1.34235172e+02, -2.83214366e-05])
inv_c

Building a new model

Inherit the base class Resource and add the custom functionality to the step method (or call methods from there). If the resource is prosuming power set the log noting the power in/outflow by the index log_index_P, according tot he position in log_titles. If not, set it to None.

To request a log, add your title to the log_titles tuple and call it by index, e.g. self.view[1,k] for the second log in the tupel accessing the value at k to read/write.

Each time step, the current simulation time k is handed to the resource to update the local state to k+1, corresponding to a time progress of value kclock clock time. Beside the two class attributes to allocate logs and to specify the log index associated with eventual power flow, it must overwrite the step function and call the super constructor. Overwriting init(), end() and jump() is optional. Minimum working example:

class Custom(Resource):
    log_titles = (    #log allocation
        'P_pros [W]',)#0:=prosumage
    log_index_P = 0   #index for power
    def init(self):   #opt.
        """Called at simulation start"""
    def end(self, k): #opt.
        """Called at simulation end"""
    def jump(self, k):#opt.
        """Restore state of k ktime"""
        self.view[:,k:] = 0#rollback log
        self.set_k(k)
    def step(self,k):
        """Custom functionality here"""

Examples

The following examples are common scenarios and solved in the template.

Scenario 1

The energy residual of one household is logged over one day. The household has only one resource, the load profile. It shows how to query within the control and ensure energy conservation. The resulting log is visualized. Layout:

	+----------+
	|   GRID   |
	|    ^     |
	|    |     |
	|    v     |
	|   LD     |
	+----------+

Scenario 2

One household self-charges his battery to achieve maximum self-consumption on its PV system. It also examples a rollback from k800 to k400. Layout:

	+--------------------+
	|             GRID   |
	|              |     |
	|PV+-->INV+--->x<->ES|
	|              v     |
	|             LD     |
	+--------------------+

Scenario3:

Battery array discharges always the max soc battery to cover the exact load (reverse inverter calculation). When the array depletes the DCGRID starts to feed in:

	+------------------------+
	|                        |
	|   +----------+INV+--+  |
	| DCGRID  ES|         |  |
	|         ES|         +  |
	|         ES+         LD |
	|                        |
	+------------------------+

Scenario 4

Two photovoltaic-installed households have read the benefits of complementing residual energy profiles. They agree to share the photovoltaic output and hope to be self-sufficient on just one battery shared for collective use over the grid. They try to balance out supply and demand for maximum SSR and SCR. Layout:

	+-----------------------------------------+
	|                                         |
	|                 +----------------+GRID  |
	|                 |                |      |
	| PV+-->INV+----+-+  PV+-->INV+--->x<->ES |
	|               v                  v      |
	|              LD                 LD      |
	+-----------------------------------------+

Debug

The IPython.core.debugger supports the standard interactive python debugger commands (breakpoints etc.). Place set_trace() in the executed code and the simulator drops to shell.

Set the VERBOSE flag and the JPN prints resource internals depending on the level.

VERBOSE = 0 # control verbose level of simulation_load/store/init/run()
#     0:= silent
#     1:= stringify each resource each step
#     2:= 1 with repr() instead of str()

Case Study

The input data of the case study is available on request.

License

MIT License

Copyright (c) 2019
	-----BEGIN PUBLIC KEY-----
	MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtAgeP1hhQHBHPICTc2ho
	vJFNXA2qtf0HjuXXV7i+imaN7RI4hUPQMo4nNCYjeiD3vzAdBTtWRQrI2ONmiFTk
	ntAuD0Mg03q+mj/88aawnZbtXBF4QM5sYClInIuW23uhSq17SseWCXtEhmHtz155
	4LllN4FBC11/R0shrAvFH4dAn2sM8PBg+FGze2wUaJbEl2rLe+qoek10krbSrpUP
	VXCsyVyicR1IaOhldH4I8zpvB6CSPzOkzhQhbxRhxvKwN7kaVlzVGg2u3ccgffHP
	dldIk2D14rz0hJ0Ix1qheAQW/+2haBP/lbwW2iLtiyC47sVeDbCpd66Zi9lKDUe4
	nwIDAQAB
	-----END PUBLIC KEY-----

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.