Skip to content

Commit

Permalink
Merge pull request #846 from ioam/dimensioned_streams
Browse files Browse the repository at this point in the history
Dimensioned streams
  • Loading branch information
philippjfr authored Sep 9, 2016
2 parents dec82e4 + a24807d commit 2942edf
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 12 deletions.
23 changes: 12 additions & 11 deletions holoviews/core/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@ def _validate_mode(self):
return 'key'
# Any unbounded kdim (any direction) implies open mode
for kdim in self.kdims:
if kdim.name in util.stream_parameters(self.streams):
return 'key'
if kdim.values:
continue
if None in kdim.range:
Expand Down Expand Up @@ -557,13 +559,10 @@ def _execute_callback(self, *args):
retval = next(self.callback)
else:
# Additional validation needed to ensure kwargs don't clash
kdims = [kdim.name for kdim in self.kdims]
kwarg_items = [s.contents.items() for s in self.streams]
flattened = [el for kws in kwarg_items for el in kws]
klist = [k for k,_ in flattened]
clashes = set([k for k in klist if klist.count(k) > 1])
if clashes:
self.warning('Parameter name clashes for keys: %r' % clashes)

flattened = [(k,v) for kws in kwarg_items for (k,v) in kws
if k not in kdims]
retval = self.callback(*args, **dict(flattened))
if self.call_mode=='key':
return self._style(retval)
Expand Down Expand Up @@ -654,7 +653,7 @@ def __getitem__(self, key):
for a previously generated key that is still in the cache
(for one of the 'open' modes)
"""
tuple_key = util.wrap_tuple(key)
tuple_key = util.wrap_tuple_streams(key, self.kdims, self.streams)

# Validation for bounded mode
if self.mode == 'bounded':
Expand All @@ -673,8 +672,8 @@ def __getitem__(self, key):

# Cache lookup
try:
if self.streams:
raise KeyError('Using streams disables DynamicMap cache')
if util.dimensionless_contents(self.streams, self.kdims):
raise KeyError('Using dimensionless streams disables DynamicMap cache')
cache = super(DynamicMap,self).__getitem__(key)
# Return selected cache items in a new DynamicMap
if isinstance(cache, DynamicMap) and self.mode=='open':
Expand Down Expand Up @@ -704,9 +703,11 @@ def _cache(self, key, val):
"""
Request that a key/value pair be considered for caching.
"""
cache_size = (1 if util.dimensionless_contents(self.streams, self.kdims)
else self.cache_size)
if self.mode == 'open' and (self.counter % self.cache_interval)!=0:
return
if len(self) >= self.cache_size:
if len(self) >= cache_size:
first_key = next(self.data.iterkeys())
self.data.pop(first_key)
self.data[key] = val
Expand All @@ -728,7 +729,7 @@ def next(self):
(key, val) = (retval if isinstance(retval, tuple)
else (self.counter, retval))

key = util.wrap_tuple(key)
key = util.wrap_tuple_streams(key, self.kdims, self.streams)
if len(key) != len(self.key_dimensions):
raise Exception("Generated key does not match the number of key dimensions")

Expand Down
46 changes: 46 additions & 0 deletions holoviews/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,52 @@ def wrap_tuple(unwrapped):
return (unwrapped if isinstance(unwrapped, tuple) else (unwrapped,))



def stream_parameters(streams, no_duplicates=True, exclude=['name']):
"""
Given a list of streams, return a flat list of parameter name,
excluding those listed in the exclude list.
If no_duplicates is enabled, a KeyError will be raised if there are
parameter name clashes across the streams.
"""
param_groups = [s.params().keys() for s in streams]
names = [name for group in param_groups for name in group]

if no_duplicates:
clashes = set([n for n in names if names.count(n) > 1])
if clashes:
raise KeyError('Parameter name clashes for keys: %r' % clashes)
return [name for name in names if name not in exclude]


def dimensionless_contents(streams, kdims):
"""
Return a list of stream parameters that have not been associated
with any of the key dimensions.
"""
names = stream_parameters(streams)
kdim_names = [kdim.name for kdim in kdims]
return [name for name in names if name not in kdim_names]


def wrap_tuple_streams(unwrapped, kdims, streams):
"""
Fills in tuple keys with dimensioned stream values as appropriate.
"""
param_groups = [(s.params().keys(), s) for s in streams]
pairs = [(name,s) for (group, s) in param_groups for name in group]
substituted = []
for pos,el in enumerate(wrap_tuple(unwrapped)):
if el is None and pos < len(kdims):
matches = [(name,s) for (name,s) in pairs if name==kdims[pos].name]
if len(matches) == 1:
(name, stream) = matches[0]
el = stream.contents[name]
substituted.append(el)
return tuple(substituted)


def itervalues(obj):
"Get value iterator from dictionary for Python 2 and 3"
return iter(obj.values()) if sys.version_info.major == 3 else obj.itervalues()
Expand Down
37 changes: 36 additions & 1 deletion tests/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

import numpy as np

from holoviews.core.util import sanitize_identifier_fn, find_range, max_range
from holoviews.core.util import sanitize_identifier_fn, find_range, max_range, wrap_tuple_streams
from holoviews import Dimension
from holoviews.streams import PositionXY
from holoviews.element.comparison import ComparisonTestCase

py_version = sys.version_info.major
Expand Down Expand Up @@ -298,3 +300,36 @@ def test_max_range2(self):
lower, upper = max_range(self.ranges2)
self.assertTrue(math.isnan(lower))
self.assertTrue(math.isnan(upper))



class TestWrapTupleStreams(unittest.TestCase):


def test_no_streams(self):
result = wrap_tuple_streams((1,2), [],[])
self.assertEqual(result, (1,2))

def test_no_streams_two_kdims(self):
result = wrap_tuple_streams((1,2),
[Dimension('x'), Dimension('y')],
[])
self.assertEqual(result, (1,2))

def test_no_streams_none_value(self):
result = wrap_tuple_streams((1,None),
[Dimension('x'), Dimension('y')],
[])
self.assertEqual(result, (1,None))

def test_no_streams_one_stream_substitution(self):
result = wrap_tuple_streams((None,3),
[Dimension('x'), Dimension('y')],
[PositionXY(x=-5,y=10)])
self.assertEqual(result, (-5,3))

def test_no_streams_two_stream_substitution(self):
result = wrap_tuple_streams((None,None),
[Dimension('x'), Dimension('y')],
[PositionXY(x=0,y=5)])
self.assertEqual(result, (0,5))

0 comments on commit 2942edf

Please sign in to comment.