~ubuntu-branches/ubuntu/utopic/python-chaco/utopic

« back to all changes in this revision

Viewing changes to docs/source/user_manual/tutorial_van_der_waal.rst

  • Committer: Package Import Robot
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2014-06-01 17:04:08 UTC
  • mfrom: (7.2.5 sid)
  • Revision ID: package-import@ubuntu.com-20140601170408-m86xvdjd83a4qon0
Tags: 4.4.1-1ubuntu1
* Merge from Debian unstable. Remaining Ubuntu changes:
 - Let the binary-predeb target work on the usr/lib/python* directory
   as we don't have usr/share/pyshared anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
2
 
.. _tutorial_van_der_waal:
3
 
 
4
 
######################################################
5
 
Modeling Van der Waal's Equation With Chaco and Traits
6
 
######################################################
7
 
 
8
 
Overview
9
 
========
10
 
 
11
 
This tutorial walks through the creation of an example program that plots a
12
 
scientific equation.  In particular, we will model `Van Der Waal's Equation
13
 
<http://en.wikipedia.org/wiki/Van_der_Waals_equation>`_, which is a
14
 
modification to the ideal gas law that takes into account the nonzero size of
15
 
molecules and the attraction to each other that they experience.
16
 
 
17
 
 
18
 
Development Setup
19
 
=================
20
 
 
21
 
In review, Traits is a manifest typing and reactive programming package for
22
 
Python. It also provides UI features that will be used to create a simple GUI.
23
 
The Traits and Traits UI user manuals are good resources for learning about the
24
 
packages and can be found on the 
25
 
`Traits Wiki <https://svn.enthought.com/enthought/wiki/Traits>`_. The wiki 
26
 
includes features, technical notes, cookbooks, FAQ and more.
27
 
 
28
 
You must have Chaco and its dependencies installed:
29
 
 
30
 
* Traits
31
 
* TraitsGUI
32
 
* Enable
33
 
 
34
 
 
35
 
Writing the Program
36
 
===================
37
 
 
38
 
First, define a Traits class and the elements necessary need to model
39
 
the task.  The following Traits class is made for the Van Der Waal
40
 
equation, whose variables can be viewed on 
41
 
`this wiki page <http://en.wikipedia.org/wiki/Van_der_Waals_equation>`_.  The
42
 
:attr:`volume` and :attr:`pressure` attributes hold lists of our X- and 
43
 
Y-coordinates, respectively, and are defined as arrays. The attributes 
44
 
:attr:`attraction` and :attr:`totVolume` are  input parameters specified by the
45
 
user.  The type of the variables dictates their appearance in the GUI.  For
46
 
example, :attr:`attraction` and :attr:`totVolume` are defined as Ranges, so they
47
 
show up as slider bars.  Likewise, :attr:`plot_type` is shown as a drop-down
48
 
list, since it is defined as an Enum.
49
 
 
50
 
::
51
 
 
52
 
    # We'll also import a few things to be used later.
53
 
    from traits.api \
54
 
        import HasTraits, Array, Range, Float, Enum, on_trait_change, Property
55
 
    from traitsui.api import View, Item 
56
 
    from chaco.chaco_plot_editor import ChacoPlotItem
57
 
    from numpy import arange
58
 
    
59
 
    class Data(HasTraits):
60
 
        volume = Array
61
 
        pressure = Array
62
 
        attraction = Range(low=-50.0,high=50.0,value=0.0)
63
 
        totVolume = Range(low=.01,high=100.0,value=0.01)
64
 
        temperature = Range(low=-50.0,high=50.0,value=50.0)
65
 
        r_constant= Float(8.314472)
66
 
        plot_type = Enum("line", "scatter")
67
 
    
68
 
    ....    
69
 
 
70
 
 
71
 
Creating the View
72
 
=================
73
 
 
74
 
The main GUI window is created by defining a Traits :class:`View` instance.
75
 
This View contains all of the GUI elements, including the plot.  To
76
 
link a variable with a widget element on the GUI, we create a Traits
77
 
:class:`Item` instance with the same name as the variable and pass it as an
78
 
argument of the Traits View instance declaration.  The 
79
 
`Traits UI User Guide <https://svn.enthought.com/svn/enthought/Traits/tags/traits_2.0.1b1/docs/Traits%20UI%20User%20Guide.pdf>`_
80
 
discusses the View and Item objects in depth. In order to
81
 
embed a Chaco plot into a Traits View, you need to import the
82
 
:class:`ChacoPlotItem` class, which can be passed as a parameter to View just
83
 
like the Item objects. The first two arguments to ChacoPlotItem are the
84
 
lists of X- and Y-coordinates for the graph.  The attributes :attr:`volume` and
85
 
:attr:`pressure` hold the lists of X- and Y-coordinates, and therefore are the
86
 
first two arguments to Chaco2PlotItem.  Other parameters have been
87
 
provided to the plot for additional customization::
88
 
 
89
 
    class Data(HasTraits):
90
 
        ....
91
 
    
92
 
        traits_view = View(ChacoPlotItem("volume", "pressure",
93
 
                                   type_trait="plot_type",
94
 
                                   resizable=True,
95
 
                                   x_label="Volume",
96
 
                                   y_label="Pressure",
97
 
                                   x_bounds=(-10,120),
98
 
                                   x_auto=False,
99
 
                                   y_bounds=(-2000,4000),
100
 
                                   y_auto=False,
101
 
                                   color="blue",
102
 
                                   bgcolor="white",
103
 
                                   border_visible=True,
104
 
                                   border_width=1,
105
 
                                   title='Pressure vs. Volume',
106
 
                                   padding_bg_color="lightgray"),
107
 
                           Item(name='attraction'),
108
 
                           Item(name='totVolume'),
109
 
                           Item(name='temperature'),   
110
 
                           Item(name='r_constant', style='readonly'),
111
 
                           Item(name='plot_type'),
112
 
                           resizable = True,
113
 
                           buttons = ["OK"],
114
 
                           title='Van der Waal Equation',
115
 
                           width=900, height=800)
116
 
    ....
117
 
 
118
 
    
119
 
Updating the Plot
120
 
=================
121
 
 
122
 
The power of Traits and Chaco enables the plot to update itself
123
 
whenever the X- or Y-arrays are changed.  So, we need a function to
124
 
re-calculate the X- and Y-coordinate lists whenever the input
125
 
parameters are changed by the user moving the sliders in the GUI.
126
 
 
127
 
The :attr:`volume` attribute is the independent variable and :attr:`pressure` is
128
 
the dependent variable. The relationship between pressure and volume, as derived
129
 
from the equation found on the wiki page, is::
130
 
 
131
 
               r_constant * Temperature       attraction
132
 
   Pressure =  ------------------------   -   ----------
133
 
                  Volume - totVolume          Volume**2
134
 
 
135
 
 
136
 
Next, there are two programing tasks to complete:
137
 
 
138
 
1. Define trait listener methods for your input parameters. These
139
 
   methods are automatically called whenever the parameters are
140
 
   changed, since it will be time to recalculate the :attr:`pressure` array.
141
 
 
142
 
2. Write a calculation method that updates your lists of X- and
143
 
   Y-coordinates for your plot.
144
 
 
145
 
The following is the code for these two needs::
146
 
 
147
 
    # Re-calculate when attraction, totVolume, or temperature are changed.
148
 
    @on_trait_change('attraction, totVolume, temperature')
149
 
    def calc(self):
150
 
        """ Update the data based on the numbers specified by the user. """
151
 
        self.volume = arange(.1, 100)
152
 
        self.pressure = ((self.r_constant*self.temperature) 
153
 
                         /(self.volume - self.totVolume)   
154
 
                        -(self.attraction/(self.volume*self.volume)))
155
 
        return
156
 
 
157
 
The :func:`calc` function computes the :attr:`pressure` array using the current
158
 
values of the independent variables.  Meanwhile, the
159
 
:func:`@on_trait_change` decorator (provided by Traits) tells Python to call
160
 
:func:`calc` whenever any of the attributes :attr:`attraction`, 
161
 
:attr:`totVolume`, or :attr:`temperature` changes.
162
 
 
163
 
 
164
 
Testing your Program
165
 
====================
166
 
 
167
 
The application is complete, and can be tested by instantiating a copy
168
 
of the class and then creating the view by calling the
169
 
:meth:`configure_traits` method on the class.  For a simple test, run these
170
 
lines from an interpreter or a separate module::
171
 
 
172
 
    from vanderwaals import Data
173
 
    viewer = Data()
174
 
    viewer.calc()            # Must calculate the initial (x,y) lists
175
 
    viewer.configure_traits()
176
 
 
177
 
Clicking and dragging on the sliders in the GUI dynamically updates the pressure
178
 
data array, and causes the plot to update, showing the new values.
179
 
 
180
 
Screenshots
181
 
===========
182
 
 
183
 
Here is what the program looks like:
184
 
 
185
 
.. image:: images/vanderwaals.png
186
 
 
187
 
 
188
 
But it could be better....
189
 
==========================
190
 
 
191
 
It seems inconvenient to have to call a calculation function manually
192
 
before we call :meth:`configure_traits`.  Also, the pressure equation depends on
193
 
the values of other variables. It would be nice to make the
194
 
relationship between the dependant and independent variables clearer.
195
 
There is another way we could define our variables that is easier for
196
 
the user to understand, and provides better source documentation.
197
 
 
198
 
Since our X-values remain constant in this example, it is wasteful to
199
 
keep recreating the :attr:`volume` array.  The Y-array, :attr:`pressure`, is the
200
 
single array that needs to be updated when the independent variables
201
 
change. So, instead of defining :attr:`pressure` as an :class:`Array`, we define
202
 
it as a :class:`Property`. Property is a Traits type that allows you to define
203
 
a variable whose value is recalculated whenever it is requested. In
204
 
addition, when the *depends_on* argument of a Property constructor is
205
 
set to list of traits in your :class:`HasTraits` class, the property's trait
206
 
events fire whenever any of the dependent trait's change events
207
 
fire. This means that the :attr:`pressure` attribute fires a trait change
208
 
whenever our *depends_on* traits are changed. Meanwhile, the Chaco plot
209
 
is automatically listening to the :attr:`pressure` attribute, so the plot
210
 
display gets the new value of :attr:`pressure` whenever someone changes
211
 
the input parameters!
212
 
 
213
 
When the value of a Property trait is requested, the
214
 
:samp:`\_get_{trait_name}` method is called to calculate and return its
215
 
current value. So we define use the :meth:`_get_pressure` method as our new
216
 
calculation method.  It is important to note that this implementation
217
 
does have a weakness.  Since we are calculating new pressures each
218
 
time someone changes the value of the input variables, this could slow
219
 
down the program if the calculation is long.  When the user drags a
220
 
slider widget, each stopping point along the slider requests a
221
 
recompute.
222
 
 
223
 
For the new implementation, these are the necessary changes:
224
 
 
225
 
1. Define the Y-coordinate array variable as a Property instead of an
226
 
   Array.
227
 
2. Perform the calculations in the :samp:`\_get_{trait_name}` method for the 
228
 
   Y-coordinate array variable, which is :meth:`_get_pressure` in this 
229
 
   example.
230
 
3. Define the :samp:`\_{trait}_default` method to set the initial value of
231
 
   the X-coordinate array, so :meth:`\_get_pressure` does not have to keep 
232
 
   recalculating it.
233
 
4. Remove the previous :func:`@on_trait_change` decorator and calculation 
234
 
   method.
235
 
 
236
 
The new pieces of code to add to the Data class are::
237
 
 
238
 
    class Data(HasTraits):
239
 
        ...
240
 
        pressure = Property(Array, depends_on=['temperature', 
241
 
                                               'attraction', 
242
 
                                               'totVolume'])
243
 
        ...
244
 
    
245
 
        def _volume_default(self):
246
 
          return arange(.1, 100)
247
 
    
248
 
        # Pressure is recalculated whenever one of the elements the property 
249
 
        # depends on changes.  No need to use @on_trait_change.
250
 
        def _get_pressure(self):
251
 
          return ((self.r_constant*self.temperature)
252
 
                  /(self.volume - self.totVolume) 
253
 
                 -(self.attraction/(self.volume*self.volume)))
254
 
 
255
 
You now no longer have to call an inconvenient calculation function
256
 
before the first call to :meth:`configure_traits`!  
257
 
 
258
 
 
259
 
Source Code
260
 
===========
261
 
 
262
 
The final version on the program, `vanderwaals.py` ::
263
 
 
264
 
    from traits.api \
265
 
        import HasTraits, Array, Range, Float, Enum, on_trait_change, Property
266
 
    from traitsui.api import View, Item
267
 
    from chaco.chaco_plot_editor import ChacoPlotItem
268
 
    from numpy import arange
269
 
    
270
 
    class Data(HasTraits):
271
 
        volume = Array
272
 
        pressure = Property(Array, depends_on=['temperature', 'attraction', 
273
 
                                           'totVolume'])
274
 
        attraction = Range(low=-50.0,high=50.0,value=0.0)
275
 
        totVolume = Range(low=.01,high=100.0,value=0.01)
276
 
        temperature = Range(low=-50.0,high=50.0,value=50.0)
277
 
        r_constant= Float(8.314472)
278
 
        plot_type = Enum("line", "scatter")
279
 
    
280
 
        traits_view = View(ChacoPlotItem("volume", "pressure",
281
 
                                   type_trait="plot_type",
282
 
                                   resizable=True,
283
 
                                   x_label="Volume",
284
 
                                   y_label="Pressure",
285
 
                                   x_bounds=(-10,120),
286
 
                                   x_auto=False,
287
 
                                   y_bounds=(-2000,4000),
288
 
                                   y_auto=False,
289
 
                                   color="blue",
290
 
                                   bgcolor="white",
291
 
                                   border_visible=True,
292
 
                                   border_width=1,
293
 
                                   title='Pressure vs. Volume',
294
 
                                   padding_bg_color="lightgray"),
295
 
                           Item(name='attraction'),
296
 
                           Item(name='totVolume'),
297
 
                           Item(name='temperature'),   
298
 
                           Item(name='r_constant', style='readonly'),
299
 
                           Item(name='plot_type'),
300
 
                           resizable = True,
301
 
                           buttons = ["OK"],
302
 
                           title='Van der Waal Equation',
303
 
                           width=900, height=800)
304
 
    
305
 
    
306
 
        def _volume_default(self):
307
 
            """ Default handler for volume Trait Array. """
308
 
            return arange(.1, 100)
309
 
    
310
 
        def _get_pressure(self):
311
 
            """Recalculate when one a trait the property depends on changes."""
312
 
            return ((self.r_constant*self.temperature)
313
 
                  /(self.volume - self.totVolume) 
314
 
                 -(self.attraction/(self.volume*self.volume)))
315
 
    
316
 
    if __name__ == '__main__':
317
 
        viewer = Data()
318
 
        viewer.configure_traits()