basics.do.txt 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. # #ifdef PRIMER_BOOK
  2. idx{`pysketcher`}
  3. Implementing a drawing program provides a very good example on the
  4. usefulness of object-oriented programming. In the following we shall
  5. develop the simpler parts of a relatively small and compact drawing
  6. program for making sketches of the type shown in Figure
  7. ref{sketcher:fig:inclinedplane}. This is a typical *principal sketch*
  8. of a physics problem, here involving a rolling wheel on an inclined
  9. plane. The sketch
  10. # #else
  11. 2DO:
  12. * two wheels of different radii on inclined plane coupled to
  13. correct solution
  14. * "Pygame backend": "http://inventwithpython.com/chapter17.html"
  15. ======= A First Glimpse of Pysketcher =======
  16. Formulation of physical problems makes heavy use of *principal sketches*
  17. such as the one in Figure ref{sketcher:fig:inclinedplane}.
  18. This particular sketch illustrates the classical mechanics problem
  19. of a rolling wheel on an inclined plane.
  20. The figure
  21. # #endif
  22. is made up many individual elements: a rectangle
  23. filled with a pattern (the inclined plane), a hollow circle with color
  24. (the wheel), arrows with labels (the $N$ and $Mg$ forces, and the $x$
  25. axis), an angle with symbol $\theta$, and a dashed line indicating the
  26. starting location of the wheel.
  27. Drawing software and plotting programs can produce such figures quite
  28. easily in principle, but the amount of details the user needs to
  29. control with the mouse can be substantial. Software more tailored to
  30. producing sketches of this type would work with more convenient
  31. abstractions, such as circle, wall, angle, force arrow, axis, and so
  32. forth. And as soon we start *programming* to construct the figure we
  33. get a range of other powerful tools at disposal. For example, we can
  34. easily translate and rotate parts of the figure and make an animation
  35. that illustrates the physics of the problem.
  36. Programming as a superior alternative to interactive drawing is
  37. the mantra of this section.
  38. FIGURE: [fig-tut/wheel_on_inclined_plane, width=400 frac=0.5] Sketch of a physics problem. label{sketcher:fig:inclinedplane}
  39. # #ifdef PRIMER_BOOK
  40. Classes are very suitable for implementing the various components that
  41. build up a sketch. In particular, we shall demonstrate that as soon as
  42. some classes are established, more are easily added. Enhanced
  43. functionality for all the classes is also easy to implement in common,
  44. generic code that can immediately be shared by all present and future
  45. classes.
  46. The fundamental data structure involved in this case study is a
  47. hierarchical tree, and much of the material on implementation issues
  48. targets how to traverse tree structures with recursive function calls.
  49. This topic is of key relevance in a wide range of other applications
  50. as well.
  51. ===== Using the Object Collection =====
  52. We start by demonstrating a convenient user interface for making
  53. sketches of the type in Figure ref{sketcher:fig:inclinedplane}.
  54. However, it is more appropriate to start with a significantly simpler
  55. example as depicted in Figure ref{sketcher:fig:vehicle0}. This toy
  56. sketch consists of several elements: two circles, two rectangles, and
  57. a ``ground'' element.
  58. # #else
  59. ===== Basic Construction of Sketches =====
  60. Before attacking real-life sketches as in Figure ref{sketcher:fig:inclinedplane}
  61. we focus on the significantly simpler drawing shown
  62. in Figure ref{sketcher:fig:vehicle0}. This toy sketch consists of
  63. several elements: two circles, two rectangles, and a ``ground'' element.
  64. # #endif
  65. FIGURE: [fig-tut/vehicle0_dim, width=600] Sketch of a simple figure. label{sketcher:fig:vehicle0}
  66. When the sketch is defined in terms of computer code, it is natural to
  67. parameterize geometric features, such as the radius of the wheel ($R$),
  68. the center point of the left wheel ($w_1$), as well as the height ($H$) and
  69. length ($L$) of the main part. The simple vehicle in
  70. Figure ref{sketcher:fig:vehicle0} is quickly drawn in almost any interactive
  71. tool. However, if we want to change the radius of the wheels, you need a
  72. sophisticated drawing tool to avoid redrawing the whole figure, while
  73. in computer code this is a matter of changing the $R$ parameter and
  74. rerunning the program.
  75. For example, Figure ref{sketcher:fig:vehicle0b} shows
  76. a variation of the drawing in
  77. Figure ref{sketcher:fig:vehicle0} obtained by just setting
  78. $R=0.5$, $L=5$, $H=2$, and $R=2$. Being able
  79. to quickly change geometric sizes is key to many problem settings in
  80. physics and engineering, but then a program must define the geometry.
  81. FIGURE: [fig-tut/vehicle_v2, width=500 frac=0.6] Redrawing a figure with other geometric parameters. label{sketcher:fig:vehicle0b}
  82. === Basic Drawing ===
  83. A typical program creating these five elements is shown next.
  84. After importing the `pysketcher` package, the first task is always to
  85. define a coordinate system:
  86. !bc pycod
  87. from pysketcher import *
  88. drawing_tool.set_coordinate_system(
  89. xmin=0, xmax=10, ymin=-1, ymax=8)
  90. !ec
  91. Instead of working with lengths expressed by specific numbers it is
  92. highly recommended to use variables to parameterize lengths as
  93. this makes it easier to change dimensions later.
  94. Here we introduce some key lengths for the radius of the wheels,
  95. distance between the wheels, etc.:
  96. !bc pycod
  97. R = 1 # radius of wheel
  98. L = 4 # distance between wheels
  99. H = 2 # height of vehicle body
  100. w_1 = 5 # position of front wheel
  101. drawing_tool.set_coordinate_system(xmin=0, xmax=w_1 + 2*L + 3*R,
  102. ymin=-1, ymax=2*R + 3*H)
  103. !ec
  104. With the drawing area in place we can make the first `Circle` object
  105. in an intuitive fashion:
  106. !bc pycod
  107. wheel1 = Circle(center=(w_1, R), radius=R)
  108. !ec
  109. to change dimensions later.
  110. To translate the geometric information about the `wheel1` object to
  111. instructions for the plotting engine (in this case Matplotlib), one calls the
  112. `wheel1.draw()`. To display all drawn objects, one issues
  113. `drawing_tool.display()`. The typical steps are hence:
  114. !bc pycod
  115. wheel1 = Circle(center=(w_1, R), radius=R)
  116. wheel1.draw()
  117. # Define other objects and call their draw() methods
  118. drawing_tool.display()
  119. drawing_tool.savefig('tmp.png') # store picture
  120. !ec
  121. The next wheel can be made by taking a copy of `wheel1` and
  122. translating the object to the right according to a
  123. displacement vector $(L,0)$:
  124. !bc pycod
  125. wheel2 = wheel1.copy()
  126. wheel2.translate((L,0))
  127. !ec
  128. The two rectangles are also made in an intuitive way:
  129. !bc pycod
  130. under = Rectangle(lower_left_corner=(w_1-2*R, 2*R),
  131. width=2*R + L + 2*R, height=H)
  132. over = Rectangle(lower_left_corner=(w_1, 2*R + H),
  133. width=2.5*R, height=1.25*H)
  134. !ec
  135. === Groups of Objects ===
  136. Instead of calling the `draw` method of every object, we can
  137. group objects and call `draw`, or perform other operations, for
  138. the whole group. For example, we may collect the two wheels
  139. in a `wheels` group and the `over` and `under` rectangles
  140. in a `body` group. The whole vehicle is a composition
  141. of its `wheels` and `body` groups. The code goes like
  142. !bc pycod
  143. wheels = Composition({'wheel1': wheel1, 'wheel2': wheel2})
  144. body = Composition({'under': under, 'over': over})
  145. vehicle = Composition({'wheels': wheels, 'body': body})
  146. !ec
  147. The ground is illustrated by an object of type `Wall`,
  148. mostly used to indicate walls in sketches of mechanical systems.
  149. A `Wall` takes the `x` and `y` coordinates of some curve,
  150. and a `thickness` parameter, and creates a thick curve filled
  151. with a simple pattern. In this case the curve is just a flat
  152. line so the construction is made of two points on the
  153. ground line ($(w_1-L,0)$ and $(w_1+3L,0)$):
  154. !bc pycod
  155. ground = Wall(x=[w_1 - L, w_1 + 3*L], y=[0, 0], thickness=-0.3*R)
  156. !ec
  157. The negative thickness makes the pattern-filled rectangle appear below
  158. the defined line, otherwise it appears above.
  159. We may now collect all the objects in a ``top'' object that contains
  160. the whole figure:
  161. !bc pycod
  162. fig = Composition({'vehicle': vehicle, 'ground': ground})
  163. fig.draw() # send all figures to plotting backend
  164. drawing_tool.display()
  165. drawing_tool.savefig('tmp.png')
  166. !ec
  167. The `fig.draw()` call will visit
  168. all subgroups, their subgroups,
  169. and so forth in the hierarchical tree structure of
  170. figure elements,
  171. and call `draw` for every object.
  172. === Changing Line Styles and Colors ===
  173. Controlling the line style, line color, and line width is
  174. fundamental when designing figures. The `pysketcher`
  175. package allows the user to control such properties in
  176. single objects, but also set global properties that are
  177. used if the object has no particular specification of
  178. the properties. Setting the global properties are done like
  179. !bc pycod
  180. drawing_tool.set_linestyle('dashed')
  181. drawing_tool.set_linecolor('black')
  182. drawing_tool.set_linewidth(4)
  183. !ec
  184. At the object level the properties are specified in a similar
  185. way:
  186. !bc pycod
  187. wheels.set_linestyle('solid')
  188. wheels.set_linecolor('red')
  189. !ec
  190. and so on.
  191. Geometric figures can be specified as *filled*, either with a color or with a
  192. special visual pattern:
  193. !bc pycod
  194. # Set filling of all curves
  195. drawing_tool.set_filled_curves(color='blue', pattern='/')
  196. # Turn off filling of all curves
  197. drawing_tool.set_filled_curves(False)
  198. # Fill the wheel with red color
  199. wheel1.set_filled_curves('red')
  200. !ec
  201. # http://packages.python.org/ete2/ for visualizing tree structures!
  202. === The Figure Composition as an Object Hierarchy ===
  203. The composition of objects making up the figure
  204. is hierarchical, similar to a family, where
  205. each object has a parent and a number of children. Do a
  206. `print fig` to display the relations:
  207. !bc dat
  208. ground
  209. wall
  210. vehicle
  211. body
  212. over
  213. rectangle
  214. under
  215. rectangle
  216. wheels
  217. wheel1
  218. arc
  219. wheel2
  220. arc
  221. !ec
  222. The indentation reflects how deep down in the hierarchy (family)
  223. we are.
  224. This output is to be interpreted as follows:
  225. * `fig` contains two objects, `ground` and `vehicle`
  226. * `ground` contains an object `wall`
  227. * `vehicle` contains two objects, `body` and `wheels`
  228. * `body` contains two objects, `over` and `under`
  229. * `wheels` contains two objects, `wheel1` and `wheel2`
  230. In this listing there are also objects not defined by the
  231. programmer: `rectangle` and `arc`. These are of type `Curve`
  232. and automatically generated by the classes `Rectangle` and `Circle`.
  233. More detailed information can be printed by
  234. !bc pycod
  235. print fig.show_hierarchy('std')
  236. !ec
  237. yielding the output
  238. !bc dat
  239. ground (Wall):
  240. wall (Curve): 4 coords fillcolor='white' fillpattern='/'
  241. vehicle (Composition):
  242. body (Composition):
  243. over (Rectangle):
  244. rectangle (Curve): 5 coords
  245. under (Rectangle):
  246. rectangle (Curve): 5 coords
  247. wheels (Composition):
  248. wheel1 (Circle):
  249. arc (Curve): 181 coords
  250. wheel2 (Circle):
  251. arc (Curve): 181 coords
  252. !ec
  253. Here we can see the class type for each figure object, how many
  254. coordinates that are involved in basic figures (`Curve` objects), and
  255. special settings of the basic figure (fillcolor, line types, etc.).
  256. For example, `wheel2` is a `Circle` object consisting of an `arc`,
  257. which is a `Curve` object consisting of 181 coordinates (the
  258. points needed to draw a smooth circle). The `Curve` objects are the
  259. only objects that really holds specific coordinates to be drawn.
  260. The other object types are just compositions used to group
  261. parts of the complete figure.
  262. One can also get a graphical overview of the hierarchy of figure objects
  263. that build up a particular figure `fig`.
  264. Just call `fig.graphviz_dot('fig')` to produce a file `fig.dot` in
  265. the *dot format*. This file contains relations between parent and
  266. child objects in the figure and can be turned into an image,
  267. as in Figure ref{sketcher:fig:vehicle0:hier1}, by
  268. running the `dot` program:
  269. !bc sys
  270. Terminal> dot -Tpng -o fig.png fig.dot
  271. !ec
  272. FIGURE: [fig-tut/vehicle0_hier1, width=500 frac=0.8] Hierarchical relation between figure objects. label{sketcher:fig:vehicle0:hier1}
  273. The call `fig.graphviz_dot('fig', classname=True)` makes a `fig.dot` file
  274. where the class type of each object is also visible, see
  275. Figure ref{sketcher:fig:vehicle0:hier2}. The ability to write out the
  276. object hierarchy or view it graphically can be of great help when
  277. working with complex figures that involve layers of subfigures.
  278. FIGURE: [fig-tut/Vehicle0_hier2, width=500 frac=0.8] Hierarchical relation between figure objects, including their class names. label{sketcher:fig:vehicle0:hier2}
  279. Any of the objects can in the program be reached through their names, e.g.,
  280. !bc pycod
  281. fig['vehicle']
  282. fig['vehicle']['wheels']
  283. fig['vehicle']['wheels']['wheel2']
  284. fig['vehicle']['wheels']['wheel2']['arc']
  285. fig['vehicle']['wheels']['wheel2']['arc'].x # x coords
  286. fig['vehicle']['wheels']['wheel2']['arc'].y # y coords
  287. fig['vehicle']['wheels']['wheel2']['arc'].linestyle
  288. fig['vehicle']['wheels']['wheel2']['arc'].linetype
  289. !ec
  290. Grabbing a part of the figure this way is handy for
  291. changing properties of that part, for example, colors, line styles
  292. (see Figure ref{sketcher:fig:vehicle0:v2}):
  293. !bc pycod
  294. fig['vehicle']['wheels'].set_filled_curves('blue')
  295. fig['vehicle']['wheels'].set_linewidth(6)
  296. fig['vehicle']['wheels'].set_linecolor('black')
  297. fig['vehicle']['body']['under'].set_filled_curves('red')
  298. fig['vehicle']['body']['over'].set_filled_curves(pattern='/')
  299. fig['vehicle']['body']['over'].set_linewidth(14)
  300. fig['vehicle']['body']['over']['rectangle'].linewidth = 4
  301. !ec
  302. The last line accesses the `Curve` object directly, while the line above,
  303. accesses the `Rectangle` object, which will then set the linewidth of
  304. its `Curve` object, and other objects if it had any.
  305. The result of the actions above is shown in Figure ref{sketcher:fig:vehicle0:v2}.
  306. FIGURE: [fig-tut/vehicle0, width=700] Left: Basic line-based drawing. Right: Thicker lines and filled parts. label{sketcher:fig:vehicle0:v2}
  307. We can also change position of parts of the figure and thereby make
  308. animations, as shown next.
  309. === Animation: Translating the Vehicle ===
  310. Can we make our little vehicle roll? A first attempt will be to
  311. fake rolling by just displacing all parts of the vehicle.
  312. The relevant parts constitute the `fig['vehicle']` object.
  313. This part of the figure can be translated, rotated, and scaled.
  314. A translation along the ground means a translation in $x$ direction,
  315. say a length $L$ to the right:
  316. !bc pycod
  317. fig['vehicle'].translate((L,0))
  318. !ec
  319. You need to erase, draw, and display to see the movement:
  320. !bc pycod
  321. drawing_tool.erase()
  322. fig.draw()
  323. drawing_tool.display()
  324. !ec
  325. Without erasing, the old drawing of the vehicle will remain in
  326. the figure so you get two vehicles. Without `fig.draw()` the
  327. new coordinates of the vehicle will not be communicated to
  328. the drawing tool, and without calling display the updated
  329. drawing will not be visible.
  330. A figure that moves in time is conveniently realized by the
  331. function `animate`:
  332. !bc pycod
  333. animate(fig, tp, action)
  334. !ec
  335. Here, `fig` is the entire figure, `tp` is an array of
  336. time points, and `action` is a user-specified function that changes
  337. `fig` at a specific time point. Typically, `action` will move
  338. parts of `fig`.
  339. In the present case we can define the movement through a velocity
  340. function `v(t)` and displace the figure `v(t)*dt` for small time
  341. intervals `dt`. A possible velocity function is
  342. !bc pycod
  343. def v(t):
  344. return -8*R*t*(1 - t/(2*R))
  345. !ec
  346. Our action function for horizontal displacements `v(t)*dt` becomes
  347. !bc pycod
  348. def move(t, fig):
  349. x_displacement = dt*v(t)
  350. fig['vehicle'].translate((x_displacement, 0))
  351. !ec
  352. Since our velocity is negative for $t\in [0,2R]$ the displacement is
  353. to the left.
  354. The `animate` function will for each time point `t` in `tp` erase
  355. the drawing, call `action(t, fig)`, and show the new figure by
  356. `fig.draw()` and `drawing_tool.display()`.
  357. Here we choose a resolution of the animation corresponding to
  358. 25 time points in the time interval $[0,2R]$:
  359. !bc pycod
  360. import numpy
  361. tp = numpy.linspace(0, 2*R, 25)
  362. dt = tp[1] - tp[0] # time step
  363. animate(fig, tp, move, pause_per_frame=0.2)
  364. !ec
  365. The `pause_per_frame` adds a pause, here 0.2 seconds, between
  366. each frame in the animation.
  367. We can also ask `animate` to store each frame in a file:
  368. !bc pycod
  369. files = animate(fig, tp, move_vehicle, moviefiles=True,
  370. pause_per_frame=0.2)
  371. !ec
  372. The `files` variable, here `'tmp_frame_%04d.png'`,
  373. is the printf-specification used to generate the individual
  374. plot files. We can use this specification to make a video
  375. file via `ffmpeg` (or `avconv` on Debian-based Linux systems such
  376. as Ubuntu). Videos in the Flash and WebM formats can be created
  377. by
  378. !bc sys
  379. Terminal> ffmpeg -r 12 -i tmp_frame_%04d.png -vcodec flv mov.flv
  380. Terminal> ffmpeg -r 12 -i tmp_frame_%04d.png -vcodec libvpx mov.webm
  381. !ec
  382. An animated GIF movie can also be made using the `convert` program
  383. from the ImageMagick software suite:
  384. !bc sys
  385. Terminal> convert -delay 20 tmp_frame*.png mov.gif
  386. Terminal> animate mov.gif # play movie
  387. !ec
  388. The delay between frames, in units of 1/100 s,
  389. governs the speed of the movie.
  390. To play the animated GIF file in a web page, simply insert
  391. `<img src="mov.gif">` in the HTML code.
  392. The individual PNG frames can be directly played in a web
  393. browser by running
  394. !bc sys
  395. Terminal> scitools movie output_file=mov.html fps=5 tmp_frame*
  396. !ec
  397. or calling
  398. !bc pycod
  399. from scitools.std import movie
  400. movie(files, encoder='html', output_file='mov.html')
  401. !ec
  402. in Python. Load the resulting file `mov.html` into a web browser
  403. to play the movie.
  404. Try to run "`vehicle0.py`": "${src_path_pysketcher}/vehicle0.py" and
  405. then load `mov.html` into a browser, or play one of the `mov.*`
  406. video files. Alternatively, you can view a ready-made "movie":
  407. "${src_path_tut}/mov-tut/vehicle0.html".
  408. === Animation: Rolling the Wheels ===
  409. label{sketcher:vehicle1:anim}
  410. It is time to show rolling wheels. To this end, we add spokes to the
  411. wheels, formed by two crossing lines, see Figure ref{sketcher:fig:vehicle1}.
  412. The construction of the wheels will now involve a circle and two lines:
  413. !bc pycod
  414. wheel1 = Composition({
  415. 'wheel': Circle(center=(w_1, R), radius=R),
  416. 'cross': Composition({'cross1': Line((w_1,0), (w_1,2*R)),
  417. 'cross2': Line((w_1-R,R), (w_1+R,R))})})
  418. wheel2 = wheel1.copy()
  419. wheel2.translate((L,0))
  420. !ec
  421. Observe that `wheel1.copy()` copies all the objects that make
  422. up the first wheel, and `wheel2.translate` translates all
  423. the copied objects.
  424. FIGURE: [fig-tut/vehicle1, width=400 frac=0.8] Wheels with spokes to illustrate rolling. label{sketcher:fig:vehicle1}
  425. The `move` function now needs to displace all the objects in the
  426. entire vehicle and also rotate the `cross1` and `cross2`
  427. objects in both wheels.
  428. The rotation angle follows from the fact that the arc length
  429. of a rolling wheel equals the displacement of the center of
  430. the wheel, leading to a rotation angle
  431. !bc pycod
  432. angle = - x_displacement/R
  433. !ec
  434. With `w_1` tracking the $x$ coordinate of the center
  435. of the front wheel, we can rotate that wheel by
  436. !bc pycod
  437. w1 = fig['vehicle']['wheels']['wheel1']
  438. from math import degrees
  439. w1.rotate(degrees(angle), center=(w_1, R))
  440. !ec
  441. The `rotate` function takes two parameters: the rotation angle
  442. (in degrees) and the center point of the rotation, which is the
  443. center of the wheel in this case. The other wheel is rotated by
  444. !bc pycod
  445. w2 = fig['vehicle']['wheels']['wheel2']
  446. w2.rotate(degrees(angle), center=(w_1 + L, R))
  447. !ec
  448. That is, the angle is the same, but the rotation point is different.
  449. The update of the center point is done by `w_1 += x_displacement`.
  450. The complete `move` function with translation of the entire
  451. vehicle and rotation of the wheels then becomes
  452. !bc pycod
  453. w_1 = w_1 + L # start position
  454. def move(t, fig):
  455. x_displacement = dt*v(t)
  456. fig['vehicle'].translate((x_displacement, 0))
  457. # Rotate wheels
  458. global w_1
  459. w_1 += x_displacement
  460. # R*angle = -x_displacement
  461. angle = - x_displacement/R
  462. w1 = fig['vehicle']['wheels']['wheel1']
  463. w1.rotate(degrees(angle), center=(w_1, R))
  464. w2 = fig['vehicle']['wheels']['wheel2']
  465. w2.rotate(degrees(angle), center=(w_1 + L, R))
  466. !ec
  467. The complete example is found in the file
  468. "`vehicle1.py`": "${src_path_pysketcher}/vehicle1.py". You may run this file or watch a "ready-made movie": "${src_path_tut}/mov-tut/vehicle1.html".
  469. The advantages with making figures this way, through programming
  470. rather than using interactive drawing programs, are numerous. For
  471. example, the objects are parameterized by variables so that various
  472. dimensions can easily be changed. Subparts of the figure, possible
  473. involving a lot of figure objects, can change color, linetype, filling
  474. or other properties through a *single* function call. Subparts of the
  475. figure can be rotated, translated, or scaled. Subparts of the figure
  476. can also be copied and moved to other parts of the drawing
  477. area. However, the single most important feature is probably the
  478. ability to make animations governed by mathematical formulas or data
  479. coming from physics simulations of the problem, as shown in the example above.