Commit 19cd9933 authored by Carlos A. Iglesias's avatar Carlos A. Iglesias
Browse files

Movidos ficheros modificados a mosak/securegrid-model

parent 94a0ff5e
import mosaik_api
import random
meta = {
'models': {
'Attack': {
'public': True,
'params': ['target_attr'],
'attrs': ['P_out_val'],
},
},
}
attackPercentageValue = 0
attackTime = 5
class Attack(mosaik_api.Simulator):
def __init__(self):
super().__init__(meta)
self.units = {}
def init(self, sid, step_size=15*60):
self.sid = sid
self.step_size = step_size
return self.meta
def create(self, num, model, **model_params):
n_units = len(self.units)
entities = []
for i in range(n_units, n_units + num):
eid = 'Attack-%d' % i
self.units[eid] = model_params
entities.append({'eid': eid, 'type': model })
return entities
def step(self, time, inputs):
commands = {}
progress = yield self.mosaik.get_progress()
if progress >attackTime:
for eid, attrs in inputs.items():
# measure = 0
for attr, vals in attrs.items():
if attr == 'P_out_val':
for src_id, val in vals.items():
target_id = src_id
values = val
if eid not in commands:
commands[eid] = {}
target_attr = self.units[eid]['target_attr']
if target_id not in commands[eid]:
commands[eid][target_id] = {}
commands[eid][target_id][target_attr] = attackPercentageValue
# print("COMMANDS", commands)
yield self.mosaik.set_data(commands)
return time + self.step_size
def main(self):
return mosaik_api.start_simulation(Attack(), 'example attack')
if __name__ == '__main__':
main()
import logging
import mosaik_api
import model_house
import random
logger = logging.getLogger('householdsim')
meta = {
'models': {
'ResidentialLoads': {
'public': True,
'params': [
'sim_start', # The start time for the simulation:
# 'YYYY-MM-DD HH:ss'
'profile_file', # Name of file with household data
'grid_name', # Name of the grid to load
],
'attrs': [],
},
'House': {
'public': False,
'params': [],
'attrs': [
'P_out', # Active power [W]
'num', # House number starting at 1
'node_id', # ID of node the house has to be connected to
'num_hh', # Number of separate households within the house
'num_res', # Number of residents per household
],
},
},
}
def eid(hid):
return 'House_%s' % hid
class HouseholdSim(mosaik_api.Simulator):
def __init__(self):
super().__init__(meta)
self.model = None
self.houses_by_eid = {}
self.pos_loads = None
self._file_cache = {}
self._offset = 0
self._cache = {}
def init(self, sid, pos_loads=True):
logger.debug('Loads will be %s numbers.' %
('positive' if pos_loads else 'negative'))
self.pos_loads = 1 if pos_loads else -1
return self.meta
def create(self, num, model, sim_start, profile_file, grid_name):
if num != 1 or self.model:
raise ValueError('Can only create one set of houses.')
logger.info('Creating houses for %s from "%s"' %
(grid_name, profile_file))
if profile_file.endswith('gz'):
import gzip
pf = gzip.open(profile_file, 'rt')
else:
pf = open(profile_file, 'rt')
try:
self.model = model_house.HouseModel(pf, grid_name)
self.houses_by_eid = {
eid(i): house for i, house in enumerate(self.model.houses)
}
except KeyError:
raise ValueError('Invalid grid name "%s".' % grid_name)
# A time offset in minutes from the simulation start to the start
# of the profiles.
self._offset = self.model.get_delta(sim_start)
return [{
'eid': 'resid_0',
'type': 'ResidentialLoads',
'rel': [],
'children': [{'eid': eid(i), 'type': 'House', 'rel': []}
for i, _ in enumerate(self.model.houses)],
}]
def step(self, time, inputs):
# "time" is in seconds. Convert to minutes and add the offset
# if sim start > start date of the profiles.
houses = []
# print("INPUTS", inputs)
for key in inputs.keys():
houses.append(key[key.find("_"):][1:])
# print("HOUSES", houses)
if len(inputs)!=0:
newValue = list(list(list(inputs.values())[0].values())[0].values())[0]
minutes = time // 60 + self._offset
cache = {}
data = self.model.get(minutes)
# print("DATA", data)
for hid, d in enumerate(data):
d *= self.pos_loads # Flip sign if necessary
cache[eid(hid)] = d
for house in houses:
# cache[eid(house)] = random.randint(50, 200)
# cache[eid(house)] = d*0.3
cache[eid(house)] = newValue*d/100
self._cache = cache
return (minutes + self.model.resolution) * 60 # seconds
def get_data(self, outputs):
data = {}
for eid, attrs in outputs.items():
data[eid] = {}
for attr in attrs:
if attr == 'P_out':
val = self._cache[eid]
else:
val = self.houses_by_eid[eid][attr]
data[eid][attr] = val
return data
def main():
return mosaik_api.start_simulation(HouseholdSim(), 'Household simulation')
"""
"""
import json
import arrow
DATE_FORMAT = 'YYYY-MM-DD HH:mm'
"""Date format used to convert strings to dates."""
class HouseModel:
"""The HouseModel processes and prepares the load profiles and their
associated meta data to allow and easier access to it.
"""
def __init__(self, data, lv_grid):
# Process meta data
assert next(data).startswith('# meta')
meta = json.loads(next(data))
self.start = arrow.get(meta['start_date'], DATE_FORMAT)
"""The start date of the profile data."""
self.resolution = meta['resolution']
"""The time resolution of the data in minutes."""
self.unit = meta['unit']
"""The unit used for the load profiles (e.g., *W*)."""
self.num_profiles = meta['num_profiles']
"""The number of load profiles in the file."""
# Obtain id lists
assert next(data).startswith('# id_list')
id_list_lines = []
for line in data:
if line.startswith('# attrs'):
break
id_list_lines.append(line)
id_lists = json.loads(''.join(id_list_lines))
self.node_ids = id_lists[lv_grid]
"""List of power grid node IDs for which to create houses."""
# Enable pre-processing of the data
self._data = self._get_line(data)
# Obtain static attributes and create list of house info dicts
attrs = {}
for attr, *vals in self._data:
if attr.startswith('# profiles'):
break
attrs[attr] = [int(val) for val in vals]
#: List of house info dicts
self.houses = [
{
'num': i + 1,
'node_id': n,
'num_hh': attrs['num_hh'][i % self.num_profiles],
'num_res': attrs['num_residents'][i % self.num_profiles],
} for i, n in enumerate(self.node_ids)
]
# Helpers for get()
self._last_date = None
self._cache = None
def get(self, minutes):
"""Get the current load for all houses for *minutes* minutes since
:attr:`start`.
If the model uses a 15min resolution and minutes not multiple of 15,
the next smaller multiple of 15 will be used. For example, if you
pass ``minutes=23``, you'll get the value for ``15``.
"""
# Trim "minutes" to multiples of "self.resolution"
# Example: res=15, minutes=40 -> minutes == 30
minutes = minutes // self.resolution * self.resolution
target_date = self.start.replace(minutes=minutes)
if target_date != self._last_date:
# If target date not already reached, search data until we find it:
for date, *values in self._data:
date = arrow.get(date, DATE_FORMAT)
if date == target_date:
# Found target date, cache results:
values = list(map(float, values))
self._cache = [values[i % self.num_profiles]
for i, _ in enumerate(self.houses)]
self._last_date = date
break
else:
# We've reached the end of our data file if the for loop
# normally finishes.
raise IndexError('Target date "%s" (%s minutes from start) '
'out of range.' % (target_date, minutes))
return self._cache
def get_delta(self, date):
"""Get the amount of minutes between *date* and :attr:`start`.
The date needs to be a strings formated like :data:`DATE_FORMAT`.
Raise a :exc:`ValueError` if *date* is smaller than :attr:`start`.
"""
date = arrow.get(date, DATE_FORMAT)
if date < self.start:
raise ValueError('date must >= "%s".' %
self.start.format(DATE_FORMAT))
dt = date - self.start
minutes = (dt.days * 1440) + (dt.seconds // 60)
return minutes
def _get_line(self, iterator):
for line in iterator:
yield [item.strip() for item in line.split(',')]
import random
import numpy as np
import sys
from mosaik.util import connect_randomly, connect_many_to_one
import mosaik
sim_config = {
'CSV': {
'python': 'mosaik_csv:CSV',
},
'DB': {
'cmd': 'mosaik-hdf5 %(addr)s',
},
'HouseholdSim': {
'python': 'householdsim:HouseholdSim',
# 'cmd': 'mosaik-householdsim %(addr)s',
},
'PyPower': {
'python': 'mosaik_pypower.mosaik:PyPower',
# 'cmd': 'mosaik-pypower %(addr)s',
},
'WebVis': {
'cmd': 'mosaik-web -s 0.0.0.0:8000 %(addr)s',
},
'Attack': {
'python': 'attack:Attack'
}
}
START = '2014-01-01 00:00:00'
END = 31 * 24 * 3600 # 1 day
PV_DATA = 'data/pv_10kw.csv'
PROFILE_FILE = 'data/profiles.data.gz'
GRID_NAME = 'demo_lv_grid'
GRID_FILE = 'data/%s.json' % GRID_NAME
nAttacks = 10
def main():
random.seed(23)
world = mosaik.World(sim_config)
create_scenario(world)
world.run(until=END) # As fast as possilbe
# world.run(until=END, rt_factor=1/60) # Real-time 1min -> 1sec
def create_scenario(world):
# Start simulators
pypower = world.start('PyPower', step_size=15*60)
hhsim = world.start('HouseholdSim')
pvsim = world.start('CSV', sim_start=START, datafile=PV_DATA)
attacksim = world.start('Attack')
# Instantiate models
grid = pypower.Grid(gridfile=GRID_FILE).children
houses = hhsim.ResidentialLoads(sim_start=START,
profile_file=PROFILE_FILE,
grid_name=GRID_NAME).children
pvs = pvsim.PV.create(20)
attacks = attacksim.Attack.create(nAttacks, target_attr='P_out')
nodes = [element for element in grid if 'node' in element.eid]
# Connect entities
connect_buildings_to_grid(world, houses, grid)
connect_randomly(world, pvs, [e for e in grid if 'node' in e.eid], 'P')
# Connect attacks to houses
attacked_houses = {}
if len(attacks) > len(houses):
print("Error: The number of attacks is greater than the number of houses")
world.shutdown()
sys.exit()
for i in range(len(attacks)):
house = np.random.choice(houses)
while attacked_houses.get(house):
house = np.random.choice(houses)
attacked_houses[house] = attacks[i]
world.connect(house, attacked_houses[house], ('P_out','P_out_val') , async_requests=True)
# Database
db = world.start('DB', step_size=60, duration=END)
hdf5 = db.Database(filename='demo.hdf5')
connect_many_to_one(world, houses, hdf5, 'P_out')
connect_many_to_one(world, pvs, hdf5, 'P')
nodes = [e for e in grid if e.type in ('RefBus, PQBus')]
connect_many_to_one(world, nodes, hdf5, 'P', 'Q', 'Vl', 'Vm', 'Va')
branches = [e for e in grid if e.type in ('Transformer', 'Branch')]
connect_many_to_one(world, branches, hdf5,
'P_from', 'Q_from', 'P_to', 'P_from')
# Web visualization
webvis = world.start('WebVis', start_date=START, step_size=60)
webvis.set_config(ignore_types=['Topology', 'ResidentialLoads', 'Grid',
'Database'])
vis_topo = webvis.Topology()
connect_many_to_one(world, attacks, vis_topo)
connect_many_to_one(world, nodes, vis_topo, 'P', 'Vm')
webvis.set_etypes({
'RefBus': {
'cls': 'refbus',
'attr': 'P',
'unit': 'P [W]',
'default': 0,
'min': 0,
'max': 30000,
},
'PQBus': {
'cls': 'pqbus',
'attr': 'Vm',
'unit': 'U [V]',
'default': 230,
'min': 0.99 * 230,
'max': 1.01 * 230,
},
})
connect_many_to_one(world, houses, vis_topo, 'P_out')
webvis.set_etypes({
'House': {
'cls': 'load',
'attr': 'P_out',
'unit': 'P [W]',
'default': 0,
'min': 0,
'max': 3000,
},
})
connect_many_to_one(world, pvs, vis_topo, 'P')
webvis.set_etypes({
'PV': {
'cls': 'gen',
'attr': 'P',
'unit': 'P [W]',
'default': 0,
'min': -10000,
'max': 0,
},
})
def connect_buildings_to_grid(world, houses, grid):
buses = filter(lambda e: e.type == 'PQBus', grid)
buses = {b.eid.split('-')[1]: b for b in buses}
house_data = world.get_data(houses, 'node_id')
for house in houses:
node_id = house_data[house]['node_id']
world.connect(house, buses[node_id], ('P_out', 'P'))
if __name__ == '__main__':
main()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment