e2nIEE / pandapower
1
# -*- coding: utf-8 -*-
2

3
# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics
4
# and Energy System Technology (IEE), Kassel. All rights reserved.
5 1
import tempfile
6 1
from collections.abc import Iterable
7

8 1
import pandapower as pp
9 1
from pandapower import LoadflowNotConverged, OPFNotConverged
10 1
from pandapower.control.run_control import ControllerNotConverged, prepare_run_ctrl, \
11
    run_control, NetCalculationNotConverged
12 1
from pandapower.control.util.diagnostic import control_diagnostic
13 1
from pandapower.timeseries.output_writer import OutputWriter
14

15 1
try:
16 1
    import pplog
17 1
except ImportError:
18 1
    import logging as pplog
19

20 1
logger = pplog.getLogger(__name__)
21 1
logger.setLevel(level=pplog.WARNING)
22

23

24 1
def init_default_outputwriter(net, time_steps, **kwargs):
25
    """
26
    Initializes the output writer. If output_writer is None, default output_writer is created
27

28
    INPUT:
29
        **net** - The pandapower format network
30

31
        **time_steps** (list) - time steps to be calculated
32

33
    """
34 1
    output_writer = kwargs.get("output_writer", None)
35 1
    if output_writer is not None:
36
        # write the output_writer to net
37 1
        logger.warning("deprecated: output_writer should not be given to run_timeseries(). "
38
                       "This overwrites the stored one in net.output_writer.")
39 1
        net.output_writer.iat[0, 0] = output_writer
40 1
    if "output_writer" not in net or net.output_writer.iat[0, 0] is None:
41
        # create a default output writer for this net
42 1
        ow = OutputWriter(net, time_steps, output_path=tempfile.gettempdir())
43 1
        logger.info("No output writer specified. Using default:")
44 1
        logger.info(ow)
45

46

47 1
def init_output_writer(net, time_steps):
48
    # init output writer before time series calculation
49 1
    output_writer = net.output_writer.iat[0, 0]
50 1
    output_writer.time_steps = time_steps
51 1
    output_writer.init_all(net)
52

53

54 1
def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=100, fill='█'):
55
    """
56
    Call in a loop to create terminal progress bar.
57
    the code is mentioned in : https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
58
    """
59 1
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
60 1
    filled_length = int(length * iteration // total)
61 1
    bar = fill * filled_length + '-' * (length - filled_length)
62
    # logger.info('\r%s |%s| %s%% %s' % (prefix, bar, percent, suffix))
63 1
    print('\r%s |%s| %s%% %s' % (prefix, bar, percent, suffix), end="")
64
    # Print New Line on Complete
65 1
    if iteration == total:
66 1
        print("\n")
67

68

69 1
def controller_not_converged(time_step, ts_variables):
70 1
    logger.error('ControllerNotConverged at time step %s' % time_step)
71 1
    if not ts_variables["continue_on_divergence"]:
72 1
        raise ControllerNotConverged
73

74

75 1
def pf_not_converged(time_step, ts_variables):
76 0
    logger.error('CalculationNotConverged at time step %s' % time_step)
77 0
    if not ts_variables["continue_on_divergence"]:
78 0
        raise ts_variables['errors'][0]
79

80

81 1
def control_time_step(controller_order, time_step):
82 1
    for levelorder in controller_order:
83 1
        for ctrl, net in levelorder:
84 1
            ctrl.time_step(net, time_step)
85

86

87 1
def output_writer_routine(net, time_step, pf_converged, ctrl_converged, recycle_options):
88 1
    output_writer = net["output_writer"].iat[0, 0]
89
    # update time step for output writer
90 1
    output_writer.time_step = time_step
91
    # save
92 1
    output_writer.save_results(net, time_step, pf_converged=pf_converged, ctrl_converged=ctrl_converged,
93
                               recycle_options=recycle_options)
94

95

96 1
def _call_output_writer(net, time_step, pf_converged, ctrl_converged, ts_variables):
97 1
    output_writer_routine(net, time_step, pf_converged, ctrl_converged, ts_variables['recycle_options'])
98

99

100 1
def run_time_step(net, time_step, ts_variables, run_control_fct=run_control, output_writer_fct=_call_output_writer,
101
                  **kwargs):
102
    """
103
    Time Series step function
104
    Is called to run the PANDAPOWER AC power flows with the timeseries module
105

106
    INPUT:
107
        **net** - The pandapower format network
108

109
        **time_step** (int) - time_step to be calculated
110

111
        **ts_variables** (dict) - contains settings for controller and time series simulation. See init_time_series()
112
    """
113 1
    ctrl_converged = True
114 1
    pf_converged = True
115
    # run time step function for each controller
116

117 1
    control_time_step(ts_variables['controller_order'], time_step)
118

119 1
    try:
120
        # calls controller init, control steps and run function (runpp usually is called in here)
121 1
        run_control_fct(net, run_control=False, ctrl_variables=ts_variables, **kwargs)
122 1
    except ControllerNotConverged:
123 1
        ctrl_converged = False
124
        # If controller did not converge do some stuff
125 1
        controller_not_converged(time_step, ts_variables)
126 0
    except ts_variables['errors']:
127
        # If power flow did not converge simulation aborts or continues if continue_on_divergence is True
128 0
        pf_converged = False
129 0
        pf_not_converged(time_step, ts_variables)
130

131 1
    output_writer_fct(net, time_step, pf_converged, ctrl_converged, ts_variables)
132

133

134 1
def _check_controller_recyclability(net):
135
    # if a parameter is set to True here, it will be recalculated during the time series simulation
136 1
    recycle = dict(trafo=False, gen=False, bus_pq=False)
137 1
    if "controller" not in net:
138
        # everything can be recycled since no controller is in net. But the time series simulation makes no sense
139
        # then anyway...
140 0
        return recycle
141

142 1
    for idx in net.controller.index:
143
        # todo: write to controller data frame recycle column instead of using self.recycle of controller instance
144 1
        ctrl_recycle = net.controller.at[idx, "recycle"]
145 1
        if not isinstance(ctrl_recycle, dict):
146
            # if one controller has a wrong recycle configuration it is deactived
147 1
            recycle = False
148 1
            break
149
        # else check which recycle parameter are set to True
150 1
        for rp in ["trafo", "bus_pq", "gen"]:
151 1
            recycle[rp] = recycle[rp] or ctrl_recycle[rp]
152

153 1
    return recycle
154

155

156 1
def _check_output_writer_recyclability(net, recycle):
157 1
    if "output_writer" not in net:
158 0
        raise ValueError("OutputWriter not defined")
159 1
    ow = net.output_writer.at[0, "object"]
160
    # results which are read with a faster batch function after the time series simulation
161 1
    recycle["batch_read"] = list()
162 1
    recycle["only_v_results"] = False
163 1
    new_log_variables = list()
164

165 1
    for output in ow.log_variables:
166 1
        table, variable = output[0], output[1]
167 1
        if table not in ["res_bus", "res_line", "res_trafo", "res_trafo3w"] or recycle["trafo"] or len(output) > 2:
168
            # no fast read of outputs possible if other elements are required as these or tap changer is active
169 1
            recycle["only_v_results"] = False
170 1
            recycle["batch_read"] = False
171 1
            return recycle
172
        else:
173
            # fast read is possible
174 1
            if variable in ["vm_pu", "va_degree"]:
175 1
                new_log_variables.append(('ppc_bus', 'vm'))
176 1
                new_log_variables.append(('ppc_bus', 'va'))
177

178 1
            recycle["only_v_results"] = True
179 1
            recycle["batch_read"].append((table, variable))
180

181 1
    ow.log_variables = new_log_variables
182 1
    ow.log_variable('ppc_bus', 'vm')
183 1
    ow.log_variable('ppc_bus', 'va')
184 1
    return recycle
185

186

187 1
def get_recycle_settings(net, **kwargs):
188
    """
189
    checks if "run" is specified in kwargs and calls this function in time series loop.
190
    if "recycle" is in kwargs we use the TimeSeriesRunpp class (not implemented yet)
191

192
    INPUT:
193
        **net** - The pandapower format network
194

195
    RETURN:
196
        **recycle** - a dict with recycle options to be used by runpp
197
    """
198

199 1
    recycle = kwargs.get("recycle", None)
200 1
    if recycle is not False:
201
        # check if every controller can be recycled and what can be recycled
202 1
        recycle = _check_controller_recyclability(net)
203
        # if still recycle is not None, also check for fast output_writer features
204 1
        if recycle is not False:
205 1
            recycle = _check_output_writer_recyclability(net, recycle)
206

207 1
    return recycle
208

209

210 1
def init_time_steps(net, time_steps, **kwargs):
211
    # initializes time steps if as a range
212 1
    if not isinstance(time_steps, Iterable):
213 1
        if isinstance(time_steps, tuple):
214 0
            time_steps = range(time_steps[0], time_steps[1])
215 1
        elif time_steps is None and ("start_step" in kwargs and "stop_step" in kwargs):
216 0
            logger.warning("start_step and stop_step are depricated. "
217
                           "Please use a tuple like time_steps = (start_step, stop_step) instead or a list")
218 0
            time_steps = range(kwargs["start_step"], kwargs["stop_step"] + 1)
219
        else:
220 1
            logger.warning("No time steps to calculate are specified. "
221
                           "I'll check the datasource of the first controller for avaiable time steps")
222 1
            ds = net.controller.object.at[0].data_source
223 1
            if ds is None:
224 0
                raise UserWarning("No time steps are specified and the first controller doesn't have a data source"
225
                                  "the time steps could be retrieved from")
226
            else:
227 1
                max_timestep = ds.get_time_steps_len()
228 1
            time_steps = range(max_timestep)
229 1
    return time_steps
230

231

232 1
def init_time_series(net, time_steps, continue_on_divergence=False, verbose=True,
233
                     **kwargs):
234
    """
235
    inits the time series calculation
236
    creates the dict ts_variables, which includes necessary variables for the time series / control function
237

238
    INPUT:
239
        **net** - The pandapower format network
240

241
        **time_steps** (list or tuple, None) - time_steps to calculate as list or tuple (start, stop)
242
        if None, all time steps from provided data source are simulated
243

244
    OPTIONAL:
245

246
        **continue_on_divergence** (bool, False) - If True time series calculation continues in case of errors.
247

248
        **verbose** (bool, True) - prints progress bar or logger debug messages
249
    """
250

251 1
    time_steps = init_time_steps(net, time_steps, **kwargs)
252

253 1
    init_default_outputwriter(net, time_steps, **kwargs)
254
    # get run function
255 1
    run = kwargs.pop("run", pp.runpp)
256 1
    recycle_options = None
257 1
    if hasattr(run, "__name__") and run.__name__ == "runpp":
258
        # use faster runpp options if possible
259 1
        recycle_options = get_recycle_settings(net, **kwargs)
260

261 1
    init_output_writer(net, time_steps)
262
    # as base take everything considered when preparing run_control
263 1
    ts_variables = prepare_run_ctrl(net, None, **kwargs)
264
    # run function to be called in run_control - default is pp.runpp, but can be runopf or whatever you like
265 1
    ts_variables["run"] = run
266
    # recycle options, which define what can be recycled
267 1
    ts_variables["recycle_options"] = recycle_options
268
    # time steps to be calculated (list or range)
269 1
    ts_variables["time_steps"] = time_steps
270
    # If True, a diverged run is ignored and the next step is calculated
271 1
    ts_variables["continue_on_divergence"] = continue_on_divergence
272
    # print settings
273 1
    ts_variables["verbose"] = verbose
274
    # errors to be considered as exception
275 1
    ts_variables["errors"] = (LoadflowNotConverged, OPFNotConverged, NetCalculationNotConverged)
276

277 1
    if logger.level != 10 and verbose:
278
        # simple progress bar
279 1
        print_progress_bar(0, len(time_steps), prefix='Progress:', suffix='Complete', length=50)
280

281 1
    return ts_variables
282

283

284 1
def cleanup(ts_variables):
285 1
    if isinstance(ts_variables["recycle_options"], dict):
286
        # Todo: delete internal variables and dumped results which are not needed
287 1
        pass
288

289

290 1
def print_progress(i, time_step, time_steps, verbose, **kwargs):
291
    # simple status print in each time step.
292 1
    if logger.level != 10 and verbose:
293 1
        len_timesteps = len(time_steps)
294 1
        print_progress_bar(i + 1, len_timesteps, prefix='Progress:', suffix='Complete', length=50)
295

296
    # print debug info
297 1
    if logger.level == pplog.DEBUG and verbose:
298 0
        logger.debug("run time step %i" % time_step)
299

300
    # call a custom progress function
301 1
    if "progress_function" in kwargs:
302 0
        func = kwargs["progress_function"]
303 0
        func(i, time_step, time_steps, **kwargs)
304

305

306 1
def run_loop(net, ts_variables, run_control_fct=run_control, output_writer_fct=_call_output_writer, **kwargs):
307
    """
308
    runs the time series loop which calls pp.runpp (or another run function) in each iteration
309

310
    Parameters
311
    ----------
312
    net - pandapower net
313
    ts_variables - settings for time series
314

315
    """
316 1
    for i, time_step in enumerate(ts_variables["time_steps"]):
317 1
        print_progress(i, time_step, ts_variables["time_steps"], ts_variables["verbose"], **kwargs)
318 1
        run_time_step(net, time_step, ts_variables, run_control_fct, output_writer_fct, **kwargs)
319

320

321 1
def run_timeseries(net, time_steps=None, continue_on_divergence=False, verbose=True, **kwargs):
322
    """
323
    Time Series main function
324

325
    Runs multiple PANDAPOWER AC power flows based on time series which are stored in a **DataSource** inside
326
    **Controllers**. Optionally other functions than the pp power flow can be called by setting the run function in kwargs
327

328
    INPUT:
329
        **net** - The pandapower format network
330

331
    OPTIONAL:
332
        **time_steps** (list or tuple, None) - time_steps to calculate as list or tuple (start, stop)
333
        if None, all time steps from provided data source are simulated
334

335
        **continue_on_divergence** (bool, False) - If True time series calculation continues in case of errors.
336

337
        **verbose** (bool, True) - prints progress bar or if logger.level == Debug it prints debug messages
338

339
        **kwargs** - Keyword arguments for run_control and runpp. If "run" is in kwargs the default call to runpp()
340
        is replaced by the function kwargs["run"]
341
    """
342

343 1
    ts_variables = init_time_series(net, time_steps, continue_on_divergence, verbose, **kwargs)
344

345 1
    control_diagnostic(net)
346 1
    run_loop(net, ts_variables, **kwargs)
347

348
    # cleanup functions after the last time step was calculated
349 1
    cleanup(ts_variables)

Read our documentation on viewing source code .

Loading