2
.. _tutorial_van_der_waal:
4
######################################################
5
Modeling Van der Waal's Equation With Chaco and Traits
6
######################################################
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.
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.
28
You must have Chaco and its dependencies installed:
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.
52
# We'll also import a few things to be used later.
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
59
class Data(HasTraits):
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")
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::
89
class Data(HasTraits):
92
traits_view = View(ChacoPlotItem("volume", "pressure",
93
type_trait="plot_type",
99
y_bounds=(-2000,4000),
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'),
114
title='Van der Waal Equation',
115
width=900, height=800)
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.
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::
131
r_constant * Temperature attraction
132
Pressure = ------------------------ - ----------
133
Volume - totVolume Volume**2
136
Next, there are two programing tasks to complete:
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.
142
2. Write a calculation method that updates your lists of X- and
143
Y-coordinates for your plot.
145
The following is the code for these two needs::
147
# Re-calculate when attraction, totVolume, or temperature are changed.
148
@on_trait_change('attraction, totVolume, temperature')
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)))
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.
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::
172
from vanderwaals import Data
174
viewer.calc() # Must calculate the initial (x,y) lists
175
viewer.configure_traits()
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.
183
Here is what the program looks like:
185
.. image:: images/vanderwaals.png
188
But it could be better....
189
==========================
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.
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!
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
223
For the new implementation, these are the necessary changes:
225
1. Define the Y-coordinate array variable as a Property instead of an
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
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
233
4. Remove the previous :func:`@on_trait_change` decorator and calculation
236
The new pieces of code to add to the Data class are::
238
class Data(HasTraits):
240
pressure = Property(Array, depends_on=['temperature',
245
def _volume_default(self):
246
return arange(.1, 100)
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)))
255
You now no longer have to call an inconvenient calculation function
256
before the first call to :meth:`configure_traits`!
262
The final version on the program, `vanderwaals.py` ::
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
270
class Data(HasTraits):
272
pressure = Property(Array, depends_on=['temperature', 'attraction',
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")
280
traits_view = View(ChacoPlotItem("volume", "pressure",
281
type_trait="plot_type",
287
y_bounds=(-2000,4000),
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'),
302
title='Van der Waal Equation',
303
width=900, height=800)
306
def _volume_default(self):
307
""" Default handler for volume Trait Array. """
308
return arange(.1, 100)
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)))
316
if __name__ == '__main__':
318
viewer.configure_traits()