Skip to content

Commit

Permalink
Merge pull request #88 from zach401/ENH_update_uplug_event_to_contain_ev
Browse files Browse the repository at this point in the history
Update UnplugEvent to contain an EV object instead of session_id and …
  • Loading branch information
zach401 committed Dec 23, 2020
2 parents 995f45d + fdbfc40 commit 716873f
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 62 deletions.
11 changes: 10 additions & 1 deletion acnportal/acnsim/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,16 @@ def _from_registry(cls, in_registry, loaded_dict=None):
loaded_attr_value = attribute_dict[attr]
# If the attribute was originally JSON serializable,
# this is correct loading.
setattr(out_obj, attr, loaded_attr_value)
try:
setattr(out_obj, attr, loaded_attr_value)
except AttributeError:
# attr could be protected for out_obj. Warn if it is.
warnings.warn(
f"Attribute {attr} is protected for object of class "
f"{out_obj.__class__}. Not setting {attr} to "
f"{loaded_attr_value}. Please see {out_obj.__class__} "
f"implementation for more info."
)

# Add this object to the dictionary of loaded objects.
loaded_dict[obj_id] = out_obj
Expand Down
123 changes: 88 additions & 35 deletions acnportal/acnsim/events/event.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# coding=utf-8
"""
Defines several classes of Events in the simulation.
"""
from typing import Optional, Dict, Any, Tuple

from ..base import BaseSimObj
import warnings

from ..models.ev import EV


class Event(BaseSimObj):
""" Base class for all events.
Expand All @@ -13,17 +19,21 @@ class Event(BaseSimObj):
Attributes:
timestamp (int): See args.
event_type (str): Name of the event type.
precedence (float): Used to order occurrence for events that happen in the same timestep. Higher precedence
events occur before lower precedence events.
precedence (float): Used to order occurrence for events that happen in the same
timestep. Higher precedence events occur before lower precedence events.
"""

def __init__(self, timestamp):
timestamp: int
event_type: str
precedence: float

def __init__(self, timestamp: int) -> None:
self.timestamp = timestamp
self.event_type = ""
self.precedence = float("inf")

def __lt__(self, other):
def __lt__(self, other: "Event") -> bool:
""" Return True if the precedence of self is less than that of other.
Args:
Expand All @@ -35,7 +45,7 @@ def __lt__(self, other):
return self.precedence < other.precedence

@property
def type(self):
def type(self) -> str:
"""
Legacy accessor for event_type. This will be removed in a future
release.
Expand All @@ -59,7 +69,9 @@ def _to_dict(
return attribute_dict, context_dict

@classmethod
def _from_dict_helper(cls, out_obj, attribute_dict):
def _from_dict_helper(
cls, out_obj: "Event", attribute_dict: Dict[str, Any]
) -> None:
out_obj.event_type = attribute_dict["event_type"]
out_obj.precedence = attribute_dict["precedence"]

Expand All @@ -76,19 +88,30 @@ def _from_dict(
return out_obj, loaded_dict


class PluginEvent(Event):
""" Subclass of Event for EV plugins.
class EVEvent(Event):
""" Subclass of Event for events which deal with an EV such as Plugin and Unplug
events.
Args:
timestamp (int): See Event.
ev (EV): The EV which will be plugged in.
ev (EV): The EV associated with this event.
"""

def __init__(self, timestamp, ev):
ev: EV

def __init__(self, timestamp: int, ev: EV) -> None:
super().__init__(timestamp)
self.event_type = "Plugin"
self.ev = ev
self.precedence = 10

@property
def station_id(self) -> str:
""" Return the station_id for the EV associated with this Event. """
return self.ev.station_id

@property
def session_id(self) -> str:
""" Return the session_id for the EV associated with this Event. """
return self.ev.session_id

def _to_dict(
self, context_dict: Optional[Dict[str, Any]] = None
Expand Down Expand Up @@ -120,32 +143,33 @@ def _from_dict(
return out_obj, loaded_dict


class UnplugEvent(Event):
class PluginEvent(EVEvent):
""" Subclass of Event for EV plugins.
Args:
timestamp (int): See Event.
ev (EV): The EV which will be plugged in.
"""

def __init__(self, timestamp: int, ev: EV) -> None:
super().__init__(timestamp, ev)
self.event_type = "Plugin"
self.precedence = 10


class UnplugEvent(EVEvent):
""" Subclass of Event for EV unplugs.
Args:
timestamp (int): See Event.
station_id (str): ID of the EVSE where the EV is to be unplugged.
session_id (str): ID of the session which should be ended.
ev (EV): The EV which will be unplugged.
"""

def __init__(self, timestamp, station_id, session_id):
super().__init__(timestamp)
def __init__(self, timestamp: int, ev: EV) -> None:
super().__init__(timestamp, ev)
self.event_type = "Unplug"
self.station_id = station_id
self.session_id = session_id
self.precedence = 0

def _to_dict(
self, context_dict: Optional[Dict[str, Any]] = None
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
""" Implements BaseSimObj._to_dict. """
attribute_dict, context_dict = super()._to_dict(context_dict)
# Unplug-specific attributes
attribute_dict["station_id"] = self.station_id
attribute_dict["session_id"] = self.session_id
return attribute_dict, context_dict

@classmethod
def _from_dict(
cls,
Expand All @@ -154,19 +178,48 @@ def _from_dict(
loaded_dict: Optional[Dict[str, BaseSimObj]] = None,
) -> Tuple[BaseSimObj, Dict[str, BaseSimObj]]:
""" Implements BaseSimObj._from_dict. """
out_obj = cls(
attribute_dict["timestamp"],
attribute_dict["station_id"],
attribute_dict["session_id"],
)
# noinspection PyProtectedMember
try:
ev, loaded_dict = BaseSimObj._build_from_id(
attribute_dict["ev"], context_dict, loaded_dict=loaded_dict
)
except KeyError:
# In acnportal v0.2.2, UnplugEvent had session_id and station_id attributes
# instead of an ev attribute. For backwards compatibility with previously
# serialized UnplugEvents, we build the EV partially (including session and
# station ids only) and set it as the ev attribute of the current
# implementation of UnplugEvent.
warnings.warn(
"Loading UnplugEvents from an older version of acnportal into a newer "
"one. UnplugEvent EV object will be incompletely deserialized."
)
ev = EV(
-1,
-1,
-1,
attribute_dict["station_id"],
attribute_dict["session_id"],
None,
)
for attribute in [
"arrival",
"departure",
"requested_energy",
"estimated_departure",
"battery",
"energy_delivered",
"current_charging_rate",
]:
delattr(ev, f"_{attribute}")
out_obj = cls(attribute_dict["timestamp"], ev)
cls._from_dict_helper(out_obj, attribute_dict)
return out_obj, loaded_dict


class RecomputeEvent(Event):
""" Subclass of Event for when the algorithm should be recomputed."""

def __init__(self, timestamp):
def __init__(self, timestamp: int) -> None:
super().__init__(timestamp)
self.event_type = "Recompute"
self.precedence = 20
11 changes: 9 additions & 2 deletions acnportal/acnsim/models/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,9 @@ def _charge_stepwise(self, pilot, voltage, period):
if self._soc < self._transition_soc:
charge_power = min([pilot * voltage / 1000, self._max_power, rate_to_full])
if self._noise_level > 0:
charge_power = max(charge_power - abs(np.random.normal(0, self._noise_level)), 0)
charge_power = max(
charge_power - abs(np.random.normal(0, self._noise_level)), 0
)
else:
charge_power = min(
[
Expand All @@ -326,7 +328,12 @@ def _charge_stepwise(self, pilot, voltage, period):
]
)
if self._noise_level > 0:
charge_power = min(max(charge_power + np.random.normal(0, self._noise_level), 0), pilot * voltage / 1000, self._max_power, rate_to_full)
charge_power = min(
max(charge_power + np.random.normal(0, self._noise_level), 0),
pilot * voltage / 1000,
self._max_power,
rate_to_full,
)
# ensure that noise does not cause the battery to violate any hard limits.
charge_power = min(
[
Expand Down
12 changes: 3 additions & 9 deletions acnportal/acnsim/simulator.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import copy
from datetime import datetime
from typing import Optional, Dict, Any, Tuple
from typing import Dict

import pandas as pd
import numpy as np
import warnings
import json

Expand Down Expand Up @@ -213,16 +211,12 @@ def _process_event(self, event):
self._print("Plugin Event...")
self.network.plugin(event.ev)
self.ev_history[event.ev.session_id] = event.ev
self.event_queue.add_event(
UnplugEvent(
event.ev.departure, event.ev.station_id, event.ev.session_id
)
)
self.event_queue.add_event(UnplugEvent(event.ev.departure, event.ev))
self._resolve = True
self._last_schedule_update = event.timestamp
elif event.event_type == "Unplug":
self._print("Unplug Event...")
self.network.unplug(event.station_id, event.session_id)
self.network.unplug(event.ev.station_id, event.ev.session_id)
self._resolve = True
self._last_schedule_update = event.timestamp
elif event.event_type == "Recompute":
Expand Down
5 changes: 1 addition & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,5 @@
"pytz",
"typing_extensions",
],
extras_require={
"all": ["scikit-learn"],
"scikit-learn": ["scikit-learn"]
}
extras_require={"all": ["scikit-learn"], "scikit-learn": ["scikit-learn"]},
)
20 changes: 20 additions & 0 deletions tests/old_unplug.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"id": "140258042267600",
"context_dict": {
"140258042267600": {
"class": "acnportal.acnsim.events.event.UnplugEvent",
"attributes": {
"timestamp": 11,
"event_type": "Unplug",
"precedence": 0,
"station_id": "PS-001",
"session_id": "EV-001"
}
}
},
"version": "0.2.2",
"dependency_versions": {
"numpy": "1.19.1",
"pandas": "1.1.0"
}
}
53 changes: 42 additions & 11 deletions tests/test_json_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from acnportal import acnsim
from acnportal.algorithms import BaseAlgorithm, UncontrolledCharging
from acnportal.acnsim.base import ErrorAllWrapper
from .serialization_extensions import NamedEvent, DefaultNamedEvent
from .serialization_extensions import SetAttrEvent, BatteryListEvent
from tests.serialization_extensions import NamedEvent, DefaultNamedEvent
from tests.serialization_extensions import SetAttrEvent, BatteryListEvent

import os
import numpy as np
Expand Down Expand Up @@ -87,7 +87,7 @@ def setUpClass(cls):
# Events
cls.event = acnsim.Event(0)
cls.plugin_event1 = acnsim.PluginEvent(10, cls.ev1)
cls.unplug_event = acnsim.UnplugEvent(20, "PS-001", "EV-001")
cls.unplug_event = acnsim.UnplugEvent(20, cls.ev1)
cls.recompute_event1 = acnsim.RecomputeEvent(30)
cls.plugin_event2 = acnsim.PluginEvent(40, cls.ev2)
cls.plugin_event3 = acnsim.PluginEvent(50, cls.ev3)
Expand Down Expand Up @@ -200,13 +200,7 @@ def setUpClass(cls):
],
"Event": ["timestamp", "event_type", "precedence"],
"PluginEvent": ["timestamp", "event_type", "precedence"],
"UnplugEvent": [
"timestamp",
"event_type",
"precedence",
"station_id",
"session_id",
],
"UnplugEvent": ["timestamp", "event_type", "precedence"],
"RecomputeEvent": ["timestamp", "event_type", "precedence"],
"EventQueue": ["_timestep"],
"ChargingNetwork": [
Expand Down Expand Up @@ -279,7 +273,8 @@ def test_plugin_event_json(self):
self.assertEqual(getattr(getattr(event_loaded, "ev"), "_session_id"), "EV-001")

def test_unplug_event_json(self):
_ = self._obj_compare_helper(self.unplug_event)
event_loaded = self._obj_compare_helper(self.unplug_event)
self.assertEqual(getattr(getattr(event_loaded, "ev"), "_session_id"), "EV-001")

def test_recompute_event_json(self):
_ = self._obj_compare_helper(self.recompute_event1)
Expand Down Expand Up @@ -624,3 +619,39 @@ def test_to_json_str_io(self):
input_str_io = io.StringIO(output_str)
battery_loaded = acnsim.Battery.from_json(input_str_io)
self.assertEqual(self.battery.__dict__, battery_loaded.__dict__)


class TestLegacyObjJSONInput(TestCase):
def test_legacy_unplug_event_json(self) -> None:
""" Tests that UnplugEvents from <0.2.2 can be loaded.
In acnportal v0.2.2, UnplugEvent had session_id and station_id attributes
instead of an ev attribute.
Returns:
None
"""
with self.assertWarns(UserWarning):
unplug_loaded: acnsim.UnplugEvent = acnsim.UnplugEvent.from_json(
os.path.join(os.path.dirname(__file__), "old_unplug.json")
)
self.assertIsInstance(unplug_loaded, acnsim.UnplugEvent)
self.assertEqual(unplug_loaded.event_type, "Unplug")
self.assertEqual(unplug_loaded.timestamp, 11)
self.assertEqual(unplug_loaded.precedence, 0)

# Check that UnplugEvent's EV is partially loaded
self.assertEqual(getattr(getattr(unplug_loaded, "ev"), "_session_id"), "EV-001")
self.assertEqual(getattr(getattr(unplug_loaded, "ev"), "_station_id"), "PS-001")

for attribute in [
"arrival",
"departure",
"requested_energy",
"estimated_departure",
"battery",
"energy_delivered",
"current_charging_rate",
]:
with self.assertRaises(AttributeError):
getattr(unplug_loaded.ev, attribute)

0 comments on commit 716873f

Please sign in to comment.