Skip to content

Commit

Permalink
Experimenting with slight change of design. EMeters now contain list
Browse files Browse the repository at this point in the history
of appliances they measure.  Tests pass.
  • Loading branch information
JackKelly committed May 9, 2014
1 parent 55bfe1a commit 305a0fd
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 107 deletions.
2 changes: 1 addition & 1 deletion nilmtk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
from nilmtk.timeframe import TimeFrame
from nilmtk.emeter import EMeter
from nilmtk.datastore import DataStore, HDFDataStore
from nilmtk.appliancegroup import ApplianceGroup
from nilmtk.metergroup import MeterGroup
from nilmtk.appliance import Appliance
from nilmtk.mains import Mains
46 changes: 2 additions & 44 deletions nilmtk/appliance.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from mains import Mains

class Appliance(Mains):
class Appliance(object):
"""
Attributes
----------
Expand All @@ -17,13 +15,7 @@ class Appliance(Mains):
on_power_threshold : float, watts
minimum_off_duration : timedelta
minimum_on_duration : timedelta
mains : Mains (used so appliance methods can default to use
the same measured parameter (active / apparent / reactive)
as Mains; and also for use in proportion of energy submetered
and for voltage normalisation.)
minimum_on_duration : timedelta
"""

# TODO: appliance_types will be loaded from disk
Expand Down Expand Up @@ -88,38 +80,4 @@ def __repr__(self):
md.get('type'), md.get('instance'),
md.get('dataset'), md.get('building')))

def total_on_duration(self):
"""Return timedelta"""
raise NotImplementedError

def on_durations(self):
raise NotImplementedError

def activity_distribution(self, bin_size, timespan):
raise NotImplementedError

def when_on(self):
"""Return Series of bools"""
raise NotImplementedError

def on_off_events(self):
# use self.metadata.minimum_[off|on]_duration
raise NotImplementedError

def discrete_appliance_activations(self):
"""
Return a Mask defining the start and end times of each appliance
activation.
"""
raise NotImplementedError

def proportion_of_energy(self):
# get the old mask so we can put it back at the end
old_mask = self.get_loader_attribute('mask')

# Mask out gaps from mains
self.set_loader_attributes(mask = self.mains.gaps())
proportion_of_energy = self.total_energy() / self. mains.total_energy()
self.set_loader_attributes(mask = old_mask)
return proportion_of_energy

6 changes: 3 additions & 3 deletions nilmtk/electricity.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import print_function, division
from .appliancegroup import ApplianceGroup
from .appliancegroup import MeterGroup

class Electricity(ApplianceGroup):
class Electricity(MeterGroup):
"""Represents mains circuit in a single building.
Attributes
Expand All @@ -21,7 +21,7 @@ def meters_directly_downstream_of_mains(self):
"""
raise NotImplementedError
wiring_graph = self.wiring_graph()
# return meters (not appliances) one hop from root (mains)
# return meters (not meters) one hop from root (mains)

def proportion_energy_submetered(self):
good_mains_timeframes = self.mains.good_timeframes()
Expand Down
102 changes: 89 additions & 13 deletions nilmtk/emeter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ class EMeter(object):
Attributes
----------
appliances : list of Appliance objects connected immediately downstream
of this meter. Will be [] if no appliances are connected directly
to this meter.
dominant_appliance : reference to Appliance which is responsibly for
most of the power demand on this channel.
mains : Mains (used so appliance methods can default to use
the same measured parameter (active / apparent / reactive)
as Mains; and also for use in proportion of energy submetered
and for voltage normalisation.)
store : nilmtk.DataStore
key : key into nilmtk.DataStore to access data
Expand Down Expand Up @@ -45,6 +57,9 @@ def __init__(self):
self.metadata = {}
self.store = None
self.key = None
self.appliances = []
self.mains = None
self.dominant_appliance = None

@classmethod
def _load_meter_devices(cls, store):
Expand All @@ -68,7 +83,40 @@ def save(self, destination, key):
"""
# destination.write_metadata(key, self.metadata)
raise NotImplementedError


def matches(self, key):
"""
Parameters
----------
key : dict
Returns
-------
True if all key:value pairs in `key` match any appliance
in `self.appliances`.
"""
for appliance in self.appliances:
if appliance.matches(key):
return True
return False

@property
def id(self):
key_attrs = ['id', 'dataset', 'building']
id_dict = {}
for key in key_attrs:
id_dict[key] = self.metadata.get(key)
return id_dict

def __eq__(self, other):
if isinstance(other, EMeter):
return self.id == other.id
else:
return False

def __hash__(self):
return hash(((k,v) for k,v in self.id.iteritems()))

def power_series(self, measurement_preferences=None,
required_measurement=None,
normalise=False, voltage_series=None,
Expand Down Expand Up @@ -112,12 +160,6 @@ def total_energy(self, **load_kwargs):
nodes = [ClipNode(), EnergyNode()]
results = self._run_pipeline(nodes, **load_kwargs)
return results['energy']

def _sanity_check_before_processing(self):
if self.store is None:
msg = ("'meter.loader' is not set!"
" Cannot process data without a loader!")
raise RuntimeError(msg)

def dropout_rate(self):
"""returns a DropoutRateResults object."""
Expand All @@ -132,13 +174,39 @@ def good_sections(self):
nodes = [LocateGoodSectionsNode()]
results = self._run_pipeline(nodes)
return results['good_sections']

def _run_pipeline(self, nodes, **load_kwargs):
self._sanity_check_before_processing()
pipeline = Pipeline(nodes)
pipeline.run(meter=self, **load_kwargs)
return pipeline.results

def total_on_duration(self):
"""Return timedelta"""
raise NotImplementedError

def on_durations(self):
raise NotImplementedError

def activity_distribution(self, bin_size, timespan):
raise NotImplementedError

def when_on(self):
"""Return Series of bools"""
raise NotImplementedError

def on_off_events(self):
# use self.metadata.minimum_[off|on]_duration
raise NotImplementedError

def discrete_appliance_activations(self):
"""
Return a Mask defining the start and end times of each appliance
activation.
"""
raise NotImplementedError

def proportion_of_energy(self):
# Mask out gaps from mains
good_mains_timeframes = self.mains.good_timeframes()
proportion_of_energy = (self.total_energy(timeframes=good_mains_timeframes) /
self. mains.total_energy())
return proportion_of_energy

def contiguous_sections(self):
"""retuns Mask object"""
raise NotImplementedError
Expand All @@ -150,3 +218,11 @@ def clean_and_export(self, destination_datastore):
implausible values removed)"""
raise NotImplementedError

def _run_pipeline(self, nodes, **load_kwargs):
if self.store is None:
msg = ("'meter.loader' is not set!"
" Cannot process data without a loader!")
raise RuntimeError(msg)
pipeline = Pipeline(nodes)
pipeline.run(meter=self, **load_kwargs)
return pipeline.results

0 comments on commit 305a0fd

Please sign in to comment.