commit
5904212460
@ -0,0 +1,251 @@
|
||||
"""Interact with functions using widgets."""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2013, the IPython Development Team.
|
||||
#
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
try: # Python >= 3.3
|
||||
from inspect import signature, Parameter
|
||||
except ImportError:
|
||||
from IPython.utils.signatures import signature, Parameter
|
||||
from inspect import getcallargs
|
||||
|
||||
from IPython.html.widgets import (Widget, TextWidget,
|
||||
FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
|
||||
ContainerWidget, DOMWidget)
|
||||
from IPython.display import display, clear_output
|
||||
from IPython.utils.py3compat import string_types, unicode_type
|
||||
from IPython.utils.traitlets import HasTraits, Any, Unicode
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Classes and Functions
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _matches(o, pattern):
|
||||
"""Match a pattern of types in a sequence."""
|
||||
if not len(o) == len(pattern):
|
||||
return False
|
||||
comps = zip(o,pattern)
|
||||
return all(isinstance(obj,kind) for obj,kind in comps)
|
||||
|
||||
|
||||
def _get_min_max_value(min, max, value=None, step=None):
|
||||
"""Return min, max, value given input values with possible None."""
|
||||
if value is None:
|
||||
if not max > min:
|
||||
raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
|
||||
value = min + abs(min-max)/2
|
||||
value = type(min)(value)
|
||||
elif min is None and max is None:
|
||||
if value == 0.0:
|
||||
min, max, value = 0.0, 1.0, 0.5
|
||||
elif value == 0:
|
||||
min, max, value = 0, 1, 0
|
||||
elif isinstance(value, (int, float)):
|
||||
min, max = (-value, 3*value) if value > 0 else (3*value, -value)
|
||||
else:
|
||||
raise TypeError('expected a number, got: %r' % value)
|
||||
else:
|
||||
raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
|
||||
if step is not None:
|
||||
# ensure value is on a step
|
||||
r = (value - min) % step
|
||||
value = value - r
|
||||
return min, max, value
|
||||
|
||||
def _widget_abbrev_single_value(o):
|
||||
"""Make widgets from single values, which can be used as parameter defaults."""
|
||||
if isinstance(o, string_types):
|
||||
return TextWidget(value=unicode_type(o))
|
||||
elif isinstance(o, dict):
|
||||
return DropdownWidget(values=o)
|
||||
elif isinstance(o, bool):
|
||||
return CheckboxWidget(value=o)
|
||||
elif isinstance(o, float):
|
||||
min, max, value = _get_min_max_value(None, None, o)
|
||||
return FloatSliderWidget(value=o, min=min, max=max)
|
||||
elif isinstance(o, int):
|
||||
min, max, value = _get_min_max_value(None, None, o)
|
||||
return IntSliderWidget(value=o, min=min, max=max)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _widget_abbrev(o):
|
||||
"""Make widgets from abbreviations: single values, lists or tuples."""
|
||||
float_or_int = (float, int)
|
||||
if isinstance(o, (list, tuple)):
|
||||
if o and all(isinstance(x, string_types) for x in o):
|
||||
return DropdownWidget(values=[unicode_type(k) for k in o])
|
||||
elif _matches(o, (float_or_int, float_or_int)):
|
||||
min, max, value = _get_min_max_value(o[0], o[1])
|
||||
if all(isinstance(_, int) for _ in o):
|
||||
cls = IntSliderWidget
|
||||
else:
|
||||
cls = FloatSliderWidget
|
||||
return cls(value=value, min=min, max=max)
|
||||
elif _matches(o, (float_or_int, float_or_int, float_or_int)):
|
||||
step = o[2]
|
||||
if step <= 0:
|
||||
raise ValueError("step must be >= 0, not %r" % step)
|
||||
min, max, value = _get_min_max_value(o[0], o[1], step=step)
|
||||
if all(isinstance(_, int) for _ in o):
|
||||
cls = IntSliderWidget
|
||||
else:
|
||||
cls = FloatSliderWidget
|
||||
return cls(value=value, min=min, max=max, step=step)
|
||||
else:
|
||||
return _widget_abbrev_single_value(o)
|
||||
|
||||
def _widget_from_abbrev(abbrev):
|
||||
"""Build a Widget intstance given an abbreviation or Widget."""
|
||||
if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
|
||||
return abbrev
|
||||
|
||||
widget = _widget_abbrev(abbrev)
|
||||
if widget is None:
|
||||
raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
|
||||
return widget
|
||||
|
||||
def _yield_abbreviations_for_parameter(param, kwargs):
|
||||
"""Get an abbreviation for a function parameter."""
|
||||
name = param.name
|
||||
kind = param.kind
|
||||
ann = param.annotation
|
||||
default = param.default
|
||||
empty = Parameter.empty
|
||||
not_found = (None, None)
|
||||
if kind == Parameter.POSITIONAL_OR_KEYWORD:
|
||||
if name in kwargs:
|
||||
yield name, kwargs.pop(name)
|
||||
elif ann is not empty:
|
||||
if default is empty:
|
||||
yield name, ann
|
||||
else:
|
||||
yield name, ann
|
||||
elif default is not empty:
|
||||
yield name, default
|
||||
else:
|
||||
yield not_found
|
||||
elif kind == Parameter.KEYWORD_ONLY:
|
||||
if name in kwargs:
|
||||
yield name, kwargs.pop(name)
|
||||
elif ann is not empty:
|
||||
yield name, ann
|
||||
elif default is not empty:
|
||||
yield name, default
|
||||
else:
|
||||
yield not_found
|
||||
elif kind == Parameter.VAR_KEYWORD:
|
||||
# In this case name=kwargs and we yield the items in kwargs with their keys.
|
||||
for k, v in kwargs.copy().items():
|
||||
kwargs.pop(k)
|
||||
yield k, v
|
||||
|
||||
def _find_abbreviations(f, kwargs):
|
||||
"""Find the abbreviations for a function and kwargs passed to interact."""
|
||||
new_kwargs = []
|
||||
for param in signature(f).parameters.values():
|
||||
for name, value in _yield_abbreviations_for_parameter(param, kwargs):
|
||||
if value is None:
|
||||
raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
|
||||
new_kwargs.append((name, value))
|
||||
return new_kwargs
|
||||
|
||||
def _widgets_from_abbreviations(seq):
|
||||
"""Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
|
||||
result = []
|
||||
for name, abbrev in seq:
|
||||
widget = _widget_from_abbrev(abbrev)
|
||||
widget.description = name
|
||||
result.append(widget)
|
||||
return result
|
||||
|
||||
def interactive(__interact_f, **kwargs):
|
||||
"""Build a group of widgets to interact with a function."""
|
||||
f = __interact_f
|
||||
co = kwargs.pop('clear_output', True)
|
||||
kwargs_widgets = []
|
||||
container = ContainerWidget()
|
||||
container.result = None
|
||||
container.args = []
|
||||
container.kwargs = dict()
|
||||
kwargs = kwargs.copy()
|
||||
|
||||
new_kwargs = _find_abbreviations(f, kwargs)
|
||||
# Before we proceed, let's make sure that the user has passed a set of args+kwargs
|
||||
# that will lead to a valid call of the function. This protects against unspecified
|
||||
# and doubly-specified arguments.
|
||||
getcallargs(f, **{n:v for n,v in new_kwargs})
|
||||
# Now build the widgets from the abbreviations.
|
||||
kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
|
||||
kwargs_widgets.extend(_widgets_from_abbreviations(sorted(kwargs.items(), key = lambda x: x[0])))
|
||||
|
||||
# This has to be done as an assignment, not using container.children.append,
|
||||
# so that traitlets notices the update. We skip any objects (such as fixed) that
|
||||
# are not DOMWidgets.
|
||||
c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
|
||||
container.children = c
|
||||
|
||||
# Build the callback
|
||||
def call_f(name, old, new):
|
||||
container.kwargs = {}
|
||||
for widget in kwargs_widgets:
|
||||
value = widget.value
|
||||
container.kwargs[widget.description] = value
|
||||
if co:
|
||||
clear_output(wait=True)
|
||||
container.result = f(**container.kwargs)
|
||||
|
||||
# Wire up the widgets
|
||||
for widget in kwargs_widgets:
|
||||
widget.on_trait_change(call_f, 'value')
|
||||
|
||||
container.on_displayed(lambda _: call_f(None, None, None))
|
||||
|
||||
return container
|
||||
|
||||
def interact(__interact_f=None, **kwargs):
|
||||
"""interact(f, **kwargs)
|
||||
|
||||
Interact with a function using widgets."""
|
||||
# positional arg support in: https://gist.github.com/8851331
|
||||
if __interact_f is not None:
|
||||
# This branch handles the cases:
|
||||
# 1. interact(f, **kwargs)
|
||||
# 2. @interact
|
||||
# def f(*args, **kwargs):
|
||||
# ...
|
||||
f = __interact_f
|
||||
w = interactive(f, **kwargs)
|
||||
f.widget = w
|
||||
display(w)
|
||||
return f
|
||||
else:
|
||||
# This branch handles the case:
|
||||
# @interact(a=30, b=40)
|
||||
# def f(*args, **kwargs):
|
||||
# ...
|
||||
def dec(f):
|
||||
w = interactive(f, **kwargs)
|
||||
f.widget = w
|
||||
display(w)
|
||||
return f
|
||||
return dec
|
||||
|
||||
class fixed(HasTraits):
|
||||
"""A pseudo-widget whose value is fixed and never synced to the client."""
|
||||
value = Any(help="Any Python object")
|
||||
description = Unicode('', help="Any Python object")
|
||||
def __init__(self, value, **kwargs):
|
||||
super(fixed, self).__init__(value=value, **kwargs)
|
||||
@ -0,0 +1,408 @@
|
||||
"""Test interact and interactive."""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2014 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import nose.tools as nt
|
||||
import IPython.testing.tools as tt
|
||||
|
||||
# from IPython.core.getipython import get_ipython
|
||||
from IPython.html import widgets
|
||||
from IPython.html.widgets import interact, interactive, Widget, interaction
|
||||
from IPython.utils.py3compat import annotate
|
||||
# from IPython.utils.capture import capture_output
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Utility stuff
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class DummyComm(object):
|
||||
comm_id = 'a-b-c-d'
|
||||
def send(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def close(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
_widget_attrs = {}
|
||||
displayed = []
|
||||
|
||||
def setup():
|
||||
_widget_attrs['comm'] = Widget.comm
|
||||
Widget.comm = DummyComm()
|
||||
_widget_attrs['_ipython_display_'] = Widget._ipython_display_
|
||||
def raise_not_implemented(*args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
Widget._ipython_display_ = raise_not_implemented
|
||||
|
||||
def teardown():
|
||||
for attr, value in _widget_attrs.items():
|
||||
setattr(Widget, attr, value)
|
||||
|
||||
def f(**kwargs):
|
||||
pass
|
||||
|
||||
def clear_display():
|
||||
global displayed
|
||||
displayed = []
|
||||
|
||||
def record_display(*args):
|
||||
displayed.extend(args)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Actual tests
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def check_widget(w, **d):
|
||||
"""Check a single widget against a dict"""
|
||||
for attr, expected in d.items():
|
||||
if attr == 'cls':
|
||||
nt.assert_is(w.__class__, expected)
|
||||
else:
|
||||
value = getattr(w, attr)
|
||||
nt.assert_equal(value, expected,
|
||||
"%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected)
|
||||
)
|
||||
|
||||
def check_widgets(container, **to_check):
|
||||
"""Check that widgets are created as expected"""
|
||||
# build a widget dictionary, so it matches
|
||||
widgets = {}
|
||||
for w in container.children:
|
||||
widgets[w.description] = w
|
||||
|
||||
for key, d in to_check.items():
|
||||
nt.assert_in(key, widgets)
|
||||
check_widget(widgets[key], **d)
|
||||
|
||||
|
||||
def test_single_value_string():
|
||||
a = u'hello'
|
||||
c = interactive(f, a=a)
|
||||
w = c.children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.TextWidget,
|
||||
description='a',
|
||||
value=a,
|
||||
)
|
||||
|
||||
def test_single_value_bool():
|
||||
for a in (True, False):
|
||||
c = interactive(f, a=a)
|
||||
w = c.children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.CheckboxWidget,
|
||||
description='a',
|
||||
value=a,
|
||||
)
|
||||
|
||||
def test_single_value_dict():
|
||||
for d in [
|
||||
dict(a=5),
|
||||
dict(a=5, b='b', c=dict),
|
||||
]:
|
||||
c = interactive(f, d=d)
|
||||
w = c.children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.DropdownWidget,
|
||||
description='d',
|
||||
values=d,
|
||||
value=next(iter(d.values())),
|
||||
)
|
||||
|
||||
def test_single_value_float():
|
||||
for a in (2.25, 1.0, -3.5):
|
||||
c = interactive(f, a=a)
|
||||
w = c.children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.FloatSliderWidget,
|
||||
description='a',
|
||||
value=a,
|
||||
min= -a if a > 0 else 3*a,
|
||||
max= 3*a if a > 0 else -a,
|
||||
step=0.1,
|
||||
readout=True,
|
||||
)
|
||||
|
||||
def test_single_value_int():
|
||||
for a in (1, 5, -3):
|
||||
c = interactive(f, a=a)
|
||||
nt.assert_equal(len(c.children), 1)
|
||||
w = c.children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.IntSliderWidget,
|
||||
description='a',
|
||||
value=a,
|
||||
min= -a if a > 0 else 3*a,
|
||||
max= 3*a if a > 0 else -a,
|
||||
step=1,
|
||||
readout=True,
|
||||
)
|
||||
|
||||
def test_list_tuple_2_int():
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(1,1))
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(1,-1))
|
||||
for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
|
||||
c = interactive(f, tup=(min, max), lis=[min, max])
|
||||
nt.assert_equal(len(c.children), 2)
|
||||
d = dict(
|
||||
cls=widgets.IntSliderWidget,
|
||||
min=min,
|
||||
max=max,
|
||||
step=1,
|
||||
readout=True,
|
||||
)
|
||||
check_widgets(c, tup=d, lis=d)
|
||||
|
||||
def test_list_tuple_3_int():
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(1,2,0))
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(1,2,-1))
|
||||
for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
|
||||
c = interactive(f, tup=(min, max, step), lis=[min, max, step])
|
||||
nt.assert_equal(len(c.children), 2)
|
||||
d = dict(
|
||||
cls=widgets.IntSliderWidget,
|
||||
min=min,
|
||||
max=max,
|
||||
step=step,
|
||||
readout=True,
|
||||
)
|
||||
check_widgets(c, tup=d, lis=d)
|
||||
|
||||
def test_list_tuple_2_float():
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(1.0,1.0))
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(0.5,-0.5))
|
||||
for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
|
||||
c = interactive(f, tup=(min, max), lis=[min, max])
|
||||
nt.assert_equal(len(c.children), 2)
|
||||
d = dict(
|
||||
cls=widgets.FloatSliderWidget,
|
||||
min=min,
|
||||
max=max,
|
||||
step=.1,
|
||||
readout=True,
|
||||
)
|
||||
check_widgets(c, tup=d, lis=d)
|
||||
|
||||
def test_list_tuple_3_float():
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(1,2,0.0))
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(-1,-2,1.))
|
||||
with nt.assert_raises(ValueError):
|
||||
c = interactive(f, tup=(1,2.,-1.))
|
||||
for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
|
||||
c = interactive(f, tup=(min, max, step), lis=[min, max, step])
|
||||
nt.assert_equal(len(c.children), 2)
|
||||
d = dict(
|
||||
cls=widgets.FloatSliderWidget,
|
||||
min=min,
|
||||
max=max,
|
||||
step=step,
|
||||
readout=True,
|
||||
)
|
||||
check_widgets(c, tup=d, lis=d)
|
||||
|
||||
def test_list_tuple_str():
|
||||
values = ['hello', 'there', 'guy']
|
||||
first = values[0]
|
||||
dvalues = OrderedDict((v,v) for v in values)
|
||||
c = interactive(f, tup=tuple(values), lis=list(values))
|
||||
nt.assert_equal(len(c.children), 2)
|
||||
d = dict(
|
||||
cls=widgets.DropdownWidget,
|
||||
value=first,
|
||||
values=dvalues
|
||||
)
|
||||
check_widgets(c, tup=d, lis=d)
|
||||
|
||||
def test_list_tuple_invalid():
|
||||
for bad in [
|
||||
(),
|
||||
(5, 'hi'),
|
||||
('hi', 5),
|
||||
({},),
|
||||
(None,),
|
||||
]:
|
||||
with nt.assert_raises(ValueError):
|
||||
print(bad) # because there is no custom message in assert_raises
|
||||
c = interactive(f, tup=bad)
|
||||
|
||||
def test_defaults():
|
||||
@annotate(n=10)
|
||||
def f(n, f=4.5):
|
||||
pass
|
||||
|
||||
c = interactive(f)
|
||||
check_widgets(c,
|
||||
n=dict(
|
||||
cls=widgets.IntSliderWidget,
|
||||
value=10,
|
||||
),
|
||||
f=dict(
|
||||
cls=widgets.FloatSliderWidget,
|
||||
value=4.5,
|
||||
),
|
||||
)
|
||||
|
||||
def test_annotations():
|
||||
@annotate(n=10, f=widgets.FloatTextWidget())
|
||||
def f(n, f):
|
||||
pass
|
||||
|
||||
c = interactive(f)
|
||||
check_widgets(c,
|
||||
n=dict(
|
||||
cls=widgets.IntSliderWidget,
|
||||
value=10,
|
||||
),
|
||||
f=dict(
|
||||
cls=widgets.FloatTextWidget,
|
||||
),
|
||||
)
|
||||
|
||||
def test_priority():
|
||||
@annotate(annotate='annotate', kwarg='annotate')
|
||||
def f(kwarg='default', annotate='default', default='default'):
|
||||
pass
|
||||
|
||||
c = interactive(f, kwarg='kwarg')
|
||||
check_widgets(c,
|
||||
kwarg=dict(
|
||||
cls=widgets.TextWidget,
|
||||
value='kwarg',
|
||||
),
|
||||
annotate=dict(
|
||||
cls=widgets.TextWidget,
|
||||
value='annotate',
|
||||
),
|
||||
)
|
||||
|
||||
@nt.with_setup(clear_display)
|
||||
def test_decorator_kwarg():
|
||||
with tt.monkeypatch(interaction, 'display', record_display):
|
||||
@interact(a=5)
|
||||
def foo(a):
|
||||
pass
|
||||
nt.assert_equal(len(displayed), 1)
|
||||
w = displayed[0].children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.IntSliderWidget,
|
||||
value=5,
|
||||
)
|
||||
|
||||
@nt.with_setup(clear_display)
|
||||
def test_decorator_no_call():
|
||||
with tt.monkeypatch(interaction, 'display', record_display):
|
||||
@interact
|
||||
def foo(a='default'):
|
||||
pass
|
||||
nt.assert_equal(len(displayed), 1)
|
||||
w = displayed[0].children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.TextWidget,
|
||||
value='default',
|
||||
)
|
||||
|
||||
@nt.with_setup(clear_display)
|
||||
def test_call_interact():
|
||||
def foo(a='default'):
|
||||
pass
|
||||
with tt.monkeypatch(interaction, 'display', record_display):
|
||||
ifoo = interact(foo)
|
||||
nt.assert_equal(len(displayed), 1)
|
||||
w = displayed[0].children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.TextWidget,
|
||||
value='default',
|
||||
)
|
||||
|
||||
@nt.with_setup(clear_display)
|
||||
def test_call_interact_kwargs():
|
||||
def foo(a='default'):
|
||||
pass
|
||||
with tt.monkeypatch(interaction, 'display', record_display):
|
||||
ifoo = interact(foo, a=10)
|
||||
nt.assert_equal(len(displayed), 1)
|
||||
w = displayed[0].children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.IntSliderWidget,
|
||||
value=10,
|
||||
)
|
||||
|
||||
@nt.with_setup(clear_display)
|
||||
def test_call_decorated_on_trait_change():
|
||||
"""test calling @interact decorated functions"""
|
||||
d = {}
|
||||
with tt.monkeypatch(interaction, 'display', record_display):
|
||||
@interact
|
||||
def foo(a='default'):
|
||||
d['a'] = a
|
||||
return a
|
||||
nt.assert_equal(len(displayed), 1)
|
||||
w = displayed[0].children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.TextWidget,
|
||||
value='default',
|
||||
)
|
||||
# test calling the function directly
|
||||
a = foo('hello')
|
||||
nt.assert_equal(a, 'hello')
|
||||
nt.assert_equal(d['a'], 'hello')
|
||||
|
||||
# test that setting trait values calls the function
|
||||
w.value = 'called'
|
||||
nt.assert_equal(d['a'], 'called')
|
||||
|
||||
@nt.with_setup(clear_display)
|
||||
def test_call_decorated_kwargs_on_trait_change():
|
||||
"""test calling @interact(foo=bar) decorated functions"""
|
||||
d = {}
|
||||
with tt.monkeypatch(interaction, 'display', record_display):
|
||||
@interact(a='kwarg')
|
||||
def foo(a='default'):
|
||||
d['a'] = a
|
||||
return a
|
||||
nt.assert_equal(len(displayed), 1)
|
||||
w = displayed[0].children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.TextWidget,
|
||||
value='kwarg',
|
||||
)
|
||||
# test calling the function directly
|
||||
a = foo('hello')
|
||||
nt.assert_equal(a, 'hello')
|
||||
nt.assert_equal(d['a'], 'hello')
|
||||
|
||||
# test that setting trait values calls the function
|
||||
w.value = 'called'
|
||||
nt.assert_equal(d['a'], 'called')
|
||||
|
||||
def test_fixed():
|
||||
c = interactive(f, a=widgets.fixed(5), b='text')
|
||||
nt.assert_equal(len(c.children), 1)
|
||||
w = c.children[0]
|
||||
check_widget(w,
|
||||
cls=widgets.TextWidget,
|
||||
value='text',
|
||||
description='b',
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,269 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": ""
|
||||
},
|
||||
"nbformat": 3,
|
||||
"nbformat_minor": 0,
|
||||
"worksheets": [
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "heading",
|
||||
"level": 1,
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Exploring the Lorenz System of Differential Equations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this Notebook we explore the Lorenz system of differential equations:\n",
|
||||
"\n",
|
||||
"$$\n",
|
||||
"\\begin{aligned}\n",
|
||||
"\\dot{x} & = \\sigma(y-x) \\\\\n",
|
||||
"\\dot{y} & = \\rho x - y - xz \\\\\n",
|
||||
"\\dot{z} & = -\\beta z + xy\n",
|
||||
"\\end{aligned}\n",
|
||||
"$$\n",
|
||||
"\n",
|
||||
"This is one of the classic systems in non-linear differential equations. It exhibits a range of different behaviors as the parameters ($\\sigma$, $\\beta$, $\\rho$) are varied."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "heading",
|
||||
"level": 2,
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Imports"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"First, we import the needed things from IPython, NumPy, Matplotlib and SciPy."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"%pylab inline"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from IPython.html.widgets.interact import interact, interactive\n",
|
||||
"from IPython.display import clear_output, display, HTML"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"import numpy as np\n",
|
||||
"from scipy import integrate\n",
|
||||
"\n",
|
||||
"from matplotlib import pyplot as plt\n",
|
||||
"from mpl_toolkits.mplot3d import Axes3D\n",
|
||||
"from matplotlib.colors import cnames\n",
|
||||
"from matplotlib import animation"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "heading",
|
||||
"level": 2,
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Computing the trajectories and plotting the result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We define a function that can integrate the differential equations numerically and then plot the solutions. This function has arguments that control the parameters of the differential equation ($\\sigma$, $\\beta$, $\\rho$), the numerical integration (`N`, `max_time`) and the visualization (`angle`)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"def solve_lorenz(N=10, angle=0.0, max_time=4.0, sigma=10.0, beta=8./3, rho=28.0):\n",
|
||||
"\n",
|
||||
" fig = plt.figure()\n",
|
||||
" ax = fig.add_axes([0, 0, 1, 1], projection='3d')\n",
|
||||
" ax.axis('off')\n",
|
||||
"\n",
|
||||
" # prepare the axes limits\n",
|
||||
" ax.set_xlim((-25, 25))\n",
|
||||
" ax.set_ylim((-35, 35))\n",
|
||||
" ax.set_zlim((5, 55))\n",
|
||||
" \n",
|
||||
" def lorenz_deriv((x, y, z), t0, sigma=sigma, beta=beta, rho=rho):\n",
|
||||
" \"\"\"Compute the time-derivative of a Lorentz system.\"\"\"\n",
|
||||
" return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z]\n",
|
||||
"\n",
|
||||
" # Choose random starting points, uniformly distributed from -15 to 15\n",
|
||||
" np.random.seed(1)\n",
|
||||
" x0 = -15 + 30 * np.random.random((N, 3))\n",
|
||||
"\n",
|
||||
" # Solve for the trajectories\n",
|
||||
" t = np.linspace(0, max_time, int(250*max_time))\n",
|
||||
" x_t = np.asarray([integrate.odeint(lorenz_deriv, x0i, t)\n",
|
||||
" for x0i in x0])\n",
|
||||
" \n",
|
||||
" # choose a different color for each trajectory\n",
|
||||
" colors = plt.cm.jet(np.linspace(0, 1, N))\n",
|
||||
"\n",
|
||||
" for i in range(N):\n",
|
||||
" x, y, z = x_t[i,:,:].T\n",
|
||||
" lines = ax.plot(x, y, z, '-', c=colors[i])\n",
|
||||
" setp(lines, linewidth=2)\n",
|
||||
"\n",
|
||||
" ax.view_init(30, angle)\n",
|
||||
" show()\n",
|
||||
"\n",
|
||||
" return t, x_t"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's call the function once to view the solutions. For this set of parameters, we see the trajectories swirling around two points, called attractors. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"t, x_t = solve_lorenz(angle=0, N=10)"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Using IPython's `interactive` function, we can explore how the trajectories behave as we change the various parameters."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"w = interactive(solve_lorenz, angle=(0.,360.), N=(0,50), sigma=(0.0,50.0), rho=(0.0,50.0))\n",
|
||||
"display(w)"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The object returned by `interactive` is a `Widget` object and it has attributes that contain the current result and arguments:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"t, x_t = w.result"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"w.arguments"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"After interacting with the system, we can take the result and perform further computations. In this case, we compute the average positions in $x$, $y$ and $z$."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"xyz_avg = x_t.mean(axis=1)"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"xyz_avg.shape"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Creating histograms of the average positions (across different trajectories) show that on average the trajectories swirl about the attractors."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"hist(xyz_avg[:,0])\n",
|
||||
"title('Average $x(t)$')"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"hist(xyz_avg[:,1])\n",
|
||||
"title('Average $y(t)$')"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
}
|
||||
],
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in new issue