"""Synthetic one-area frequency response; the RK4 kernel is extracted from the original Lab. 50 Hz, D=1, zero initial deviation and governor output, deficit step at 2 s. No network, voltage, saturation, deadband, secondary control or protection model. """ import math import time from frequency_kernel import frequency_values def run(name, p): if name != 'frequency' or set(p) != {'inertia', 'governor', 'deficit', 'droop', 'governor_tau'}: raise ValueError('Unknown experiment or parameters') for key, low, high in [('inertia', .5, 10), ('deficit', 0, .1), ('droop', .02, .1), ('governor_tau', .1, 2)]: value = p[key] if type(value) not in (int, float) or not math.isfinite(value) or not low <= value <= high: raise ValueError('Parameter out of range: ' + key) if type(p['governor']) is not bool: raise ValueError('Governor must be boolean') start = time.perf_counter() t, values = frequency_values(p['inertia'], p['governor'], p['deficit'], p['droop'], p['governor_tau']) equilibrium = 50 * (1 - p['deficit'] / (1 + 1 / p['droop'] if p['governor'] else 1)) return dict( metrics=[dict(id='minimum_hz', value=float(min(values)), unit='Hz'), dict(id='end_hz', value=float(values[-1]), unit='Hz'), dict(id='initial_rocof', value=-50 * p['deficit'] / (2 * p['inertia']), unit='Hz/s'), dict(id='equilibrium_hz', value=equilibrium, unit='Hz')], series=[dict(id='grid_frequency', x=t.tolist(), y=values.tolist(), x_unit='s', y_unit='Hz'), dict(id='nominal_frequency', x=[0, 30], y=[50, 50], x_unit='s', y_unit='Hz')], stdout='', provenance=dict(data_type='synthetic', compute_ms=(time.perf_counter()-start)*1000)) if __name__ == '__main__': import argparse import json parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--parameters', default='{"inertia":2,"governor":true,"deficit":0.05,"droop":0.05,"governor_tau":0.5}') args = parser.parse_args() print(json.dumps(run('frequency', json.loads(args.parameters)), allow_nan=False))