shapes.py 129 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776
  1. from __future__ import division
  2. from __future__ import unicode_literals
  3. from __future__ import print_function
  4. from __future__ import absolute_import
  5. from future import standard_library
  6. standard_library.install_aliases()
  7. from builtins import input
  8. from builtins import zip
  9. from builtins import str
  10. from builtins import range
  11. from builtins import *
  12. from builtins import object
  13. from numpy import linspace, sin, cos, pi, array, asarray, ndarray, sqrt, abs
  14. import pprint, copy, glob, os
  15. from math import radians
  16. from .MatplotlibDraw import MatplotlibDraw
  17. drawing_tool = MatplotlibDraw()
  18. def point(x, y, check_inside=False):
  19. for obj, name in zip([x, y], ['x', 'y']):
  20. if isinstance(obj, (float,int)):
  21. pass
  22. elif isinstance(obj, ndarray):
  23. if obj.size == 1:
  24. pass
  25. else:
  26. raise TypeError('%s=%s of type %d has length=%d > 1' %
  27. (name, obj, type(obj), obj.size))
  28. else:
  29. raise TypeError('%s=%s is of wrong type %d' %
  30. (name, obj, type(obj)))
  31. if check_inside:
  32. ok, msg = drawing_tool.inside((x,y), exception=True)
  33. if not ok:
  34. print(msg)
  35. return array((x, y), dtype=float)
  36. def distance(p1, p2):
  37. p1 = arr2D(p1); p2 = arr2D(p2)
  38. d = p2 - p1
  39. return sqrt(d[0]**2 + d[1]**2)
  40. def unit_vec(x, y=None):
  41. """Return unit vector of the vector (x,y), or just x if x is a 2D point."""
  42. if isinstance(x, (float,int)) and isinstance(y, (float,int)):
  43. x = point(x, y)
  44. elif isinstance(x, (list,tuple,ndarray)) and y is None:
  45. return arr2D(x)/sqrt(x[0]**2 + x[1]**2)
  46. else:
  47. raise TypeError('x=%s is %s, must be float or ndarray 2D point' %
  48. (x, type(x)))
  49. def arr2D(x, check_inside=False):
  50. if isinstance(x, (tuple,list,ndarray)):
  51. if len(x) == 2:
  52. pass
  53. else:
  54. raise ValueError('x=%s has length %d, not 2' % (x, len(x)))
  55. else:
  56. raise TypeError('x=%s must be list/tuple/ndarray, not %s' %
  57. (x, type(x)))
  58. if check_inside:
  59. ok, msg = drawing_tool.inside(x, exception=True)
  60. if not ok:
  61. print(msg)
  62. return asarray(x, dtype=float)
  63. def _is_sequence(seq, length=None,
  64. can_be_None=False, error_message=True):
  65. if can_be_None:
  66. legal_types = (list,tuple,ndarray,None)
  67. else:
  68. legal_types = (list,tuple,ndarray)
  69. if isinstance(seq, legal_types):
  70. if length is not None:
  71. if length == len(seq):
  72. return True
  73. elif error_message:
  74. raise TypeError('sequence %s is not a sequence but %s; must be %s of length %d' %
  75. (str(seq), type(seq),
  76. ', '.join([str(t) for t in legal_types]),
  77. len(seq)))
  78. else:
  79. return False
  80. else:
  81. return True
  82. elif error_message:
  83. raise TypeError('sequence %s is not a sequence but %s, %s; must be %s' %
  84. (str(seq), seq.__class__.__name__, type(seq),
  85. ','.join([str(t)[5:-1] for t in legal_types])))
  86. else:
  87. return False
  88. def is_sequence(*sequences, **kwargs):
  89. length = kwargs.get('length', 2)
  90. can_be_None = kwargs.get('can_be_None', False)
  91. error_message = kwargs.get('error_message', True)
  92. check_inside = kwargs.get('check_inside', False)
  93. for x in sequences:
  94. _is_sequence(x, length=length, can_be_None=can_be_None,
  95. error_message=error_message)
  96. if check_inside:
  97. ok, msg = drawing_tool.inside(x, exception=True)
  98. if not ok:
  99. print(msg)
  100. def animate(fig, time_points, action, moviefiles=False,
  101. pause_per_frame=0.5, show_screen_graphics=True,
  102. title=None,
  103. **action_kwargs):
  104. if moviefiles:
  105. # Clean up old frame files
  106. framefilestem = 'tmp_frame_'
  107. framefiles = glob.glob('%s*.png' % framefilestem)
  108. for framefile in framefiles:
  109. os.remove(framefile)
  110. for n, t in enumerate(time_points):
  111. drawing_tool.erase()
  112. action(t, fig, **action_kwargs)
  113. #could demand returning fig, but in-place modifications
  114. #are done anyway
  115. #fig = action(t, fig)
  116. #if fig is None:
  117. # raise TypeError(
  118. # 'animate: action returns None, not fig\n'
  119. # '(a Shape object with the whole figure)')
  120. fig.draw()
  121. drawing_tool.display(title=title, show=show_screen_graphics)
  122. if moviefiles:
  123. drawing_tool.savefig('%s%04d.png' % (framefilestem, n),
  124. crop=False)
  125. if moviefiles:
  126. return '%s%%04d.png' % framefilestem
  127. """
  128. def save():
  129. os.system("ffmpeg -r 1 -i img%01d.png -vcodec mpeg4 -y movie.mp4")
  130. """
  131. class Shape(object):
  132. """
  133. Superclass for drawing different geometric shapes.
  134. Subclasses define shapes, but drawing, rotation, translation,
  135. etc. are done in generic functions in this superclass.
  136. """
  137. def __init__(self):
  138. """
  139. Never to be called from subclasses.
  140. """
  141. raise NotImplementedError(
  142. 'class %s must implement __init__,\nwhich defines '
  143. 'self.shapes as a dict (or list) of Shape objects\n'
  144. 'Do not call Shape.__init__!' % \
  145. self.__class__.__name__)
  146. def set_name(self, name):
  147. self.name = name
  148. return self
  149. def get_name(self):
  150. return self.name if hasattr(self, 'name') else 'no_name'
  151. def __iter__(self):
  152. # We iterate over self.shapes many places, and will
  153. # get here if self.shapes is just a Shape object and
  154. # not the assumed dict/list.
  155. print('Warning: class %s does not define self.shapes\n'\
  156. 'as a dict of Shape objects')
  157. return [self] # Make the iteration work
  158. def copy(self):
  159. return copy.deepcopy(self)
  160. def __getitem__(self, name):
  161. """
  162. Allow indexing like::
  163. obj1['name1']['name2']
  164. all the way down to ``Curve`` or ``Point`` (``Text``)
  165. objects.
  166. """
  167. if hasattr(self, 'shapes'):
  168. if name in self.shapes:
  169. return self.shapes[name]
  170. else:
  171. for shape in self.shapes:
  172. if isinstance(self.shapes[shape], (Curve,Point)):
  173. # Indexing of Curve/Point/Text is not possible
  174. raise TypeError(
  175. 'Index "%s" (%s) is illegal' %
  176. (name, self.__class__.__name__))
  177. return self.shapes[shape][name]
  178. else:
  179. raise Exception('This is a bug in __getitem__')
  180. def __setitem__(self, name, value):
  181. """
  182. Allow assignment like::
  183. obj1['name1']['name2'] = value
  184. all the way down to ``Curve`` or ``Point`` (``Text``)
  185. objects.
  186. """
  187. if hasattr(self, 'shapes'):
  188. self.shapes[name] = value
  189. else:
  190. raise Exception('Cannot assign')
  191. def _for_all_shapes(self, func, *args, **kwargs):
  192. verbose = kwargs.get('verbose', 0)
  193. if not hasattr(self, 'shapes'):
  194. # When self.shapes is lacking, we either come to
  195. # a special implementation of func or we come here
  196. # because Shape.func is just inherited. This is
  197. # an error if the class is not Curve or Point
  198. if isinstance(self, (Curve, Point)):
  199. return # ok: no shapes, but object is a curve or point end leaf
  200. else:
  201. raise AttributeError('class %s has no shapes attribute!' %
  202. self.__class__.__name__)
  203. is_dict = True if isinstance(self.shapes, dict) else False
  204. for k, shape in enumerate(self.shapes):
  205. if is_dict:
  206. shape_name = shape
  207. shape = self.shapes[shape]
  208. else:
  209. shape_name = k # use index as name if list (not dict)
  210. if not isinstance(shape, Shape):
  211. if isinstance(shape, dict):
  212. raise TypeError(
  213. 'class %s has a self.shapes member "%s" that is just\n'
  214. 'a plain dictionary,\n%s\n'
  215. 'Did you mean to embed this dict in a Composition\n'
  216. 'object?' % (self.__class__.__name__, shape_name,
  217. str(shape)))
  218. elif isinstance(shape, (list,tuple)):
  219. raise TypeError(
  220. 'class %s has self.shapes member "%s" containing\n'
  221. 'a %s object %s,\n'
  222. 'Did you mean to embed this list in a Composition\n'
  223. 'object?' % (self.__class__.__name__, shape_name,
  224. type(shape), str(shape)))
  225. elif shape is None:
  226. raise TypeError(
  227. 'class %s has a self.shapes member "%s" that is None.\n'
  228. 'Some variable name is wrong, or some function\n'
  229. 'did not return the right object...' \
  230. % (self.__class__.__name__, shape_name))
  231. else:
  232. raise TypeError(
  233. 'class %s has a self.shapes member "%s" of %s which '
  234. 'is not a Shape object\n%s' %
  235. (self.__class__.__name__, shape_name, type(shape),
  236. pprint.pformat(self.shapes)))
  237. if isinstance(shape, Curve):
  238. shape.name = shape_name
  239. if verbose > 0:
  240. print('calling %s.%s' % (shape_name, func))
  241. getattr(shape, func)(*args, **kwargs)
  242. def draw(self, verbose=0):
  243. self._for_all_shapes('draw', verbose=verbose)
  244. return self
  245. def draw_dimensions(self):
  246. if hasattr(self, 'dimensions'):
  247. for shape in self.dimensions:
  248. self.dimensions[shape].draw()
  249. return self
  250. else:
  251. #raise AttributeError('no self.dimensions dict for defining dimensions of class %s' % self.__classname__.__name__)
  252. return self
  253. def rotate(self, angle, center):
  254. is_sequence(center, length=2)
  255. self._for_all_shapes('rotate', angle, center)
  256. return self
  257. def translate(self, vec):
  258. is_sequence(vec, length=2)
  259. self._for_all_shapes('translate', vec)
  260. return self
  261. def scale(self, factor):
  262. self._for_all_shapes('scale', factor)
  263. return self
  264. def deform(self, displacement_function):
  265. self._for_all_shapes('deform', displacement_function)
  266. return self
  267. def minmax_coordinates(self, minmax=None):
  268. if minmax is None:
  269. minmax = {'xmin': 1E+20, 'xmax': -1E+20,
  270. 'ymin': 1E+20, 'ymax': -1E+20}
  271. self._for_all_shapes('minmax_coordinates', minmax)
  272. return minmax
  273. def recurse(self, name, indent=0):
  274. if not isinstance(self.shapes, dict):
  275. raise TypeError('recurse works only with dict self.shape, not %s' %
  276. type(self.shapes))
  277. space = ' '*indent
  278. print(space, '%s: %s.shapes has entries' % \
  279. (self.__class__.__name__, name), \
  280. str(list(self.shapes.keys()))[1:-1])
  281. for shape in self.shapes:
  282. print(space, end=' ')
  283. print('call %s.shapes["%s"].recurse("%s", %d)' % \
  284. (name, shape, shape, indent+2))
  285. self.shapes[shape].recurse(shape, indent+2)
  286. def graphviz_dot(self, name, classname=True):
  287. if not isinstance(self.shapes, dict):
  288. raise TypeError('recurse works only with dict self.shape, not %s' %
  289. type(self.shapes))
  290. dotfile = name + '.dot'
  291. pngfile = name + '.png'
  292. if classname:
  293. name = r"%s:\n%s" % (self.__class__.__name__, name)
  294. couplings = self._object_couplings(name, classname=classname)
  295. # Insert counter for similar names
  296. from collections import defaultdict
  297. count = defaultdict(lambda: 0)
  298. couplings2 = []
  299. for i in range(len(couplings)):
  300. parent, child = couplings[i]
  301. count[child] += 1
  302. parent += ' (%d)' % count[parent]
  303. child += ' (%d)' % count[child]
  304. couplings2.append((parent, child))
  305. print('graphviz', couplings, count)
  306. # Remove counter for names there are only one of
  307. for i in range(len(couplings)):
  308. parent2, child2 = couplings2[i]
  309. parent, child = couplings[i]
  310. if count[parent] > 1:
  311. parent = parent2
  312. if count[child] > 1:
  313. child = child2
  314. couplings[i] = (parent, child)
  315. print(couplings)
  316. f = open(dotfile, 'w')
  317. f.write('digraph G {\n')
  318. for parent, child in couplings:
  319. f.write('"%s" -> "%s";\n' % (parent, child))
  320. f.write('}\n')
  321. f.close()
  322. print('Run dot -Tpng -o %s %s' % (pngfile, dotfile))
  323. def _object_couplings(self, parent, couplings=[], classname=True):
  324. """Find all couplings of parent and child objects in a figure."""
  325. for shape in self.shapes:
  326. if classname:
  327. childname = r"%s:\n%s" % \
  328. (self.shapes[shape].__class__.__name__, shape)
  329. else:
  330. childname = shape
  331. couplings.append((parent, childname))
  332. self.shapes[shape]._object_couplings(childname, couplings,
  333. classname)
  334. return couplings
  335. def set_linestyle(self, style):
  336. styles = ('solid', 'dashed', 'dashdot', 'dotted')
  337. if style not in styles:
  338. raise ValueError('%s: style=%s must be in %s' %
  339. (self.__class__.__name__ + '.set_linestyle:',
  340. style, str(styles)))
  341. self._for_all_shapes('set_linestyle', style)
  342. return self
  343. def set_linewidth(self, width):
  344. if not isinstance(width, int) and width >= 0:
  345. raise ValueError('%s: width=%s must be positive integer' %
  346. (self.__class__.__name__ + '.set_linewidth:',
  347. width))
  348. self._for_all_shapes('set_linewidth', width)
  349. return self
  350. def set_linecolor(self, color):
  351. if color in drawing_tool.line_colors:
  352. color = drawing_tool.line_colors[color]
  353. elif color in list(drawing_tool.line_colors.values()):
  354. pass # color is ok
  355. else:
  356. raise ValueError('%s: invalid color "%s", must be in %s' %
  357. (self.__class__.__name__ + '.set_linecolor:',
  358. color, list(drawing_tool.line_colors.keys())))
  359. self._for_all_shapes('set_linecolor', color)
  360. return self
  361. def set_arrow(self, style):
  362. styles = ('->', '<-', '<->')
  363. if not style in styles:
  364. raise ValueError('%s: style=%s must be in %s' %
  365. (self.__class__.__name__ + '.set_arrow:',
  366. style, styles))
  367. self._for_all_shapes('set_arrow', style)
  368. return self
  369. def set_filled_curves(self, color='', pattern=''):
  370. if color in drawing_tool.line_colors:
  371. color = drawing_tool.line_colors[color]
  372. elif color in list(drawing_tool.line_colors.values()):
  373. pass # color is ok
  374. else:
  375. raise ValueError('%s: invalid color "%s", must be in %s' %
  376. (self.__class__.__name__ + '.set_filled_curves:',
  377. color, list(drawing_tool.line_colors.keys())))
  378. self._for_all_shapes('set_filled_curves', color, pattern)
  379. return self
  380. def set_shadow(self, pixel_displacement=3):
  381. self._for_all_shapes('set_shadow', pixel_displacement)
  382. return self
  383. def show_hierarchy(self, indent=0, format='std'):
  384. """Recursive pretty print of hierarchy of objects."""
  385. if not isinstance(self.shapes, dict):
  386. print('cannot print hierarchy when %s.shapes is not a dict' % \
  387. self.__class__.__name__)
  388. s = ''
  389. if format == 'dict':
  390. s += '{'
  391. for shape in self.shapes:
  392. if format == 'dict':
  393. shape_str = repr(shape) + ':'
  394. elif format == 'plain':
  395. shape_str = shape
  396. else:
  397. shape_str = shape + ':'
  398. if format == 'dict' or format == 'plain':
  399. class_str = ''
  400. else:
  401. class_str = ' (%s)' % \
  402. self.shapes[shape].__class__.__name__
  403. s += '\n%s%s%s %s,' % (
  404. ' '*indent,
  405. shape_str,
  406. class_str,
  407. self.shapes[shape].show_hierarchy(indent+4, format))
  408. if format == 'dict':
  409. s += '}'
  410. return s
  411. def __str__(self):
  412. """Display hierarchy with minimum information (just object names)."""
  413. return self.show_hierarchy(format='plain')
  414. def __repr__(self):
  415. """Display hierarchy as a dictionary."""
  416. return self.show_hierarchy(format='dict')
  417. #return pprint.pformat(self.shapes)
  418. class Curve(Shape):
  419. """General curve as a sequence of (x,y) coordintes."""
  420. def __init__(self, x, y):
  421. """
  422. `x`, `y`: arrays holding the coordinates of the curve.
  423. """
  424. self.x = asarray(x, dtype=float)
  425. self.y = asarray(y, dtype=float)
  426. #self.shapes must not be defined in this class
  427. #as self.shapes holds children objects:
  428. #Curve has no children (end leaf of self.shapes tree)
  429. self.linestyle = None
  430. self.linewidth = None
  431. self.linecolor = None
  432. self.fillcolor = None
  433. self.fillpattern = None
  434. self.arrow = None
  435. self.shadow = False
  436. self.name = None # name of object that this Curve represents
  437. def inside_plot_area(self, verbose=True):
  438. """Check that all coordinates are within drawing_tool's area."""
  439. xmin, xmax = self.x.min(), self.x.max()
  440. ymin, ymax = self.y.min(), self.y.max()
  441. t = drawing_tool
  442. inside = True
  443. if not hasattr(t, 'xmin'):
  444. return None # drawing area is not defined
  445. if xmin < t.xmin:
  446. inside = False
  447. if verbose:
  448. print('x_min=%g < plot area x_min=%g' % (xmin, t.xmin))
  449. if xmax > t.xmax:
  450. inside = False
  451. if verbose:
  452. print('x_max=%g > plot area x_max=%g' % (xmax, t.xmax))
  453. if ymin < t.ymin:
  454. inside = False
  455. if verbose:
  456. print('y_min=%g < plot area y_min=%g' % (ymin, t.ymin))
  457. if ymax > t.ymax:
  458. inside = False
  459. if verbose:
  460. print('y_max=%g > plot area y_max=%g' % (ymax, t.ymax))
  461. return inside
  462. def draw(self, verbose=0):
  463. """
  464. Send the curve to the plotting engine. That is, convert
  465. coordinate information in self.x and self.y, together
  466. with optional settings of linestyles, etc., to
  467. plotting commands for the chosen engine.
  468. """
  469. self.inside_plot_area()
  470. drawing_tool.plot_curve(
  471. self.x, self.y,
  472. self.linestyle, self.linewidth, self.linecolor,
  473. self.arrow, self.fillcolor, self.fillpattern,
  474. self.shadow, self.name)
  475. if verbose:
  476. print('drawing Curve object with %d points' % len(self.x))
  477. def rotate(self, angle, center):
  478. """
  479. Rotate all coordinates: `angle` is measured in degrees and
  480. (`x`,`y`) is the "origin" of the rotation.
  481. """
  482. angle = radians(angle)
  483. x, y = center
  484. c = cos(angle); s = sin(angle)
  485. xnew = x + (self.x - x)*c - (self.y - y)*s
  486. ynew = y + (self.x - x)*s + (self.y - y)*c
  487. self.x = xnew
  488. self.y = ynew
  489. return self
  490. def scale(self, factor):
  491. """Scale all coordinates by `factor`: ``x = factor*x``, etc."""
  492. self.x = factor*self.x
  493. self.y = factor*self.y
  494. return self
  495. def translate(self, vec):
  496. """Translate all coordinates by a vector `vec`."""
  497. self.x += vec[0]
  498. self.y += vec[1]
  499. return self
  500. def deform(self, displacement_function):
  501. """Displace all coordinates according to displacement_function(x,y)."""
  502. for i in range(len(self.x)):
  503. self.x[i], self.y[i] = displacement_function(self.x[i], self.y[i])
  504. return self
  505. def minmax_coordinates(self, minmax=None):
  506. if minmax is None:
  507. minmax = {'xmin': [], 'xmax': [], 'ymin': [], 'ymax': []}
  508. minmax['xmin'] = min(self.x.min(), minmax['xmin'])
  509. minmax['xmax'] = max(self.x.max(), minmax['xmax'])
  510. minmax['ymin'] = min(self.y.min(), minmax['ymin'])
  511. minmax['ymax'] = max(self.y.max(), minmax['ymax'])
  512. return minmax
  513. def recurse(self, name, indent=0):
  514. space = ' '*indent
  515. print(space, 'reached "bottom" object %s' % \
  516. self.__class__.__name__)
  517. def _object_couplings(self, parent, couplings=[], classname=True):
  518. return
  519. def set_linecolor(self, color):
  520. self.linecolor = color
  521. return self
  522. def set_linewidth(self, width):
  523. self.linewidth = width
  524. return self
  525. def set_linestyle(self, style):
  526. self.linestyle = style
  527. return self
  528. def set_arrow(self, style=None):
  529. self.arrow = style
  530. return self
  531. def set_filled_curves(self, color='', pattern=''):
  532. self.fillcolor = color
  533. self.fillpattern = pattern
  534. return self
  535. def set_shadow(self, pixel_displacement=3):
  536. self.shadow = pixel_displacement
  537. return self
  538. def show_hierarchy(self, indent=0, format='std'):
  539. if format == 'dict':
  540. return '"%s"' % str(self)
  541. elif format == 'plain':
  542. return ''
  543. else:
  544. return str(self)
  545. def __str__(self):
  546. """Compact pretty print of a Curve object."""
  547. s = '%d (x,y) coords' % self.x.size
  548. inside = self.inside_plot_area(verbose=False)
  549. if inside is None:
  550. pass # no info about the plotting area
  551. elif not inside:
  552. s += ', some coordinates are outside plotting area!\n'
  553. props = ('linecolor', 'linewidth', 'linestyle', 'arrow',
  554. 'fillcolor', 'fillpattern')
  555. for prop in props:
  556. value = getattr(self, prop)
  557. if value is not None:
  558. s += ' %s=%s' % (prop, repr(value))
  559. return s
  560. def __repr__(self):
  561. return str(self)
  562. class Spline(Shape):
  563. # Note: UnivariateSpline interpolation may not work if
  564. # the x[i] points are far from uniformly spaced
  565. def __init__(self, x, y, degree=3, resolution=501):
  566. from scipy.interpolate import UnivariateSpline
  567. self.smooth = UnivariateSpline(x, y, s=0, k=degree)
  568. self.xcoor = linspace(x[0], x[-1], resolution)
  569. ycoor = self.smooth(self.xcoor)
  570. self.shapes = {'smooth': Curve(self.xcoor, ycoor)}
  571. def geometric_features(self):
  572. s = self.shapes['smooth']
  573. return {'start': point(s.x[0], s.y[0]),
  574. 'end': point(s.x[-1], s.y[-1]),
  575. 'interval': [s.x[0], s.x[-1]]}
  576. def __call__(self, x):
  577. return self.smooth(x)
  578. # Can easily find the derivative and the integral as
  579. # self.smooth.derivative(n=1) and self.smooth.antiderivative()
  580. class SketchyFunc1(Spline):
  581. """
  582. A typical function curve used to illustrate an "arbitrary" function.
  583. """
  584. domain = [1, 6]
  585. def __init__(self, name=None, name_pos='start',
  586. xmin=0, xmax=6, ymin=0, ymax=2):
  587. x = array([0, 2, 3, 4, 5, 6])
  588. y = array([1, 1.8, 1.2, 0.7, 0.8, 0.85])
  589. #y = array([5, 3.5, 3.8, 3, 2.5, 2.4])
  590. # Scale x and y
  591. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  592. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  593. Spline.__init__(self, x, y)
  594. self.shapes['smooth'].set_linecolor('black')
  595. if name is not None:
  596. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  597. class SketchyFunc3(Spline):
  598. """
  599. A typical function curve used to illustrate an "arbitrary" function.
  600. """
  601. domain = [0, 6]
  602. def __init__(self, name=None, name_pos='start',
  603. xmin=0, xmax=6, ymin=0.5, ymax=3.8):
  604. x = array([0, 2, 3, 4, 5, 6])
  605. y = array([0.5, 3.5, 3.8, 2, 2.5, 3.5])
  606. # Scale x and y
  607. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  608. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  609. Spline.__init__(self, x, y)
  610. self.shapes['smooth'].set_linecolor('black')
  611. if name is not None:
  612. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  613. class SketchyFunc4(Spline):
  614. """
  615. A typical function curve used to illustrate an "arbitrary" function.
  616. Can be a companion function to SketchyFunc3.
  617. """
  618. domain = [1, 6]
  619. def __init__(self, name=None, name_pos='start',
  620. xmin=0, xmax=6, ymin=0.5, ymax=1.8):
  621. x = array([0, 2, 3, 4, 5, 6])
  622. y = array([1.5, 1.3, 0.7, 0.5, 0.6, 0.8])
  623. # Scale x and y
  624. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  625. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  626. Spline.__init__(self, x, y)
  627. self.shapes['smooth'].set_linecolor('black')
  628. if name is not None:
  629. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  630. class SketchyFunc2(Shape):
  631. """
  632. A typical function curve used to illustrate an "arbitrary" function.
  633. """
  634. domain = [0, 2.25]
  635. def __init__(self, name=None, name_pos='end',
  636. xmin=0, xmax=2.25, ymin=0.046679703125, ymax=1.259375):
  637. a = 0; b = 2.25
  638. resolution = 100
  639. x = linspace(a, b, resolution+1)
  640. f = self # for calling __call__
  641. y = f(x)
  642. # Scale x and y
  643. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  644. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  645. self.shapes = {'smooth': Curve(x, y)}
  646. self.shapes['smooth'].set_linecolor('black')
  647. pos = point(a, f(a)) if name_pos == 'start' else point(b, f(b))
  648. if name is not None:
  649. self.shapes['name'] = Text(name, pos + point(0,0.1))
  650. def __call__(self, x):
  651. return 0.5+x*(2-x)*(0.9-x) # on [0, 2.25]
  652. class Point(Shape):
  653. """A point (x,y) which can be rotated, translated, and scaled."""
  654. def __init__(self, x, y):
  655. self.x, self.y = x, y
  656. #self.shapes is not needed in this class
  657. def __add__(self, other):
  658. if isinstance(other, (list,tuple)):
  659. other = Point(other)
  660. return Point(self.x+other.x, self.y+other.y)
  661. # class Point is an abstract class - only subclasses are useful
  662. # and must implement draw
  663. def draw(self, verbose=0):
  664. raise NotImplementedError(
  665. 'class %s must implement the draw method' %
  666. self.__class__.__name__)
  667. def rotate(self, angle, center):
  668. """Rotate point an `angle` (in degrees) around (`x`,`y`)."""
  669. angle = angle*pi/180
  670. x, y = center
  671. c = cos(angle); s = sin(angle)
  672. xnew = x + (self.x - x)*c - (self.y - y)*s
  673. ynew = y + (self.x - x)*s + (self.y - y)*c
  674. self.x = xnew
  675. self.y = ynew
  676. return self
  677. def scale(self, factor):
  678. """Scale point coordinates by `factor`: ``x = factor*x``, etc."""
  679. self.x = factor*self.x
  680. self.y = factor*self.y
  681. return self
  682. def translate(self, vec):
  683. """Translate point by a vector `vec`."""
  684. self.x += vec[0]
  685. self.y += vec[1]
  686. return self
  687. def deform(self, displacement_function):
  688. """Displace coordinates according to displacement_function(x,y)."""
  689. for i in range(len(self.x)):
  690. self.x, self.y = displacement_function(self.x, self.y)
  691. return self
  692. def minmax_coordinates(self, minmax=None):
  693. if minmax is None:
  694. minmax = {'xmin': [], 'xmax': [], 'ymin': [], 'ymax': []}
  695. minmax['xmin'] = min(self.x, minmax['xmin'])
  696. minmax['xmax'] = max(self.x, minmax['xmax'])
  697. minmax['ymin'] = min(self.y, minmax['ymin'])
  698. minmax['ymax'] = max(self.y, minmax['ymax'])
  699. return minmax
  700. def recurse(self, name, indent=0):
  701. space = ' '*indent
  702. print(space, 'reached "bottom" object %s' % \
  703. self.__class__.__name__)
  704. def _object_couplings(self, parent, couplings=[], classname=True):
  705. return
  706. # No need for set_linecolor etc since self._for_all_shapes, which
  707. # is always called for these functions, makes a test and stops
  708. # calls if self.shapes is missing and the object is Point or Curve
  709. def show_hierarchy(self, indent=0, format='std'):
  710. s = '%s at (%g,%g)' % (self.__class__.__name__, self.x, self.y)
  711. if format == 'dict':
  712. return '"%s"' % s
  713. elif format == 'plain':
  714. return ''
  715. else:
  716. return s
  717. # no need to store input data as they are invalid after rotations etc.
  718. class Rectangle(Shape):
  719. """
  720. Rectangle specified by the point `lower_left_corner`, `width`,
  721. and `height`.
  722. """
  723. def __init__(self, lower_left_corner, width, height):
  724. is_sequence(lower_left_corner)
  725. p = arr2D(lower_left_corner) # short form
  726. x = [p[0], p[0] + width,
  727. p[0] + width, p[0], p[0]]
  728. y = [p[1], p[1], p[1] + height,
  729. p[1] + height, p[1]]
  730. self.shapes = {'rectangle': Curve(x,y)}
  731. # Dimensions
  732. dims = {
  733. 'width': Distance_wText(p + point(0, -height/5.),
  734. p + point(width, -height/5.),
  735. 'width'),
  736. 'height': Distance_wText(p + point(width + width/5., 0),
  737. p + point(width + width/5., height),
  738. 'height'),
  739. 'lower_left_corner': Text_wArrow('lower_left_corner',
  740. p - point(width/5., height/5.), p)
  741. }
  742. self.dimensions = dims
  743. def geometric_features(self):
  744. """
  745. Return dictionary with
  746. ==================== =============================================
  747. Attribute Description
  748. ==================== =============================================
  749. lower_left Lower left corner point.
  750. upper_left Upper left corner point.
  751. lower_right Lower right corner point.
  752. upper_right Upper right corner point.
  753. lower_mid Middle point on lower side.
  754. upper_mid Middle point on upper side.
  755. center Center point
  756. ==================== =============================================
  757. """
  758. r = self.shapes['rectangle']
  759. d = {'lower_left': point(r.x[0], r.y[0]),
  760. 'lower_right': point(r.x[1], r.y[1]),
  761. 'upper_right': point(r.x[2], r.y[2]),
  762. 'upper_left': point(r.x[3], r.y[3])}
  763. d['lower_mid'] = 0.5*(d['lower_left'] + d['lower_right'])
  764. d['upper_mid'] = 0.5*(d['upper_left'] + d['upper_right'])
  765. d['left_mid'] = 0.5*(d['lower_left'] + d['upper_left'])
  766. d['right_mid'] = 0.5*(d['lower_right'] + d['upper_right'])
  767. d['center'] = point(d['lower_mid'][0], d['left_mid'][1])
  768. return d
  769. class Triangle(Shape):
  770. """
  771. Triangle defined by its three vertices p1, p2, and p3.
  772. Recorded geometric features:
  773. ==================== =============================================
  774. Attribute Description
  775. ==================== =============================================
  776. p1, p2, p3 Corners as given to the constructor.
  777. ==================== =============================================
  778. """
  779. def __init__(self, p1, p2, p3):
  780. is_sequence(p1, p2, p3)
  781. x = [p1[0], p2[0], p3[0], p1[0]]
  782. y = [p1[1], p2[1], p3[1], p1[1]]
  783. self.shapes = {'triangle': Curve(x,y)}
  784. # Dimensions
  785. self.dimensions = {'p1': Text('p1', p1),
  786. 'p2': Text('p2', p2),
  787. 'p3': Text('p3', p3)}
  788. def geometric_features(self):
  789. t = self.shapes['triangle']
  790. return {'p1': point(t.x[0], t.y[0]),
  791. 'p2': point(t.x[1], t.y[1]),
  792. 'p3': point(t.x[2], t.y[2])}
  793. class Line(Shape):
  794. def __init__(self, start, end):
  795. is_sequence(start, end, length=2)
  796. if isinstance(start, (list,tuple)):
  797. start = array(start)
  798. if isinstance(end, (list,tuple)):
  799. end = array(end)
  800. if (start == end).all():
  801. # Introduce a very small perturbation since identical points
  802. # give drawing error
  803. end[0] = start[0] + 1E-10
  804. x = [start[0], end[0]]
  805. y = [start[1], end[1]]
  806. self.shapes = {'line': Curve(x, y)}
  807. def geometric_features(self):
  808. line = self.shapes['line']
  809. return {'start': point(line.x[0], line.y[0]),
  810. 'end': point(line.x[1], line.y[1]),}
  811. def compute_formulas(self):
  812. x, y = self.shapes['line'].x, self.shapes['line'].y
  813. # Define equations for line:
  814. # y = a*x + b, x = c*y + d
  815. try:
  816. self.a = (y[1] - y[0])/(x[1] - x[0])
  817. self.b = y[0] - self.a*x[0]
  818. except ZeroDivisionError:
  819. # Vertical line, y is not a function of x
  820. self.a = None
  821. self.b = None
  822. try:
  823. if self.a is None:
  824. self.c = 0
  825. else:
  826. self.c = 1/float(self.a)
  827. if self.b is None:
  828. self.d = x[1]
  829. except ZeroDivisionError:
  830. # Horizontal line, x is not a function of y
  831. self.c = None
  832. self.d = None
  833. def compute_formulas(self):
  834. x, y = self.shapes['line'].x, self.shapes['line'].y
  835. tol = 1E-14
  836. # Define equations for line:
  837. # y = a*x + b, x = c*y + d
  838. if abs(x[1] - x[0]) > tol:
  839. self.a = (y[1] - y[0])/(x[1] - x[0])
  840. self.b = y[0] - self.a*x[0]
  841. else:
  842. # Vertical line, y is not a function of x
  843. self.a = None
  844. self.b = None
  845. if self.a is None:
  846. self.c = 0
  847. elif abs(self.a) > tol:
  848. self.c = 1/float(self.a)
  849. self.d = x[1]
  850. else: # self.a is 0
  851. # Horizontal line, x is not a function of y
  852. self.c = None
  853. self.d = None
  854. def __call__(self, x=None, y=None):
  855. """Given x, return y on the line, or given y, return x."""
  856. self.compute_formulas()
  857. if x is not None and self.a is not None:
  858. return self.a*x + self.b
  859. elif y is not None and self.c is not None:
  860. return self.c*y + self.d
  861. else:
  862. raise ValueError(
  863. 'Line.__call__(x=%s, y=%s) not meaningful' % \
  864. (x, y))
  865. def new_interval(self, x=None, y=None):
  866. """Redefine current Line to cover interval in x or y."""
  867. if x is not None:
  868. is_sequence(x, length=2)
  869. xL, xR = x
  870. new_line = Line((xL, self(x=xL)), (xR, self(x=xR)))
  871. elif y is not None:
  872. is_sequence(y, length=2)
  873. yL, yR = y
  874. new_line = Line((xL, self(y=xL)), (xR, self(y=xR)))
  875. self.shapes['line'] = new_line['line']
  876. return self
  877. # First implementation of class Circle
  878. class Circle(Shape):
  879. def __init__(self, center, radius, resolution=180):
  880. self.center, self.radius = center, radius
  881. self.resolution = resolution
  882. t = linspace(0, 2*pi, resolution+1)
  883. x0 = center[0]; y0 = center[1]
  884. R = radius
  885. x = x0 + R*cos(t)
  886. y = y0 + R*sin(t)
  887. self.shapes = {'circle': Curve(x, y)}
  888. def __call__(self, theta):
  889. """
  890. Return (x, y) point corresponding to angle theta.
  891. Not valid after a translation, rotation, or scaling.
  892. """
  893. return self.center[0] + self.radius*cos(theta), \
  894. self.center[1] + self.radius*sin(theta)
  895. class Arc(Shape):
  896. def __init__(self, center, radius,
  897. start_angle, arc_angle,
  898. resolution=180):
  899. is_sequence(center)
  900. # Must record some parameters for __call__
  901. self.center = arr2D(center)
  902. self.radius = radius
  903. self.start_angle = radians(start_angle)
  904. self.arc_angle = radians(arc_angle)
  905. t = linspace(self.start_angle,
  906. self.start_angle + self.arc_angle,
  907. resolution+1)
  908. x0 = center[0]; y0 = center[1]
  909. R = radius
  910. x = x0 + R*cos(t)
  911. y = y0 + R*sin(t)
  912. self.shapes = {'arc': Curve(x, y)}
  913. # Cannot set dimensions (Arc_wText recurses into this
  914. # constructor forever). Set in test_Arc instead.
  915. def geometric_features(self):
  916. a = self.shapes['arc']
  917. m = len(a.x)//2 # mid point in array
  918. d = {'start': point(a.x[0], a.y[0]),
  919. 'end': point(a.x[-1], a.y[-1]),
  920. 'mid': point(a.x[m], a.y[m])}
  921. return d
  922. def __call__(self, theta):
  923. """
  924. Return (x,y) point at start_angle + theta.
  925. Not valid after translation, rotation, or scaling.
  926. """
  927. theta = radians(theta)
  928. t = self.start_angle + theta
  929. x0 = self.center[0]
  930. y0 = self.center[1]
  931. R = self.radius
  932. x = x0 + R*cos(t)
  933. y = y0 + R*sin(t)
  934. return (x, y)
  935. # Alternative for small arcs: Parabola
  936. class Parabola(Shape):
  937. def __init__(self, start, mid, stop, resolution=21):
  938. self.p1, self.p2, self.p3 = start, mid, stop
  939. # y as function of x? (no point on line x=const?)
  940. tol = 1E-14
  941. if abs(self.p1[0] - self.p2[0]) > 1E-14 and \
  942. abs(self.p2[0] - self.p3[0]) > 1E-14 and \
  943. abs(self.p3[0] - self.p1[0]) > 1E-14:
  944. self.y_of_x = True
  945. else:
  946. self.y_of_x = False
  947. # x as function of y? (no point on line y=const?)
  948. tol = 1E-14
  949. if abs(self.p1[1] - self.p2[1]) > 1E-14 and \
  950. abs(self.p2[1] - self.p3[1]) > 1E-14 and \
  951. abs(self.p3[1] - self.p1[1]) > 1E-14:
  952. self.x_of_y = True
  953. else:
  954. self.x_of_y = False
  955. if self.y_of_x:
  956. x = linspace(start[0], end[0], resolution)
  957. y = self(x=x)
  958. elif self.x_of_y:
  959. y = linspace(start[1], end[1], resolution)
  960. x = self(y=y)
  961. else:
  962. raise ValueError(
  963. 'Parabola: two or more points lie on x=const '
  964. 'or y=const - not allowed')
  965. self.shapes = {'parabola': Curve(x, y)}
  966. def __call__(self, x=None, y=None):
  967. if x is not None and self.y_of_x:
  968. return self._L2x(self.p1, self.p2)*self.p3[1] + \
  969. self._L2x(self.p2, self.p3)*self.p1[1] + \
  970. self._L2x(self.p3, self.p1)*self.p2[1]
  971. elif y is not None and self.x_of_y:
  972. return self._L2y(self.p1, self.p2)*self.p3[0] + \
  973. self._L2y(self.p2, self.p3)*self.p1[0] + \
  974. self._L2y(self.p3, self.p1)*self.p2[0]
  975. else:
  976. raise ValueError(
  977. 'Parabola.__call__(x=%s, y=%s) not meaningful' % \
  978. (x, y))
  979. def _L2x(self, x, pi, pj, pk):
  980. return (x - pi[0])*(x - pj[0])/((pk[0] - pi[0])*(pk[0] - pj[0]))
  981. def _L2y(self, y, pi, pj, pk):
  982. return (y - pi[1])*(y - pj[1])/((pk[1] - pi[1])*(pk[1] - pj[1]))
  983. class Circle(Arc):
  984. def __init__(self, center, radius, resolution=180):
  985. Arc.__init__(self, center, radius, 0, 360, resolution)
  986. class Wall(Shape):
  987. def __init__(self, x, y, thickness, pattern='/', transparent=False):
  988. is_sequence(x, y, length=len(x))
  989. if isinstance(x[0], (tuple,list,ndarray)):
  990. # x is list of curves
  991. x1 = concatenate(x)
  992. else:
  993. x1 = asarray(x, float)
  994. if isinstance(y[0], (tuple,list,ndarray)):
  995. # x is list of curves
  996. y1 = concatenate(y)
  997. else:
  998. y1 = asarray(y, float)
  999. self.x1 = x1; self.y1 = y1
  1000. # Displaced curve (according to thickness)
  1001. x2 = x1
  1002. y2 = y1 + thickness
  1003. # Combine x1,y1 with x2,y2 reversed
  1004. from numpy import concatenate
  1005. x = concatenate((x1, x2[-1::-1]))
  1006. y = concatenate((y1, y2[-1::-1]))
  1007. wall = Curve(x, y)
  1008. wall.set_filled_curves(color='white', pattern=pattern)
  1009. x = [x1[-1]] + x2[-1::-1].tolist() + [x1[0]]
  1010. y = [y1[-1]] + y2[-1::-1].tolist() + [y1[0]]
  1011. self.shapes = {'wall': wall}
  1012. from collections import OrderedDict
  1013. self.shapes = OrderedDict()
  1014. self.shapes['wall'] = wall
  1015. if transparent:
  1016. white_eraser = Curve(x, y)
  1017. white_eraser.set_linecolor('white')
  1018. self.shapes['eraser'] = white_eraser
  1019. def geometric_features(self):
  1020. d = {'start': point(self.x1[0], self.y1[0]),
  1021. 'end': point(self.x1[-1], self.y1[-1])}
  1022. return d
  1023. class Wall2(Shape):
  1024. def __init__(self, x, y, thickness, pattern='/'):
  1025. is_sequence(x, y, length=len(x))
  1026. if isinstance(x[0], (tuple,list,ndarray)):
  1027. # x is list of curves
  1028. x1 = concatenate(x)
  1029. else:
  1030. x1 = asarray(x, float)
  1031. if isinstance(y[0], (tuple,list,ndarray)):
  1032. # x is list of curves
  1033. y1 = concatenate(y)
  1034. else:
  1035. y1 = asarray(y, float)
  1036. self.x1 = x1; self.y1 = y1
  1037. # Displaced curve (according to thickness)
  1038. x2 = x1.copy()
  1039. y2 = y1.copy()
  1040. def displace(idx, idx_m, idx_p):
  1041. # Find tangent and normal
  1042. tangent = point(x1[idx_m], y1[idx_m]) - point(x1[idx_p], y1[idx_p])
  1043. tangent = unit_vec(tangent)
  1044. normal = point(tangent[1], -tangent[0])
  1045. # Displace length "thickness" in "positive" normal direction
  1046. displaced_pt = point(x1[idx], y1[idx]) + thickness*normal
  1047. x2[idx], y2[idx] = displaced_pt
  1048. for i in range(1, len(x1)-1):
  1049. displace(i-1, i+1, i) # centered difference for normal comp.
  1050. # One-sided differences at the end points
  1051. i = 0
  1052. displace(i, i+1, i)
  1053. i = len(x1)-1
  1054. displace(i-1, i, i)
  1055. # Combine x1,y1 with x2,y2 reversed
  1056. from numpy import concatenate
  1057. x = concatenate((x1, x2[-1::-1]))
  1058. y = concatenate((y1, y2[-1::-1]))
  1059. wall = Curve(x, y)
  1060. wall.set_filled_curves(color='white', pattern=pattern)
  1061. x = [x1[-1]] + x2[-1::-1].tolist() + [x1[0]]
  1062. y = [y1[-1]] + y2[-1::-1].tolist() + [y1[0]]
  1063. self.shapes['wall'] = wall
  1064. def geometric_features(self):
  1065. d = {'start': point(self.x1[0], self.y1[0]),
  1066. 'end': point(self.x1[-1], self.y1[-1])}
  1067. return d
  1068. class VelocityProfile(Shape):
  1069. def __init__(self, start, height, profile, num_arrows, scaling=1):
  1070. # vx, vy = profile(y)
  1071. shapes = {}
  1072. # Draw left line
  1073. shapes['start line'] = Line(start, (start[0], start[1]+height))
  1074. # Draw velocity arrows
  1075. dy = float(height)/(num_arrows-1)
  1076. x = start[0]
  1077. y = start[1]
  1078. r = profile(y) # Test on return type
  1079. if not isinstance(r, (list,tuple,ndarray)) and len(r) != 2:
  1080. raise TypeError('VelocityProfile constructor: profile(y) function must return velocity vector (vx,vy), not %s' % type(r))
  1081. for i in range(num_arrows):
  1082. y = start[1] + i*dy
  1083. vx, vy = profile(y)
  1084. if abs(vx) < 1E-8:
  1085. continue
  1086. vx *= scaling
  1087. vy *= scaling
  1088. arr = Arrow1((x,y), (x+vx, y+vy), '->')
  1089. shapes['arrow%d' % i] = arr
  1090. # Draw smooth profile
  1091. xs = []
  1092. ys = []
  1093. n = 100
  1094. dy = float(height)/n
  1095. for i in range(n+2):
  1096. y = start[1] + i*dy
  1097. vx, vy = profile(y)
  1098. vx *= scaling
  1099. vy *= scaling
  1100. xs.append(x+vx)
  1101. ys.append(y+vy)
  1102. shapes['smooth curve'] = Curve(xs, ys)
  1103. self.shapes = shapes
  1104. class Arrow1(Shape):
  1105. """Draw an arrow as Line with arrow."""
  1106. def __init__(self, start, end, style='->'):
  1107. arrow = Line(start, end)
  1108. arrow.set_arrow(style)
  1109. # Note:
  1110. self.shapes = {'arrow': arrow}
  1111. def geometric_features(self):
  1112. return self.shapes['arrow'].geometric_features()
  1113. class Arrow3(Shape):
  1114. """
  1115. Build a vertical line and arrow head from Line objects.
  1116. Then rotate `rotation_angle`.
  1117. """
  1118. def __init__(self, start, length, rotation_angle=0):
  1119. self.bottom = start
  1120. self.length = length
  1121. self.angle = rotation_angle
  1122. top = (self.bottom[0], self.bottom[1] + self.length)
  1123. main = Line(self.bottom, top)
  1124. #head_length = self.length/8.0
  1125. head_length = drawing_tool.xrange/50.
  1126. head_degrees = radians(30)
  1127. head_left_pt = (top[0] - head_length*sin(head_degrees),
  1128. top[1] - head_length*cos(head_degrees))
  1129. head_right_pt = (top[0] + head_length*sin(head_degrees),
  1130. top[1] - head_length*cos(head_degrees))
  1131. head_left = Line(head_left_pt, top)
  1132. head_right = Line(head_right_pt, top)
  1133. head_left.set_linestyle('solid')
  1134. head_right.set_linestyle('solid')
  1135. self.shapes = {'line': main, 'head left': head_left,
  1136. 'head right': head_right}
  1137. # rotate goes through self.shapes so self.shapes
  1138. # must be initialized first
  1139. self.rotate(rotation_angle, start)
  1140. def geometric_features(self):
  1141. return self.shapes['line'].geometric_features()
  1142. class Text(Point):
  1143. """
  1144. Place `text` at the (x,y) point `position`, with the given
  1145. fontsize (0 indicates that the default fontsize set in drawing_tool
  1146. is to be used). The text is centered around `position` if `alignment` is
  1147. 'center'; if 'left', the text starts at `position`, and if
  1148. 'right', the right and of the text is located at `position`.
  1149. """
  1150. def __init__(self, text, position, alignment='center', fontsize=0,
  1151. bgcolor=None, fgcolor=None, fontfamily=None):
  1152. """
  1153. fontfamily can be (e.g.) 'serif' or 'monospace' (for code!).
  1154. """
  1155. is_sequence(position)
  1156. is_sequence(position, length=2, can_be_None=True)
  1157. self.text = text
  1158. self.position = position
  1159. self.alignment = alignment
  1160. self.fontsize = fontsize
  1161. self.bgcolor = bgcolor
  1162. self.fgcolor = fgcolor
  1163. self.fontfamily = fontfamily
  1164. Point.__init__(self, position[0], position[1])
  1165. #no need for self.shapes here
  1166. def draw(self, verbose=0):
  1167. drawing_tool.text(
  1168. self.text, (self.x, self.y),
  1169. self.alignment, self.fontsize,
  1170. arrow_tip=None, bgcolor=self.bgcolor, fgcolor=self.fgcolor,
  1171. fontfamily=self.fontfamily)
  1172. if verbose > 0:
  1173. print('drawing Text "%s"' % self.text)
  1174. def __str__(self):
  1175. return 'text "%s" at (%g,%g)' % (self.text, self.x, self.y)
  1176. def __repr__(self):
  1177. return repr(str(self))
  1178. class Text_wArrow(Text):
  1179. """
  1180. As class Text, but an arrow is drawn from the mid part of the text
  1181. to some point `arrow_tip`.
  1182. """
  1183. def __init__(self, text, position, arrow_tip,
  1184. alignment='center', fontsize=0):
  1185. is_sequence(arrow_tip, length=2, can_be_None=True)
  1186. is_sequence(position)
  1187. self.arrow_tip = arrow_tip
  1188. Text.__init__(self, text, position, alignment, fontsize)
  1189. def draw(self, verbose=0):
  1190. drawing_tool.text(
  1191. self.text, self.position,
  1192. self.alignment, self.fontsize,
  1193. arrow_tip=self.arrow_tip,
  1194. bgcolor=self.bgcolor, fgcolor=self.fgcolor,
  1195. fontfamily=self.fontfamily)
  1196. if verbose > 0:
  1197. print('drawing Text_wArrow "%s"' % self.text)
  1198. def __str__(self):
  1199. return 'annotation "%s" at (%g,%g) with arrow to (%g,%g)' % \
  1200. (self.text, self.x, self.y,
  1201. self.arrow_tip[0], self.arrow_tip[1])
  1202. def __repr__(self):
  1203. return repr(str(self))
  1204. class Axis(Shape):
  1205. def __init__(self, start, length, label,
  1206. rotation_angle=0, fontsize=0,
  1207. label_spacing=1./45, label_alignment='left'):
  1208. """
  1209. Draw axis from start with `length` to the right
  1210. (x axis). Place label at the end of the arrow tip.
  1211. Then return `rotation_angle` (in degrees).
  1212. The `label_spacing` denotes the space between the label
  1213. and the arrow tip as a fraction of the length of the plot
  1214. in x direction. A tuple can be given to adjust the position
  1215. in both the x and y directions (with one parameter, the
  1216. x position is adjusted).
  1217. With `label_alignment` one can place
  1218. the axis label text such that the arrow tip is to the 'left',
  1219. 'right', or 'center' with respect to the text field.
  1220. The `label_spacing` and `label_alignment`parameters can
  1221. be used to fine-tune the location of the label.
  1222. """
  1223. # Arrow is vertical arrow, make it horizontal
  1224. arrow = Arrow3(start, length, rotation_angle=-90)
  1225. arrow.rotate(rotation_angle, start)
  1226. if isinstance(label_spacing, (list,tuple)) and len(label_spacing) == 2:
  1227. x_spacing = drawing_tool.xrange*label_spacing[0]
  1228. y_spacing = drawing_tool.yrange*label_spacing[1]
  1229. elif isinstance(label_spacing, (int,float)):
  1230. # just x spacing
  1231. x_spacing = drawing_tool.xrange*label_spacing
  1232. y_spacing = 0
  1233. # should increase spacing for downward pointing axis
  1234. label_pos = [start[0] + length + x_spacing, start[1] + y_spacing]
  1235. label = Text(label, position=label_pos, fontsize=fontsize)
  1236. label.rotate(rotation_angle, start)
  1237. self.shapes = {'arrow': arrow, 'label': label}
  1238. def geometric_features(self):
  1239. return self.shapes['arrow'].geometric_features()
  1240. # Maybe Axis3 with label below/above?
  1241. class Force(Arrow1):
  1242. """
  1243. Indication of a force by an arrow and a text (symbol). Draw an
  1244. arrow, starting at `start` and with the tip at `end`. The symbol
  1245. is placed at `text_pos`, which can be 'start', 'end' or the
  1246. coordinates of a point. If 'end' or 'start', the text is placed at
  1247. a distance `text_spacing` times the width of the total plotting
  1248. area away from the specified point.
  1249. """
  1250. def __init__(self, start, end, text, text_spacing=1./60,
  1251. fontsize=0, text_pos='start', text_alignment='center'):
  1252. Arrow1.__init__(self, start, end, style='->')
  1253. if isinstance(text_spacing, (tuple,list)):
  1254. if len(text_spacing) == 2:
  1255. spacing = point(drawing_tool.xrange*text_spacing[0],
  1256. drawing_tool.xrange*text_spacing[1])
  1257. else:
  1258. spacing = drawing_tool.xrange*text_spacing[0]
  1259. else:
  1260. # just a number, this is x spacing
  1261. spacing = drawing_tool.xrange*text_spacing
  1262. start, end = arr2D(start), arr2D(end)
  1263. # Two cases: label at bottom of line or top, need more
  1264. # spacing if bottom
  1265. downward = (end-start)[1] < 0
  1266. upward = not downward # for easy code reading
  1267. if isinstance(text_pos, (str,bytes)):
  1268. if text_pos == 'start':
  1269. spacing_dir = unit_vec(start - end)
  1270. if upward:
  1271. spacing *= 1.7
  1272. if isinstance(spacing, (int, float)):
  1273. text_pos = start + spacing*spacing_dir
  1274. else:
  1275. text_pos = start + spacing
  1276. elif text_pos == 'end':
  1277. spacing_dir = unit_vec(end - start)
  1278. if downward:
  1279. spacing *= 1.7
  1280. if isinstance(spacing, (int, float)):
  1281. text_pos = end + spacing*spacing_dir
  1282. else:
  1283. text_pos = end + spacing
  1284. self.shapes['text'] = Text(text, text_pos, fontsize=fontsize,
  1285. alignment=text_alignment)
  1286. def geometric_features(self):
  1287. d = Arrow1.geometric_features(self)
  1288. d['symbol_location'] = self.shapes['text'].position
  1289. return d
  1290. class Axis2(Force):
  1291. def __init__(self, start, length, label,
  1292. rotation_angle=0, fontsize=0,
  1293. label_spacing=1./45, label_alignment='left'):
  1294. direction = point(cos(radians(rotation_angle)),
  1295. sin(radians(rotation_angle)))
  1296. Force.__init__(start=start, end=length*direction, text=label,
  1297. text_spacing=label_spacing,
  1298. fontsize=fontsize, text_pos='end',
  1299. text_alignment=label_alignment)
  1300. # Substitute text by label for axis
  1301. self.shapes['label'] = self.shapes['text']
  1302. del self.shapes['text']
  1303. # geometric features from Force is ok
  1304. class Gravity(Axis):
  1305. """Downward-pointing gravity arrow with the symbol g."""
  1306. def __init__(self, start, length, fontsize=0):
  1307. Axis.__init__(self, start, length, '$g$', below=False,
  1308. rotation_angle=-90, label_spacing=1./30,
  1309. fontsize=fontsize)
  1310. self.shapes['arrow'].set_linecolor('black')
  1311. class Gravity(Force):
  1312. """Downward-pointing gravity arrow with the symbol g."""
  1313. def __init__(self, start, length, text='$g$', fontsize=0):
  1314. Force.__init__(self, start, (start[0], start[1]-length),
  1315. text, text_spacing=1./60,
  1316. fontsize=0, text_pos='end')
  1317. self.shapes['arrow'].set_linecolor('black')
  1318. class Distance_wText(Shape):
  1319. """
  1320. Arrow <-> with text (usually a symbol) at the midpoint, used for
  1321. identifying a some distance in a figure. The text is placed
  1322. slightly to the right of vertical-like arrows, with text displaced
  1323. `text_spacing` times to total distance in x direction of the plot
  1324. area. The text is by default aligned 'left' in this case. For
  1325. horizontal-like arrows, the text is placed the same distance
  1326. above, but aligned 'center' by default (when `alignment` is None).
  1327. """
  1328. def __init__(self, start, end, text, fontsize=0, text_spacing=1/60.,
  1329. alignment=None, text_pos='mid'):
  1330. start = arr2D(start)
  1331. end = arr2D(end)
  1332. # Decide first if we have a vertical or horizontal arrow
  1333. vertical = abs(end[0]-start[0]) < 2*abs(end[1]-start[1])
  1334. if vertical:
  1335. # Assume end above start
  1336. if end[1] < start[1]:
  1337. start, end = end, start
  1338. if alignment is None:
  1339. alignment = 'left'
  1340. else: # horizontal arrow
  1341. # Assume start to the right of end
  1342. if start[0] < end[0]:
  1343. start, end = end, start
  1344. if alignment is None:
  1345. alignment = 'center'
  1346. tangent = end - start
  1347. # Tangeng goes always to the left and upward
  1348. normal = unit_vec([tangent[1], -tangent[0]])
  1349. mid = 0.5*(start + end) # midpoint of start-end line
  1350. if text_pos == 'mid':
  1351. text_pos = mid + normal*drawing_tool.xrange*text_spacing
  1352. text = Text(text, text_pos, fontsize=fontsize,
  1353. alignment=alignment)
  1354. else:
  1355. is_sequence(text_pos, length=2)
  1356. text = Text_wArrow(text, text_pos, mid, alignment='left',
  1357. fontsize=fontsize)
  1358. arrow = Arrow1(start, end, style='<->')
  1359. arrow.set_linecolor('black')
  1360. arrow.set_linewidth(1)
  1361. self.shapes = {'arrow': arrow, 'text': text}
  1362. def geometric_features(self):
  1363. d = self.shapes['arrow'].geometric_features()
  1364. d['text_position'] = self.shapes['text'].position
  1365. return d
  1366. class Arc_wText(Shape):
  1367. def __init__(self, text, center, radius,
  1368. start_angle, arc_angle, fontsize=0,
  1369. resolution=180, text_spacing=1/60.):
  1370. arc = Arc(center, radius, start_angle, arc_angle,
  1371. resolution)
  1372. mid = arr2D(arc(arc_angle/2.))
  1373. normal = unit_vec(mid - arr2D(center))
  1374. text_pos = mid + normal*drawing_tool.xrange*text_spacing
  1375. self.shapes = {'arc': arc,
  1376. 'text': Text(text, text_pos, fontsize=fontsize)}
  1377. class Composition(Shape):
  1378. def __init__(self, shapes):
  1379. """shapes: list or dict of Shape objects."""
  1380. if isinstance(shapes, (tuple,list)):
  1381. # Convert to dict using the type of the list element as key
  1382. # (add a counter to make the keys unique)
  1383. shapes = {s.__class__.__name__ + '_' + str(i): s
  1384. for i, s in enumerate(shapes)}
  1385. self.shapes = shapes
  1386. # can make help methods: Line.midpoint, Line.normal(pt, dir='left') -> (x,y)
  1387. # list annotations in each class? contains extra annotations for explaining
  1388. # important parameters to the constructor, e.g., Line.annotations holds
  1389. # start and end as Text objects. Shape.demo calls shape.draw and
  1390. # for annotation in self.demo: annotation.draw() YES!
  1391. # Can make overall demo of classes by making objects and calling demo
  1392. # Could include demo fig in each constructor
  1393. class SimplySupportedBeam(Shape):
  1394. def __init__(self, pos, size):
  1395. pos = arr2D(pos)
  1396. P0 = (pos[0] - size/2., pos[1]-size)
  1397. P1 = (pos[0] + size/2., pos[1]-size)
  1398. triangle = Triangle(P0, P1, pos)
  1399. gap = size/5.
  1400. h = size/4. # height of rectangle
  1401. P2 = (P0[0], P0[1]-gap-h)
  1402. rectangle = Rectangle(P2, size, h).set_filled_curves(pattern='/')
  1403. self.shapes = {'triangle': triangle, 'rectangle': rectangle}
  1404. self.dimensions = {'pos': Text('pos', pos),
  1405. 'size': Distance_wText((P2[0], P2[1]-size),
  1406. (P2[0]+size, P2[1]-size),
  1407. 'size')}
  1408. def geometric_features(self):
  1409. t = self.shapes['triangle']
  1410. r = self.shapes['rectangle']
  1411. d = {'pos': t.geometric_features()['p2'],
  1412. 'mid_support': r.geometric_features()['lower_mid']}
  1413. return d
  1414. class ConstantBeamLoad(Shape):
  1415. """
  1416. Downward-pointing arrows indicating a vertical load.
  1417. The arrows are of equal length and filling a rectangle
  1418. specified as in the :class:`Rectangle` class.
  1419. Recorded geometric features:
  1420. ==================== =============================================
  1421. Attribute Description
  1422. ==================== =============================================
  1423. mid_top Middle point at the top of the row of
  1424. arrows (often used for positioning a text).
  1425. ==================== =============================================
  1426. """
  1427. def __init__(self, lower_left_corner, width, height, num_arrows=10):
  1428. box = Rectangle(lower_left_corner, width, height)
  1429. self.shapes = {'box': box}
  1430. dx = float(width)/(num_arrows-1)
  1431. y_top = lower_left_corner[1] + height
  1432. y_tip = lower_left_corner[1]
  1433. for i in range(num_arrows):
  1434. x = lower_left_corner[0] + i*dx
  1435. self.shapes['arrow%d' % i] = Arrow1((x, y_top), (x, y_tip))
  1436. def geometric_features(self):
  1437. return {'mid_top': self.shapes['box'].geometric_features()['upper_mid']}
  1438. class Moment(Arc_wText):
  1439. def __init__(self, text, center, radius,
  1440. left=True, counter_clockwise=True,
  1441. fontsize=0, text_spacing=1/60.):
  1442. style = '->' if counter_clockwise else '<-'
  1443. start_angle = 90 if left else -90
  1444. Arc_wText.__init__(self, text, center, radius,
  1445. start_angle=start_angle,
  1446. arc_angle=180, fontsize=fontsize,
  1447. text_spacing=text_spacing,
  1448. resolution=180)
  1449. self.shapes['arc']['arc'].set_arrow(style) # Curve object
  1450. class Wheel(Shape):
  1451. def __init__(self, center, radius, inner_radius=None, nlines=10):
  1452. if inner_radius is None:
  1453. inner_radius = radius/5.0
  1454. outer = Circle(center, radius)
  1455. inner = Circle(center, inner_radius)
  1456. lines = []
  1457. # Draw nlines+1 since the first and last coincide
  1458. # (then nlines lines will be visible)
  1459. t = linspace(0, 2*pi, self.nlines+1)
  1460. Ri = inner_radius; Ro = radius
  1461. x0 = center[0]; y0 = center[1]
  1462. xinner = x0 + Ri*cos(t)
  1463. yinner = y0 + Ri*sin(t)
  1464. xouter = x0 + Ro*cos(t)
  1465. youter = y0 + Ro*sin(t)
  1466. lines = [Line((xi,yi),(xo,yo)) for xi, yi, xo, yo in \
  1467. zip(xinner, yinner, xouter, youter)]
  1468. self.shapes = {'inner': inner, 'outer': outer,
  1469. 'spokes': Composition(
  1470. {'spoke%d' % i: lines[i]
  1471. for i in range(len(lines))})}
  1472. class SineWave(Shape):
  1473. def __init__(self, xstart, xstop,
  1474. wavelength, amplitude, mean_level):
  1475. self.xstart = xstart
  1476. self.xstop = xstop
  1477. self.wavelength = wavelength
  1478. self.amplitude = amplitude
  1479. self.mean_level = mean_level
  1480. npoints = (self.xstop - self.xstart)/(self.wavelength/61.0)
  1481. x = linspace(self.xstart, self.xstop, npoints)
  1482. k = 2*pi/self.wavelength # frequency
  1483. y = self.mean_level + self.amplitude*sin(k*x)
  1484. self.shapes = {'waves': Curve(x,y)}
  1485. class Spring(Shape):
  1486. """
  1487. Specify a *vertical* spring, starting at `start` and with `length`
  1488. as total vertical length. In the middle of the spring there are
  1489. `num_windings` circular windings to illustrate the spring. If
  1490. `teeth` is true, the spring windings look like saw teeth,
  1491. otherwise the windings are smooth circles. The parameters `width`
  1492. (total width of spring) and `bar_length` (length of first and last
  1493. bar are given sensible default values if they are not specified
  1494. (these parameters can later be extracted as attributes, see table
  1495. below).
  1496. """
  1497. spring_fraction = 1./2 # fraction of total length occupied by spring
  1498. def __init__(self, start, length, width=None, bar_length=None,
  1499. num_windings=11, teeth=False):
  1500. B = start
  1501. n = num_windings - 1 # n counts teeth intervals
  1502. if n <= 6:
  1503. n = 7
  1504. # n must be odd:
  1505. if n % 2 == 0:
  1506. n = n+1
  1507. L = length
  1508. if width is None:
  1509. w = L/10.
  1510. else:
  1511. w = width/2.0
  1512. s = bar_length
  1513. # [0, x, L-x, L], f = (L-2*x)/L
  1514. # x = L*(1-f)/2.
  1515. # B: start point
  1516. # w: half-width
  1517. # L: total length
  1518. # s: length of first bar
  1519. # P0: start of dashpot (B[0]+s)
  1520. # P1: end of dashpot
  1521. # P2: end point
  1522. shapes = {}
  1523. if s is None:
  1524. f = Spring.spring_fraction
  1525. s = L*(1-f)/2. # start of spring
  1526. self.bar_length = s # record
  1527. self.width = 2*w
  1528. P0 = (B[0], B[1] + s)
  1529. P1 = (B[0], B[1] + L-s)
  1530. P2 = (B[0], B[1] + L)
  1531. if s >= L:
  1532. raise ValueError('length of first bar: %g is larger than total length: %g' % (s, L))
  1533. shapes['bar1'] = Line(B, P0)
  1534. spring_length = L - 2*s
  1535. t = spring_length/n # height increment per winding
  1536. if teeth:
  1537. resolution = 4
  1538. else:
  1539. resolution = 90
  1540. q = linspace(0, n, n*resolution + 1)
  1541. x = P0[0] + w*sin(2*pi*q)
  1542. y = P0[1] + q*t
  1543. shapes['spiral'] = Curve(x, y)
  1544. shapes['bar2'] = Line(P1,P2)
  1545. self.shapes = shapes
  1546. # Dimensions
  1547. start = Text_wArrow('start', (B[0]-1.5*w,B[1]-1.5*w), B)
  1548. width = Distance_wText((B[0]-w, B[1]-3.5*w), (B[0]+w, B[1]-3.5*w),
  1549. 'width')
  1550. length = Distance_wText((B[0]+3*w, B[1]), (B[0]+3*w, B[1]+L),
  1551. 'length')
  1552. num_windings = Text_wArrow('num_windings',
  1553. (B[0]+2*w,P2[1]+w),
  1554. (B[0]+1.2*w, B[1]+L/2.))
  1555. blength1 = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
  1556. 'bar_length',
  1557. text_pos=(P0[0]-7*w, P0[1]+w))
  1558. blength2 = Distance_wText((P1[0]-2*w, P1[1]), (P2[0]-2*w, P2[1]),
  1559. 'bar_length',
  1560. text_pos=(P2[0]-7*w, P2[1]+w))
  1561. dims = {'start': start, 'width': width, 'length': length,
  1562. 'num_windings': num_windings, 'bar_length1': blength1,
  1563. 'bar_length2': blength2}
  1564. self.dimensions = dims
  1565. def geometric_features(self):
  1566. """
  1567. Recorded geometric features:
  1568. ==================== =============================================
  1569. Attribute Description
  1570. ==================== =============================================
  1571. start Start point of spring.
  1572. end End point of spring.
  1573. width Total width of spring.
  1574. bar_length Length of first (and last) bar part.
  1575. ==================== =============================================
  1576. """
  1577. b1 = self.shapes['bar1']
  1578. d = {'start': b1.geometric_features()['start'],
  1579. 'end': self.shapes['bar2'].geometric_features()['end'],
  1580. 'bar_length': self.bar_length,
  1581. 'width': self.width}
  1582. return d
  1583. class Dashpot(Shape):
  1584. """
  1585. Specify a vertical dashpot of height `total_length` and `start` as
  1586. bottom/starting point. The first bar part has length `bar_length`.
  1587. Then comes the dashpot as a rectangular construction of total
  1588. width `width` and height `dashpot_length`. The position of the
  1589. piston inside the rectangular dashpot area is given by
  1590. `piston_pos`, which is the distance between the first bar (given
  1591. by `bar_length`) to the piston.
  1592. If some of `dashpot_length`, `bar_length`, `width` or `piston_pos`
  1593. are not given, suitable default values are calculated. Their
  1594. values can be extracted as keys in the dict returned from
  1595. ``geometric_features``.
  1596. """
  1597. dashpot_fraction = 1./2 # fraction of total_length
  1598. piston_gap_fraction = 1./6 # fraction of width
  1599. piston_thickness_fraction = 1./8 # fraction of dashplot_length
  1600. def __init__(self, start, total_length, bar_length=None,
  1601. width=None, dashpot_length=None, piston_pos=None):
  1602. B = start
  1603. L = total_length
  1604. if width is None:
  1605. w = L/10. # total width 1/5 of length
  1606. else:
  1607. w = width/2.0
  1608. s = bar_length
  1609. # [0, x, L-x, L], f = (L-2*x)/L
  1610. # x = L*(1-f)/2.
  1611. # B: start point
  1612. # w: half-width
  1613. # L: total length
  1614. # s: length of first bar
  1615. # P0: start of dashpot (B[0]+s)
  1616. # P1: end of dashpot
  1617. # P2: end point
  1618. shapes = {}
  1619. # dashpot is P0-P1 in y and width 2*w
  1620. if dashpot_length is None:
  1621. if s is None:
  1622. f = Dashpot.dashpot_fraction
  1623. s = L*(1-f)/2. # default
  1624. P1 = (B[0], B[1]+L-s)
  1625. dashpot_length = f*L
  1626. else:
  1627. if s is None:
  1628. f = 1./2 # the bar lengths are taken as f*dashpot_length
  1629. s = f*dashpot_length # default
  1630. P1 = (B[0], B[1]+s+dashpot_length)
  1631. P0 = (B[0], B[1]+s)
  1632. P2 = (B[0], B[1]+L)
  1633. if P2[1] > P1[1] > P0[1]:
  1634. pass # ok
  1635. else:
  1636. raise ValueError('Dashpot has inconsistent dimensions! start: %g, dashpot begin: %g, dashpot end: %g, very end: %g' % (B[1], P0[1], P1[1], P2[1]))
  1637. shapes['line start'] = Line(B, P0)
  1638. shapes['pot'] = Curve([P1[0]-w, P0[0]-w, P0[0]+w, P1[0]+w],
  1639. [P1[1], P0[1], P0[1], P1[1]])
  1640. piston_thickness = dashpot_length*Dashpot.piston_thickness_fraction
  1641. if piston_pos is None:
  1642. piston_pos = 1/3.*dashpot_length
  1643. if piston_pos < 0:
  1644. piston_pos = 0
  1645. elif piston_pos > dashpot_length:
  1646. piston_pos = dashpot_length - piston_thickness
  1647. abs_piston_pos = P0[1] + piston_pos
  1648. gap = w*Dashpot.piston_gap_fraction
  1649. shapes['piston'] = Composition(
  1650. {'line': Line(P2, (B[0], abs_piston_pos + piston_thickness)),
  1651. 'rectangle': Rectangle((B[0] - w+gap, abs_piston_pos),
  1652. 2*w-2*gap, piston_thickness),
  1653. })
  1654. shapes['piston']['rectangle'].set_filled_curves(pattern='X')
  1655. self.shapes = shapes
  1656. self.bar_length = s
  1657. self.width = 2*w
  1658. self.piston_pos = piston_pos
  1659. self.dashpot_length = dashpot_length
  1660. # Dimensions
  1661. start = Text_wArrow('start', (B[0]-1.5*w,B[1]-1.5*w), B)
  1662. width = Distance_wText((B[0]-w, B[1]-3.5*w), (B[0]+w, B[1]-3.5*w),
  1663. 'width')
  1664. dplength = Distance_wText((B[0]+2*w, P0[1]), (B[0]+2*w, P1[1]),
  1665. 'dashpot_length', text_pos=(B[0]+w,B[1]-w))
  1666. blength = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
  1667. 'bar_length', text_pos=(B[0]-6*w,P0[1]-w))
  1668. ppos = Distance_wText((B[0]-2*w, P0[1]), (B[0]-2*w, P0[1]+piston_pos),
  1669. 'piston_pos', text_pos=(B[0]-6*w,P0[1]+piston_pos-w))
  1670. tlength = Distance_wText((B[0]+4*w, B[1]), (B[0]+4*w, B[1]+L),
  1671. 'total_length',
  1672. text_pos=(B[0]+4.5*w, B[1]+L-2*w))
  1673. line = Line((B[0]+w, abs_piston_pos), (B[0]+7*w, abs_piston_pos)).set_linestyle('dashed').set_linecolor('black').set_linewidth(1)
  1674. pp = Text('abs_piston_pos', (B[0]+7*w, abs_piston_pos), alignment='left')
  1675. dims = {'start': start, 'width': width, 'dashpot_length': dplength,
  1676. 'bar_length': blength, 'total_length': tlength,
  1677. 'piston_pos': ppos,}
  1678. #'abs_piston_pos': Composition({'line': line, 'text': pp})}
  1679. self.dimensions = dims
  1680. def geometric_features(self):
  1681. """
  1682. Recorded geometric features:
  1683. ==================== =============================================
  1684. Attribute Description
  1685. ==================== =============================================
  1686. start Start point of dashpot.
  1687. end End point of dashpot.
  1688. bar_length Length of first bar (from start to spring).
  1689. dashpot_length Length of dashpot middle part.
  1690. width Total width of dashpot.
  1691. piston_pos Position of piston in dashpot, relative to
  1692. start[1] + bar_length.
  1693. ==================== =============================================
  1694. """
  1695. d = {'start': self.shapes['line start'].geometric_features()['start'],
  1696. 'end': self.shapes['piston']['line'].geometric_features()['start'],
  1697. 'bar_length': self.bar_length,
  1698. 'piston_pos': self.piston_pos,
  1699. 'width': self.width,
  1700. 'dashpot_length': self.dashpot_length,
  1701. }
  1702. return d
  1703. class Wavy(Shape):
  1704. """
  1705. A wavy graph consisting of a user-given main curve y=f(x) with
  1706. additional sinusoidal waves of given (constant) amplitude,
  1707. but varying wavelength (a characteristic wavelength is specified).
  1708. """
  1709. def __init__(self, main_curve, interval, wavelength_of_perturbations,
  1710. amplitude_of_perturbations, smoothness):
  1711. """
  1712. ============================ ====================================
  1713. Name Description
  1714. ============================ ====================================
  1715. main_curve f(x) Python function
  1716. interval interval for main_curve
  1717. wavelength_of_perturbations dominant wavelength perturbed waves
  1718. amplitude_of_perturbations amplitude of perturbed waves
  1719. smoothness in [0, 1]: smooth=0, rough=1
  1720. ============================ ====================================
  1721. """
  1722. xmin, xmax = interval
  1723. L = wavelength_of_perturbations
  1724. k_0 = 2*pi/L # main frequency of waves
  1725. k_p = k_0*0.5
  1726. k_k = k_0/2*smoothness
  1727. A_0 = amplitude_of_perturbations
  1728. A_p = 0.3*A_0
  1729. A_k = k_0/2
  1730. x = linspace(xmin, xmax, 2001)
  1731. def w(x):
  1732. A = A_0 + A_p*sin(A_k*x)
  1733. k = k_0 + k_p*sin(k_k*x)
  1734. y = main_curve(x) + A*sin(k*x)
  1735. return y
  1736. self.shapes = {'wavy': Curve(x, w(x))}
  1737. # Use closure w to define __call__ - then we do not need
  1738. # to store all the parameters A_0, A_k, etc. as attributes
  1739. self.__call__ = w
  1740. class StochasticWavyCurve(object):
  1741. """
  1742. Precomputed stochastic wavy graphs.
  1743. There are three graphs with different look.
  1744. Curve 0:
  1745. ----------------------------------------------------------------------
  1746. |
  1747. |
  1748. *|
  1749. * |
  1750. * |
  1751. * |
  1752. * |
  1753. * |
  1754. * |
  1755. * |
  1756. * |
  1757. * |
  1758. * |
  1759. * |
  1760. |*
  1761. | *
  1762. | *
  1763. | *
  1764. | *
  1765. | *
  1766. | *
  1767. | *
  1768. | *
  1769. | *
  1770. | *
  1771. | *
  1772. | *
  1773. | *
  1774. | *
  1775. | *
  1776. | *
  1777. | *
  1778. | *
  1779. | *
  1780. | *
  1781. | *
  1782. | *
  1783. | *
  1784. | *
  1785. | *
  1786. | *
  1787. | *
  1788. | *
  1789. | *
  1790. | *
  1791. | *
  1792. | *
  1793. | *
  1794. | *
  1795. | *
  1796. | *
  1797. | *
  1798. | *
  1799. | *
  1800. | *
  1801. | *
  1802. | *
  1803. | *
  1804. | *
  1805. | *
  1806. | *
  1807. | *
  1808. | *
  1809. | *
  1810. | *
  1811. | *
  1812. | *
  1813. | *
  1814. | *
  1815. | *
  1816. | *
  1817. | *
  1818. |*
  1819. *|
  1820. * |
  1821. * |
  1822. * |
  1823. * |
  1824. * |
  1825. * |
  1826. * |
  1827. * |
  1828. * |
  1829. * |
  1830. * |
  1831. * |
  1832. * |
  1833. * |
  1834. * |
  1835. |*
  1836. | *
  1837. | *
  1838. | *
  1839. | *
  1840. | *
  1841. | *
  1842. | *
  1843. | *
  1844. | *
  1845. | *
  1846. | *
  1847. | *
  1848. | *
  1849. | *
  1850. | *
  1851. | *
  1852. | *
  1853. | *
  1854. | *
  1855. | *
  1856. | *
  1857. | *
  1858. | *
  1859. | *
  1860. | *
  1861. | *
  1862. | *
  1863. | *
  1864. | *
  1865. | *
  1866. | *
  1867. | *
  1868. | *
  1869. | *
  1870. | *
  1871. | *
  1872. | *
  1873. | *
  1874. | *
  1875. | *
  1876. | *
  1877. | *
  1878. | *
  1879. | *
  1880. |
  1881. * |
  1882. * |
  1883. * |
  1884. * |
  1885. * |
  1886. * |
  1887. * |
  1888. * |
  1889. * |
  1890. * |
  1891. * |
  1892. * |
  1893. * |
  1894. * |
  1895. * |
  1896. * |
  1897. * |
  1898. * |
  1899. * |
  1900. * |
  1901. * |
  1902. * |
  1903. * |
  1904. * |
  1905. * |
  1906. * |
  1907. * |
  1908. * |
  1909. * |
  1910. * |
  1911. Curve 2:
  1912. ----------------------------------------------------------------------
  1913. |
  1914. |
  1915. |
  1916. |*
  1917. |*
  1918. |*
  1919. |
  1920. |
  1921. *|
  1922. |*
  1923. | *
  1924. | *
  1925. | *
  1926. | *
  1927. | *
  1928. | *
  1929. | *
  1930. | *
  1931. | *
  1932. | *
  1933. | *
  1934. | *
  1935. | *
  1936. | *
  1937. | *
  1938. | *
  1939. | *
  1940. | *
  1941. | *
  1942. | *
  1943. | *
  1944. | *
  1945. | *
  1946. | *
  1947. | *
  1948. | *
  1949. | *
  1950. | *
  1951. | *
  1952. | *
  1953. | *
  1954. | *
  1955. | *
  1956. | *
  1957. | *
  1958. | *
  1959. | *
  1960. | *
  1961. | *
  1962. | *
  1963. | *
  1964. | *
  1965. | *
  1966. | *
  1967. | *
  1968. | *
  1969. | *
  1970. | *
  1971. | *
  1972. | *
  1973. | *
  1974. |
  1975. * |
  1976. * |
  1977. * |
  1978. * |
  1979. * |
  1980. * |
  1981. * |
  1982. * |
  1983. * |
  1984. * |
  1985. * |
  1986. * |
  1987. * |
  1988. * |
  1989. * |
  1990. * |
  1991. * |
  1992. * |
  1993. * |
  1994. * |
  1995. * |
  1996. * |
  1997. * |
  1998. * |
  1999. * |
  2000. * |
  2001. * |
  2002. * |
  2003. * |
  2004. |
  2005. | *
  2006. | *
  2007. | *
  2008. | *
  2009. | *
  2010. | *
  2011. | *
  2012. | *
  2013. | *
  2014. | *
  2015. | *
  2016. | *
  2017. | *
  2018. | *
  2019. | *
  2020. | *
  2021. | *
  2022. | *
  2023. | *
  2024. | *
  2025. | *
  2026. | *
  2027. |*
  2028. |*
  2029. |
  2030. |
  2031. |
  2032. |*
  2033. | *
  2034. | *
  2035. |*
  2036. |
  2037. *|
  2038. |*
  2039. | *
  2040. | *
  2041. | *
  2042. | *
  2043. | *
  2044. | *
  2045. | *
  2046. | *
  2047. | *
  2048. | *
  2049. | *
  2050. | *
  2051. | *
  2052. | *
  2053. | *
  2054. | *
  2055. | *
  2056. | *
  2057. | *
  2058. | *
  2059. | *
  2060. | *
  2061. | *
  2062. | *
  2063. | *
  2064. | *
  2065. | *
  2066. | *
  2067. | *
  2068. | *
  2069. | *
  2070. | *
  2071. | *
  2072. | *
  2073. | *
  2074. | *
  2075. | *
  2076. | *
  2077. | *
  2078. Curve 2:
  2079. ----------------------------------------------------------------------
  2080. |
  2081. |
  2082. |
  2083. |
  2084. |*
  2085. | *
  2086. | *
  2087. | *
  2088. | *
  2089. | *
  2090. | *
  2091. | *
  2092. | *
  2093. | *
  2094. | *
  2095. | *
  2096. | *
  2097. | *
  2098. | *
  2099. | *
  2100. | *
  2101. | *
  2102. | *
  2103. | *
  2104. | *
  2105. | *
  2106. | *
  2107. | *
  2108. |*
  2109. |
  2110. * |
  2111. * |
  2112. * |
  2113. * |
  2114. * |
  2115. * |
  2116. * |
  2117. * |
  2118. * |
  2119. * |
  2120. |*
  2121. | *
  2122. | *
  2123. | *
  2124. | *
  2125. | *
  2126. | *
  2127. | *
  2128. | *
  2129. | *
  2130. | *
  2131. | *
  2132. | *
  2133. | *
  2134. | *
  2135. | *
  2136. | *
  2137. | *
  2138. | *
  2139. | *
  2140. *|
  2141. * |
  2142. * |
  2143. * |
  2144. * |
  2145. * |
  2146. * |
  2147. * |
  2148. * |
  2149. * |
  2150. * |
  2151. * |
  2152. * |
  2153. * |
  2154. * |
  2155. * |
  2156. * |
  2157. |
  2158. | *
  2159. | *
  2160. | *
  2161. | *
  2162. | *
  2163. | *
  2164. | *
  2165. | *
  2166. | *
  2167. | *
  2168. | *
  2169. | *
  2170. | *
  2171. | *
  2172. | *
  2173. | *
  2174. | *
  2175. | *
  2176. | *
  2177. | *
  2178. | *
  2179. | *
  2180. |*
  2181. *|
  2182. * |
  2183. * |
  2184. * |
  2185. * |
  2186. * |
  2187. * |
  2188. * |
  2189. * |
  2190. * |
  2191. * |
  2192. * |
  2193. * |
  2194. * |
  2195. * |
  2196. * |
  2197. * |
  2198. * |
  2199. * |
  2200. * |
  2201. * |
  2202. * |
  2203. * |
  2204. * |
  2205. * |
  2206. * |
  2207. * |
  2208. * |
  2209. * |
  2210. * |
  2211. * |
  2212. * |
  2213. * |
  2214. * |
  2215. * |
  2216. * |
  2217. * |
  2218. * |
  2219. * |
  2220. * |
  2221. * |
  2222. * |
  2223. * |
  2224. * |
  2225. * |
  2226. * |
  2227. * |
  2228. * |
  2229. * |
  2230. * |
  2231. * |
  2232. * |
  2233. * |
  2234. * |
  2235. * |
  2236. * |
  2237. *|
  2238. |*
  2239. | *
  2240. | *
  2241. | *
  2242. | *
  2243. | *
  2244. | *
  2245. See also hplgit.github.io/pysketcher/doc/src/tut/fig-tut/StochasticWavyCurve.png (and .pdf)
  2246. """
  2247. # The curves were generated by the script generate_road_profiles.py and
  2248. # the code below were generated by plot_roads.py. Both scripts are
  2249. # found doc/src/src-bumpy in the repo git@github.com:hplgit/bumpy.git
  2250. def __init__(self, curve_no=0, percentage=100):
  2251. """
  2252. ============= ===================================================
  2253. Argument Explanation
  2254. ============= ===================================================
  2255. curve_no 0, 1, or 2: chooses one out of three shapes.
  2256. percentage The percentage of the defined curve to be used.
  2257. ============= ===================================================
  2258. """
  2259. self._define_curves()
  2260. self.curve_no = curve_no
  2261. m = int(len(self.x)/float(percentage)*100)
  2262. self.shapes = {'wavy': Curve(self.x[:m], self.y[curve_no][:m])}
  2263. def __call__(self, x):
  2264. raise NotImplementedError
  2265. def _define_curves(self):
  2266. self.x = array([0.0000, 0.0606, 0.1212, 0.1818, 0.2424, 0.3030, 0.3636, 0.4242, 0.4848, 0.5455, 0.6061, 0.6667, 0.7273, 0.7879, 0.8485, 0.9091, 0.9697, 1.0303, 1.0909, 1.1515, 1.2121, 1.2727, 1.3333, 1.3939, 1.4545, 1.5152, 1.5758, 1.6364, 1.6970, 1.7576, 1.8182, 1.8788, 1.9394, 2.0000, 2.0606, 2.1212, 2.1818, 2.2424, 2.3030, 2.3636, 2.4242, 2.4848, 2.5455, 2.6061, 2.6667, 2.7273, 2.7879, 2.8485, 2.9091, 2.9697, 3.0303, 3.0909, 3.1515, 3.2121, 3.2727, 3.3333, 3.3939, 3.4545, 3.5152, 3.5758, 3.6364, 3.6970, 3.7576, 3.8182, 3.8788, 3.9394, 4.0000, 4.0606, 4.1212, 4.1818, 4.2424, 4.3030, 4.3636, 4.4242, 4.4848, 4.5455, 4.6061, 4.6667, 4.7273, 4.7879, 4.8485, 4.9091, 4.9697, 5.0303, 5.0909, 5.1515, 5.2121, 5.2727, 5.3333, 5.3939, 5.4545, 5.5152, 5.5758, 5.6364, 5.6970, 5.7576, 5.8182, 5.8788, 5.9394, 6.0000, 6.0606, 6.1212, 6.1818, 6.2424, 6.3030, 6.3636, 6.4242, 6.4848, 6.5455, 6.6061, 6.6667, 6.7273, 6.7879, 6.8485, 6.9091, 6.9697, 7.0303, 7.0909, 7.1515, 7.2121, 7.2727, 7.3333, 7.3939, 7.4545, 7.5152, 7.5758, 7.6364, 7.6970, 7.7576, 7.8182, 7.8788, 7.9394, 8.0000, 8.0606, 8.1212, 8.1818, 8.2424, 8.3030, 8.3636, 8.4242, 8.4848, 8.5455, 8.6061, 8.6667, 8.7273, 8.7879, 8.8485, 8.9091, 8.9697, 9.0303, 9.0909, 9.1515, 9.2121, 9.2727, 9.3333, 9.3939, 9.4545, 9.5152, 9.5758, 9.6364, 9.6970, 9.7576, 9.8182, 9.8788, 9.9394, 10.0000, 10.0606, 10.1212, 10.1818, 10.2424, 10.3030, 10.3636, 10.4242, 10.4848, 10.5455, 10.6061, 10.6667, 10.7273, 10.7879, 10.8485, 10.9091, 10.9697, 11.0303, 11.0909, 11.1515, 11.2121, 11.2727, 11.3333, 11.3939, 11.4545, 11.5152, 11.5758, 11.6364, 11.6970, 11.7576, 11.8182, 11.8788, 11.9394, 12.0000, 12.0606, 12.1212, 12.1818, 12.2424, 12.3030, 12.3636, 12.4242, 12.4848, 12.5455, 12.6061, 12.6667, 12.7273, 12.7879, 12.8485, 12.9091, 12.9697, 13.0303, 13.0909, 13.1515, 13.2121, 13.2727, 13.3333, 13.3939, 13.4545, 13.5152, 13.5758, 13.6364, 13.6970, 13.7576, 13.8182, 13.8788, 13.9394, 14.0000, 14.0606, 14.1212, 14.1818, 14.2424, 14.3030, 14.3636, 14.4242, 14.4848, 14.5455, 14.6061, 14.6667, 14.7273, 14.7879, 14.8485, 14.9091, 14.9697, 15.0303, 15.0909, 15.1515, 15.2121, 15.2727, 15.3333, 15.3939, 15.4545, 15.5152, 15.5758, 15.6364, 15.6970, 15.7576, 15.8182, 15.8788, 15.9394, 16.0000, 16.0606, 16.1212, 16.1818, 16.2424, 16.3030, 16.3636, 16.4242, 16.4848, 16.5455, 16.6061, 16.6667, 16.7273, 16.7879, 16.8485, 16.9091, 16.9697, 17.0303, 17.0909, 17.1515, 17.2121, 17.2727, 17.3333, 17.3939, 17.4545, 17.5152, 17.5758, 17.6364, 17.6970, 17.7576, 17.8182, 17.8788, 17.9394, 18.0000, 18.0606, 18.1212, 18.1818, 18.2424, 18.3030, 18.3636, 18.4242, 18.4848, 18.5455, 18.6061, 18.6667, 18.7273, 18.7879, 18.8485, 18.9091, 18.9697, 19.0303, 19.0909, 19.1515, 19.2121, 19.2727, 19.3333, 19.3939, 19.4545, 19.5152, 19.5758, 19.6364, 19.6970, 19.7576, 19.8182, 19.8788, 19.9394, 20.0000, 20.0606, 20.1212, 20.1818, 20.2424, 20.3030, 20.3636, 20.4242, 20.4848, 20.5455, 20.6061, 20.6667, 20.7273, 20.7879, 20.8485, 20.9091, 20.9697, 21.0303, 21.0909, 21.1515, 21.2121, 21.2727, 21.3333, 21.3939, 21.4545, 21.5152, 21.5758, 21.6364, 21.6970, 21.7576, 21.8182, 21.8788, 21.9394, 22.0000, 22.0606, 22.1212, 22.1818, 22.2424, 22.3030, 22.3636, 22.4242, 22.4848, 22.5455, 22.6061, 22.6667, 22.7273, 22.7879, 22.8485, 22.9091, 22.9697, 23.0303, 23.0909, 23.1515, 23.2121, 23.2727, 23.3333, 23.3939, 23.4545, 23.5152, 23.5758, 23.6364, 23.6970, 23.7576, 23.8182, 23.8788, 23.9394, 24.0000, 24.0606, 24.1212, 24.1818, 24.2424, 24.3030, 24.3636, 24.4242, 24.4848, 24.5455, 24.6061, 24.6667, 24.7273, 24.7879, 24.8485, 24.9091, 24.9697, 25.0303, 25.0909, 25.1515, 25.2121, 25.2727, 25.3333, 25.3939, 25.4545, 25.5152, 25.5758, 25.6364, 25.6970, 25.7576, 25.8182, 25.8788, 25.9394, 26.0000, 26.0606, 26.1212, 26.1818, 26.2424, 26.3030, 26.3636, 26.4242, 26.4848, 26.5455, 26.6061, 26.6667, 26.7273, 26.7879, 26.8485, 26.9091, 26.9697, 27.0303, 27.0909, 27.1515, 27.2121, 27.2727, 27.3333, 27.3939, 27.4545, 27.5152, 27.5758, 27.6364, 27.6970, 27.7576, 27.8182, 27.8788, 27.9394, 28.0000, 28.0606, 28.1212, 28.1818, 28.2424, 28.3030, 28.3636, 28.4242, 28.4848, 28.5455, 28.6061, 28.6667, 28.7273, 28.7879, 28.8485, 28.9091, 28.9697, 29.0303, 29.0909, 29.1515, 29.2121, 29.2727, 29.3333, 29.3939, 29.4545, 29.5152, 29.5758, 29.6364, 29.6970, 29.7576, 29.8182, 29.8788, 29.9394, 30.0000, 30.0606, 30.1212, 30.1818, 30.2424, 30.3030, 30.3636, 30.4242, 30.4848, 30.5455, 30.6061, 30.6667, 30.7273, 30.7879, 30.8485, 30.9091, 30.9697, 31.0303, 31.0909, 31.1515, 31.2121, 31.2727, 31.3333, 31.3939, 31.4545, 31.5152, 31.5758, 31.6364, 31.6970, 31.7576, 31.8182, 31.8788, 31.9394, 32.0000, 32.0606, 32.1212, 32.1818, 32.2424, 32.3030, 32.3636, 32.4242, 32.4848, 32.5455, 32.6061, 32.6667, 32.7273, 32.7879, 32.8485, 32.9091, 32.9697, 33.0303, 33.0909, 33.1515, 33.2121, 33.2727, 33.3333, 33.3939, 33.4545, 33.5152, 33.5758, 33.6364, 33.6970, 33.7576, 33.8182, 33.8788, 33.9394, 34.0000, 34.0606, 34.1212, 34.1818, 34.2424, 34.3030, 34.3636, 34.4242, 34.4848, 34.5455, 34.6061, 34.6667, 34.7273, 34.7879, 34.8485, 34.9091, 34.9697, 35.0303, 35.0909, 35.1515, 35.2121, 35.2727, 35.3333, 35.3939, 35.4545, 35.5152, 35.5758, 35.6364, 35.6970, 35.7576, 35.8182, 35.8788, 35.9394, 36.0000, 36.0606, 36.1212, 36.1818, 36.2424, 36.3030, 36.3636, 36.4242, 36.4848, 36.5455, 36.6061, 36.6667, 36.7273, 36.7879, 36.8485, 36.9091, 36.9697, 37.0303, 37.0909, 37.1515, 37.2121, 37.2727, 37.3333, 37.3939, 37.4545, 37.5152, 37.5758, 37.6364, 37.6970, 37.7576, 37.8182, 37.8788, 37.9394, 38.0000, 38.0606, 38.1212, 38.1818, 38.2424, 38.3030, 38.3636, 38.4242, 38.4848, 38.5455, 38.6061, 38.6667, 38.7273, 38.7879, 38.8485, 38.9091, 38.9697, 39.0303, 39.0909, 39.1515, 39.2121, 39.2727, 39.3333, 39.3939, 39.4545, 39.5152, 39.5758, 39.6364, 39.6970, 39.7576, 39.8182, 39.8788, 39.9394, 40.0000, 40.0606, 40.1212, 40.1818, 40.2424, 40.3030, 40.3636, 40.4242, 40.4848, 40.5455, 40.6061, 40.6667, 40.7273, 40.7879, 40.8485, 40.9091, 40.9697, 41.0303, 41.0909, 41.1515, 41.2121, 41.2727, 41.3333, 41.3939, 41.4545, 41.5152, 41.5758, 41.6364, 41.6970, 41.7576, 41.8182, 41.8788, 41.9394, 42.0000, 42.0606, 42.1212, 42.1818, 42.2424, 42.3030, 42.3636, 42.4242, 42.4848, 42.5455, 42.6061, 42.6667, 42.7273, 42.7879, 42.8485, 42.9091, 42.9697, 43.0303, 43.0909, 43.1515, 43.2121, 43.2727, 43.3333, 43.3939, 43.4545, 43.5152, 43.5758, 43.6364, 43.6970, 43.7576, 43.8182, 43.8788, 43.9394, 44.0000, 44.0606, 44.1212, 44.1818, 44.2424, 44.3030, 44.3636, 44.4242, 44.4848, 44.5455, 44.6061, 44.6667, 44.7273, 44.7879, 44.8485, 44.9091, 44.9697, 45.0303, 45.0909, 45.1515, 45.2121, 45.2727, 45.3333, 45.3939, 45.4545, 45.5152, 45.5758, 45.6364, 45.6970, 45.7576, 45.8182, 45.8788, 45.9394, 46.0000, 46.0606, 46.1212, 46.1818, 46.2424, 46.3030, 46.3636, 46.4242, 46.4848, 46.5455, 46.6061, 46.6667, 46.7273, 46.7879, 46.8485, 46.9091, 46.9697, 47.0303, 47.0909, 47.1515, 47.2121, 47.2727, 47.3333, 47.3939, 47.4545, 47.5152, 47.5758, 47.6364, 47.6970, 47.7576, 47.8182, 47.8788, 47.9394, 48.0000, 48.0606, 48.1212, 48.1818, 48.2424, 48.3030, 48.3636, 48.4242, 48.4848, 48.5455, 48.6061, 48.6667, 48.7273, 48.7879, 48.8485, 48.9091, 48.9697, 49.0303, 49.0909, 49.1515, 49.2121, 49.2727, 49.3333, 49.3939, 49.4545, 49.5152, 49.5758, 49.6364, 49.6970, 49.7576, 49.8182, 49.8788, 49.9394, ])
  2267. self.y = [None]*3
  2268. self.y[0] = array([0.0000, 0.0005, 0.0006, 0.0004, -0.0004, -0.0007, -0.0022, -0.0027, -0.0036, -0.0042, -0.0050, -0.0049, -0.0060, -0.0072, -0.0085, -0.0092, -0.0104, -0.0116, -0.0133, -0.0148, -0.0160, -0.0177, -0.0186, -0.0191, -0.0192, -0.0187, -0.0187, -0.0187, -0.0192, -0.0198, -0.0201, -0.0208, -0.0216, -0.0227, -0.0242, -0.0260, -0.0277, -0.0299, -0.0319, -0.0328, -0.0333, -0.0338, -0.0347, -0.0360, -0.0363, -0.0365, -0.0370, -0.0373, -0.0364, -0.0355, -0.0343, -0.0329, -0.0317, -0.0312, -0.0309, -0.0306, -0.0301, -0.0290, -0.0275, -0.0259, -0.0238, -0.0222, -0.0200, -0.0176, -0.0154, -0.0130, -0.0108, -0.0081, -0.0046, -0.0001, 0.0035, 0.0061, 0.0083, 0.0105, 0.0130, 0.0156, 0.0170, 0.0181, 0.0196, 0.0212, 0.0231, 0.0247, 0.0262, 0.0277, 0.0293, 0.0309, 0.0325, 0.0336, 0.0348, 0.0360, 0.0378, 0.0401, 0.0423, 0.0443, 0.0457, 0.0473, 0.0488, 0.0500, 0.0511, 0.0518, 0.0528, 0.0534, 0.0547, 0.0561, 0.0577, 0.0585, 0.0594, 0.0606, 0.0611, 0.0614, 0.0617, 0.0612, 0.0607, 0.0608, 0.0603, 0.0599, 0.0588, 0.0577, 0.0557, 0.0543, 0.0532, 0.0520, 0.0505, 0.0496, 0.0499, 0.0490, 0.0489, 0.0496, 0.0504, 0.0504, 0.0509, 0.0512, 0.0512, 0.0504, 0.0499, 0.0498, 0.0493, 0.0491, 0.0483, 0.0478, 0.0474, 0.0468, 0.0462, 0.0460, 0.0462, 0.0467, 0.0472, 0.0476, 0.0483, 0.0491, 0.0502, 0.0510, 0.0504, 0.0503, 0.0514, 0.0527, 0.0538, 0.0547, 0.0554, 0.0561, 0.0561, 0.0558, 0.0548, 0.0540, 0.0531, 0.0524, 0.0516, 0.0513, 0.0511, 0.0520, 0.0519, 0.0513, 0.0512, 0.0525, 0.0535, 0.0545, 0.0552, 0.0566, 0.0577, 0.0591, 0.0602, 0.0605, 0.0609, 0.0615, 0.0627, 0.0638, 0.0644, 0.0652, 0.0661, 0.0670, 0.0678, 0.0692, 0.0706, 0.0729, 0.0757, 0.0786, 0.0805, 0.0825, 0.0846, 0.0870, 0.0897, 0.0921, 0.0947, 0.0968, 0.0997, 0.1018, 0.1027, 0.1025, 0.1018, 0.1004, 0.1000, 0.0994, 0.0980, 0.0972, 0.0960, 0.0941, 0.0927, 0.0916, 0.0902, 0.0896, 0.0890, 0.0892, 0.0896, 0.0908, 0.0919, 0.0922, 0.0937, 0.0948, 0.0957, 0.0960, 0.0961, 0.0963, 0.0965, 0.0970, 0.0983, 0.0994, 0.0997, 0.0993, 0.0984, 0.0965, 0.0951, 0.0934, 0.0916, 0.0897, 0.0870, 0.0840, 0.0813, 0.0791, 0.0766, 0.0751, 0.0730, 0.0707, 0.0683, 0.0644, 0.0616, 0.0592, 0.0562, 0.0545, 0.0531, 0.0519, 0.0504, 0.0490, 0.0468, 0.0451, 0.0432, 0.0414, 0.0403, 0.0394, 0.0386, 0.0380, 0.0370, 0.0364, 0.0367, 0.0374, 0.0385, 0.0390, 0.0390, 0.0381, 0.0380, 0.0377, 0.0381, 0.0380, 0.0377, 0.0374, 0.0376, 0.0378, 0.0380, 0.0382, 0.0385, 0.0381, 0.0377, 0.0373, 0.0367, 0.0365, 0.0358, 0.0351, 0.0342, 0.0336, 0.0334, 0.0326, 0.0322, 0.0329, 0.0327, 0.0321, 0.0310, 0.0297, 0.0293, 0.0290, 0.0283, 0.0279, 0.0272, 0.0271, 0.0271, 0.0279, 0.0282, 0.0302, 0.0325, 0.0351, 0.0375, 0.0393, 0.0406, 0.0416, 0.0422, 0.0428, 0.0430, 0.0434, 0.0443, 0.0447, 0.0457, 0.0465, 0.0479, 0.0494, 0.0514, 0.0527, 0.0539, 0.0557, 0.0571, 0.0572, 0.0563, 0.0539, 0.0504, 0.0469, 0.0441, 0.0412, 0.0385, 0.0359, 0.0334, 0.0308, 0.0282, 0.0260, 0.0232, 0.0211, 0.0196, 0.0180, 0.0169, 0.0154, 0.0137, 0.0121, 0.0105, 0.0088, 0.0067, 0.0044, 0.0027, -0.0000, -0.0024, -0.0048, -0.0066, -0.0082, -0.0111, -0.0136, -0.0158, -0.0179, -0.0201, -0.0218, -0.0235, -0.0242, -0.0245, -0.0236, -0.0231, -0.0237, -0.0237, -0.0233, -0.0229, -0.0233, -0.0239, -0.0241, -0.0244, -0.0247, -0.0251, -0.0259, -0.0270, -0.0288, -0.0295, -0.0305, -0.0311, -0.0322, -0.0327, -0.0343, -0.0352, -0.0361, -0.0358, -0.0362, -0.0365, -0.0365, -0.0358, -0.0353, -0.0348, -0.0355, -0.0365, -0.0373, -0.0373, -0.0375, -0.0365, -0.0345, -0.0327, -0.0322, -0.0327, -0.0335, -0.0337, -0.0337, -0.0348, -0.0359, -0.0361, -0.0364, -0.0371, -0.0366, -0.0361, -0.0358, -0.0353, -0.0348, -0.0345, -0.0335, -0.0320, -0.0300, -0.0281, -0.0257, -0.0233, -0.0208, -0.0179, -0.0146, -0.0104, -0.0062, -0.0031, -0.0007, 0.0023, 0.0049, 0.0077, 0.0099, 0.0125, 0.0147, 0.0177, 0.0202, 0.0232, 0.0264, 0.0291, 0.0322, 0.0346, 0.0365, 0.0386, 0.0403, 0.0415, 0.0425, 0.0428, 0.0436, 0.0447, 0.0457, 0.0465, 0.0475, 0.0494, 0.0516, 0.0534, 0.0555, 0.0574, 0.0591, 0.0616, 0.0638, 0.0655, 0.0660, 0.0657, 0.0656, 0.0650, 0.0647, 0.0637, 0.0623, 0.0613, 0.0611, 0.0611, 0.0618, 0.0633, 0.0652, 0.0665, 0.0677, 0.0690, 0.0700, 0.0712, 0.0713, 0.0710, 0.0709, 0.0695, 0.0675, 0.0655, 0.0626, 0.0598, 0.0566, 0.0533, 0.0501, 0.0470, 0.0437, 0.0405, 0.0372, 0.0342, 0.0323, 0.0305, 0.0289, 0.0267, 0.0250, 0.0229, 0.0204, 0.0183, 0.0164, 0.0152, 0.0148, 0.0141, 0.0135, 0.0139, 0.0148, 0.0160, 0.0179, 0.0193, 0.0210, 0.0234, 0.0266, 0.0291, 0.0314, 0.0337, 0.0358, 0.0375, 0.0395, 0.0412, 0.0417, 0.0424, 0.0428, 0.0428, 0.0418, 0.0415, 0.0398, 0.0373, 0.0347, 0.0328, 0.0318, 0.0301, 0.0284, 0.0258, 0.0240, 0.0214, 0.0188, 0.0168, 0.0148, 0.0137, 0.0122, 0.0109, 0.0101, 0.0093, 0.0093, 0.0096, 0.0089, 0.0104, 0.0123, 0.0141, 0.0150, 0.0159, 0.0165, 0.0174, 0.0185, 0.0205, 0.0225, 0.0247, 0.0269, 0.0290, 0.0308, 0.0331, 0.0357, 0.0379, 0.0392, 0.0405, 0.0423, 0.0440, 0.0465, 0.0484, 0.0503, 0.0506, 0.0506, 0.0504, 0.0504, 0.0513, 0.0521, 0.0537, 0.0555, 0.0577, 0.0595, 0.0619, 0.0636, 0.0654, 0.0666, 0.0674, 0.0672, 0.0667, 0.0664, 0.0660, 0.0648, 0.0645, 0.0642, 0.0645, 0.0651, 0.0653, 0.0643, 0.0629, 0.0621, 0.0607, 0.0597, 0.0585, 0.0574, 0.0553, 0.0539, 0.0525, 0.0516, 0.0508, 0.0505, 0.0500, 0.0500, 0.0490, 0.0475, 0.0464, 0.0452, 0.0440, 0.0427, 0.0416, 0.0403, 0.0393, 0.0377, 0.0360, 0.0349, 0.0343, 0.0332, 0.0325, 0.0313, 0.0305, 0.0287, 0.0263, 0.0237, 0.0211, 0.0194, 0.0182, 0.0180, 0.0184, 0.0186, 0.0186, 0.0192, 0.0198, 0.0203, 0.0200, 0.0196, 0.0177, 0.0156, 0.0130, 0.0110, 0.0087, 0.0064, 0.0049, 0.0029, 0.0011, -0.0016, -0.0042, -0.0064, -0.0080, -0.0100, -0.0118, -0.0133, -0.0147, -0.0160, -0.0170, -0.0176, -0.0188, -0.0206, -0.0209, -0.0201, -0.0200, -0.0198, -0.0191, -0.0181, -0.0170, -0.0165, -0.0161, -0.0159, -0.0157, -0.0156, -0.0156, -0.0150, -0.0137, -0.0131, -0.0128, -0.0129, -0.0124, -0.0119, -0.0106, -0.0096, -0.0086, -0.0084, -0.0075, -0.0069, -0.0070, -0.0075, -0.0092, -0.0109, -0.0124, -0.0137, -0.0140, -0.0146, -0.0146, -0.0144, -0.0144, -0.0155, -0.0160, -0.0167, -0.0174, -0.0187, -0.0198, -0.0208, -0.0217, -0.0226, -0.0229, -0.0226, -0.0219, -0.0194, -0.0173, -0.0155, -0.0143, -0.0129, -0.0120, -0.0126, -0.0137, -0.0148, -0.0153, -0.0166, -0.0182, -0.0197, -0.0216, -0.0230, -0.0244, -0.0255, -0.0270, -0.0275, -0.0280, -0.0277, -0.0276, -0.0272, -0.0272, -0.0276, -0.0284, -0.0292, -0.0300, -0.0299, -0.0305, -0.0315, -0.0316, -0.0313, -0.0320, -0.0324, -0.0326, -0.0339, -0.0354, -0.0368, -0.0376, -0.0379, -0.0381, -0.0377, -0.0373, -0.0374, -0.0380, -0.0382, -0.0394, -0.0402, -0.0415, -0.0429, -0.0439, -0.0442, -0.0455, -0.0472, -0.0487, -0.0504, -0.0515, -0.0537, -0.0555, -0.0570, -0.0583, -0.0589, -0.0601, -0.0606, -0.0614, -0.0617, -0.0621, -0.0620, -0.0614, -0.0616, -0.0618, -0.0620, -0.0622, -0.0628, -0.0623, -0.0617, -0.0602, -0.0590, -0.0584, -0.0577, -0.0572, -0.0555, -0.0536, -0.0511, -0.0488, -0.0468, -0.0448, -0.0427, -0.0401, -0.0378, -0.0358, ])
  2269. self.y[1] = array([0.0000, 0.0002, 0.0002, 0.0001, 0.0001, 0.0003, 0.0008, 0.0009, 0.0009, 0.0015, 0.0019, 0.0027, 0.0033, 0.0037, 0.0041, 0.0052, 0.0055, 0.0050, 0.0048, 0.0054, 0.0054, 0.0059, 0.0061, 0.0060, 0.0054, 0.0050, 0.0047, 0.0042, 0.0033, 0.0031, 0.0027, 0.0021, 0.0015, 0.0008, -0.0002, -0.0011, -0.0015, -0.0015, -0.0021, -0.0025, -0.0027, -0.0013, 0.0005, 0.0030, 0.0049, 0.0074, 0.0099, 0.0119, 0.0142, 0.0166, 0.0186, 0.0205, 0.0229, 0.0247, 0.0266, 0.0286, 0.0307, 0.0327, 0.0346, 0.0368, 0.0379, 0.0393, 0.0409, 0.0434, 0.0457, 0.0478, 0.0499, 0.0520, 0.0534, 0.0544, 0.0561, 0.0576, 0.0587, 0.0593, 0.0597, 0.0599, 0.0594, 0.0588, 0.0588, 0.0595, 0.0609, 0.0628, 0.0651, 0.0673, 0.0693, 0.0721, 0.0760, 0.0798, 0.0837, 0.0881, 0.0931, 0.0977, 0.1023, 0.1076, 0.1127, 0.1175, 0.1223, 0.1264, 0.1302, 0.1333, 0.1373, 0.1408, 0.1441, 0.1472, 0.1497, 0.1516, 0.1523, 0.1527, 0.1532, 0.1537, 0.1542, 0.1549, 0.1551, 0.1543, 0.1536, 0.1532, 0.1524, 0.1512, 0.1497, 0.1484, 0.1473, 0.1460, 0.1445, 0.1429, 0.1411, 0.1398, 0.1395, 0.1395, 0.1388, 0.1386, 0.1380, 0.1369, 0.1355, 0.1341, 0.1319, 0.1293, 0.1266, 0.1240, 0.1215, 0.1189, 0.1160, 0.1135, 0.1111, 0.1079, 0.1049, 0.1016, 0.0995, 0.0972, 0.0948, 0.0927, 0.0914, 0.0904, 0.0892, 0.0878, 0.0862, 0.0848, 0.0829, 0.0809, 0.0792, 0.0776, 0.0760, 0.0754, 0.0740, 0.0724, 0.0700, 0.0680, 0.0668, 0.0658, 0.0655, 0.0653, 0.0648, 0.0636, 0.0613, 0.0589, 0.0573, 0.0559, 0.0552, 0.0558, 0.0571, 0.0587, 0.0602, 0.0608, 0.0613, 0.0607, 0.0601, 0.0595, 0.0595, 0.0590, 0.0591, 0.0598, 0.0603, 0.0603, 0.0610, 0.0610, 0.0607, 0.0603, 0.0601, 0.0607, 0.0608, 0.0616, 0.0627, 0.0640, 0.0653, 0.0662, 0.0669, 0.0673, 0.0667, 0.0654, 0.0646, 0.0635, 0.0624, 0.0604, 0.0583, 0.0562, 0.0547, 0.0537, 0.0524, 0.0522, 0.0516, 0.0515, 0.0518, 0.0515, 0.0504, 0.0501, 0.0501, 0.0501, 0.0498, 0.0494, 0.0489, 0.0485, 0.0483, 0.0484, 0.0477, 0.0472, 0.0468, 0.0471, 0.0463, 0.0461, 0.0460, 0.0466, 0.0461, 0.0454, 0.0448, 0.0445, 0.0439, 0.0437, 0.0439, 0.0438, 0.0430, 0.0423, 0.0418, 0.0415, 0.0409, 0.0400, 0.0386, 0.0376, 0.0370, 0.0357, 0.0351, 0.0354, 0.0356, 0.0356, 0.0362, 0.0366, 0.0380, 0.0385, 0.0378, 0.0381, 0.0390, 0.0390, 0.0388, 0.0386, 0.0379, 0.0366, 0.0362, 0.0364, 0.0365, 0.0363, 0.0363, 0.0359, 0.0354, 0.0348, 0.0354, 0.0350, 0.0337, 0.0314, 0.0282, 0.0258, 0.0240, 0.0230, 0.0227, 0.0227, 0.0221, 0.0214, 0.0201, 0.0188, 0.0179, 0.0173, 0.0165, 0.0155, 0.0133, 0.0109, 0.0082, 0.0052, 0.0028, 0.0003, -0.0026, -0.0062, -0.0095, -0.0120, -0.0151, -0.0180, -0.0209, -0.0233, -0.0262, -0.0293, -0.0316, -0.0337, -0.0361, -0.0385, -0.0419, -0.0445, -0.0470, -0.0494, -0.0515, -0.0534, -0.0561, -0.0590, -0.0619, -0.0643, -0.0666, -0.0688, -0.0710, -0.0733, -0.0753, -0.0774, -0.0795, -0.0822, -0.0837, -0.0851, -0.0865, -0.0876, -0.0892, -0.0892, -0.0893, -0.0896, -0.0901, -0.0905, -0.0909, -0.0909, -0.0901, -0.0890, -0.0884, -0.0880, -0.0873, -0.0871, -0.0872, -0.0860, -0.0858, -0.0863, -0.0865, -0.0864, -0.0865, -0.0871, -0.0887, -0.0904, -0.0924, -0.0941, -0.0962, -0.0980, -0.0999, -0.1014, -0.1021, -0.1029, -0.1037, -0.1041, -0.1052, -0.1056, -0.1065, -0.1070, -0.1075, -0.1073, -0.1079, -0.1080, -0.1078, -0.1075, -0.1074, -0.1078, -0.1082, -0.1087, -0.1083, -0.1079, -0.1087, -0.1096, -0.1101, -0.1105, -0.1114, -0.1121, -0.1127, -0.1135, -0.1137, -0.1133, -0.1131, -0.1126, -0.1115, -0.1109, -0.1099, -0.1097, -0.1092, -0.1081, -0.1066, -0.1057, -0.1050, -0.1047, -0.1040, -0.1030, -0.1018, -0.1004, -0.0988, -0.0972, -0.0955, -0.0946, -0.0938, -0.0925, -0.0905, -0.0886, -0.0864, -0.0838, -0.0805, -0.0776, -0.0748, -0.0720, -0.0686, -0.0655, -0.0621, -0.0588, -0.0555, -0.0520, -0.0477, -0.0434, -0.0389, -0.0353, -0.0323, -0.0287, -0.0247, -0.0211, -0.0179, -0.0149, -0.0123, -0.0094, -0.0065, -0.0042, -0.0020, -0.0001, 0.0016, 0.0028, 0.0044, 0.0072, 0.0097, 0.0109, 0.0120, 0.0127, 0.0135, 0.0140, 0.0151, 0.0147, 0.0158, 0.0161, 0.0162, 0.0154, 0.0154, 0.0162, 0.0172, 0.0186, 0.0204, 0.0214, 0.0213, 0.0210, 0.0211, 0.0208, 0.0204, 0.0202, 0.0201, 0.0187, 0.0179, 0.0167, 0.0164, 0.0167, 0.0170, 0.0180, 0.0188, 0.0199, 0.0204, 0.0213, 0.0209, 0.0203, 0.0198, 0.0186, 0.0183, 0.0181, 0.0168, 0.0163, 0.0161, 0.0160, 0.0154, 0.0157, 0.0162, 0.0166, 0.0171, 0.0171, 0.0179, 0.0186, 0.0196, 0.0205, 0.0229, 0.0254, 0.0280, 0.0305, 0.0327, 0.0343, 0.0354, 0.0361, 0.0367, 0.0363, 0.0361, 0.0355, 0.0353, 0.0351, 0.0342, 0.0335, 0.0328, 0.0326, 0.0324, 0.0325, 0.0330, 0.0333, 0.0333, 0.0327, 0.0326, 0.0328, 0.0326, 0.0327, 0.0329, 0.0328, 0.0332, 0.0331, 0.0325, 0.0315, 0.0311, 0.0305, 0.0302, 0.0300, 0.0289, 0.0275, 0.0260, 0.0246, 0.0234, 0.0230, 0.0216, 0.0211, 0.0205, 0.0191, 0.0172, 0.0147, 0.0131, 0.0112, 0.0094, 0.0082, 0.0062, 0.0045, 0.0041, 0.0035, 0.0037, 0.0035, 0.0033, 0.0033, 0.0037, 0.0034, 0.0036, 0.0033, 0.0028, 0.0021, 0.0015, 0.0008, 0.0004, 0.0002, -0.0002, -0.0008, -0.0001, 0.0007, 0.0015, 0.0020, 0.0020, 0.0028, 0.0037, 0.0045, 0.0052, 0.0060, 0.0063, 0.0074, 0.0083, 0.0092, 0.0095, 0.0095, 0.0096, 0.0093, 0.0100, 0.0097, 0.0093, 0.0084, 0.0071, 0.0050, 0.0044, 0.0031, 0.0017, 0.0005, -0.0004, -0.0018, -0.0026, -0.0026, -0.0020, -0.0018, -0.0010, 0.0003, 0.0024, 0.0046, 0.0066, 0.0090, 0.0110, 0.0126, 0.0143, 0.0157, 0.0167, 0.0171, 0.0179, 0.0195, 0.0223, 0.0254, 0.0287, 0.0320, 0.0359, 0.0400, 0.0439, 0.0470, 0.0491, 0.0515, 0.0535, 0.0563, 0.0585, 0.0605, 0.0620, 0.0635, 0.0654, 0.0670, 0.0687, 0.0701, 0.0728, 0.0746, 0.0766, 0.0785, 0.0800, 0.0812, 0.0817, 0.0822, 0.0820, 0.0814, 0.0814, 0.0817, 0.0820, 0.0823, 0.0826, 0.0826, 0.0831, 0.0833, 0.0846, 0.0851, 0.0853, 0.0855, 0.0858, 0.0859, 0.0864, 0.0868, 0.0876, 0.0884, 0.0890, 0.0894, 0.0895, 0.0888, 0.0882, 0.0883, 0.0884, 0.0892, 0.0903, 0.0915, 0.0917, 0.0916, 0.0908, 0.0900, 0.0891, 0.0881, 0.0869, 0.0849, 0.0825, 0.0805, 0.0789, 0.0773, 0.0761, 0.0750, 0.0738, 0.0729, 0.0717, 0.0712, 0.0714, 0.0718, 0.0727, 0.0737, 0.0735, 0.0733, 0.0723, 0.0706, 0.0683, 0.0666, 0.0651, 0.0636, 0.0620, 0.0599, 0.0580, 0.0563, 0.0551, 0.0540, 0.0526, 0.0516, 0.0503, 0.0492, 0.0482, 0.0469, 0.0468, 0.0471, 0.0471, 0.0471, 0.0483, 0.0490, 0.0486, 0.0489, 0.0495, 0.0510, 0.0523, 0.0538, 0.0547, 0.0552, 0.0556, 0.0569, 0.0573, 0.0578, 0.0583, 0.0579, 0.0579, 0.0580, 0.0583, 0.0585, 0.0594, 0.0601, 0.0598, 0.0594, 0.0597, 0.0602, 0.0611, 0.0612, 0.0618, 0.0620, 0.0626, 0.0635, 0.0637, 0.0645, 0.0649, 0.0657, 0.0667, 0.0680, 0.0688, 0.0693, 0.0698, 0.0702, 0.0699, 0.0703, 0.0702, 0.0705, 0.0716, 0.0725, 0.0730, 0.0736, 0.0736, 0.0734, 0.0735, 0.0739, 0.0736, 0.0728, 0.0720, 0.0717, 0.0715, 0.0709, 0.0704, 0.0707, 0.0704, 0.0695, 0.0690, 0.0687, 0.0667, 0.0648, 0.0629, 0.0620, 0.0620, 0.0618, 0.0621, 0.0623, 0.0633, 0.0637, 0.0637, 0.0638, 0.0639, 0.0639, 0.0642, 0.0650, 0.0653, 0.0657, 0.0661, ])
  2270. self.y[2] = array([0.0000, 0.0001, -0.0002, -0.0003, 0.0004, 0.0014, 0.0021, 0.0025, 0.0025, 0.0021, 0.0018, 0.0022, 0.0016, 0.0018, 0.0018, 0.0021, 0.0027, 0.0034, 0.0046, 0.0060, 0.0076, 0.0080, 0.0084, 0.0090, 0.0100, 0.0104, 0.0098, 0.0097, 0.0100, 0.0100, 0.0105, 0.0117, 0.0124, 0.0128, 0.0133, 0.0133, 0.0133, 0.0132, 0.0132, 0.0136, 0.0144, 0.0161, 0.0179, 0.0196, 0.0222, 0.0251, 0.0265, 0.0279, 0.0287, 0.0291, 0.0297, 0.0305, 0.0316, 0.0328, 0.0340, 0.0361, 0.0382, 0.0408, 0.0425, 0.0442, 0.0460, 0.0474, 0.0489, 0.0502, 0.0512, 0.0517, 0.0526, 0.0525, 0.0525, 0.0521, 0.0508, 0.0498, 0.0487, 0.0478, 0.0472, 0.0461, 0.0446, 0.0434, 0.0415, 0.0399, 0.0388, 0.0374, 0.0360, 0.0351, 0.0339, 0.0320, 0.0308, 0.0294, 0.0286, 0.0278, 0.0255, 0.0224, 0.0194, 0.0170, 0.0147, 0.0131, 0.0123, 0.0121, 0.0110, 0.0106, 0.0100, 0.0090, 0.0089, 0.0093, 0.0100, 0.0111, 0.0132, 0.0159, 0.0179, 0.0195, 0.0207, 0.0220, 0.0229, 0.0242, 0.0261, 0.0279, 0.0290, 0.0300, 0.0303, 0.0309, 0.0316, 0.0328, 0.0335, 0.0332, 0.0323, 0.0314, 0.0300, 0.0283, 0.0268, 0.0248, 0.0225, 0.0201, 0.0172, 0.0143, 0.0119, 0.0104, 0.0088, 0.0071, 0.0059, 0.0051, 0.0038, 0.0027, 0.0017, 0.0009, 0.0007, -0.0002, -0.0008, -0.0020, -0.0034, -0.0055, -0.0069, -0.0082, -0.0086, -0.0085, -0.0089, -0.0088, -0.0092, -0.0099, -0.0110, -0.0121, -0.0137, -0.0150, -0.0157, -0.0160, -0.0155, -0.0149, -0.0140, -0.0139, -0.0134, -0.0135, -0.0136, -0.0138, -0.0145, -0.0158, -0.0155, -0.0155, -0.0153, -0.0154, -0.0159, -0.0157, -0.0152, -0.0143, -0.0140, -0.0138, -0.0142, -0.0148, -0.0154, -0.0154, -0.0147, -0.0136, -0.0122, -0.0107, -0.0102, -0.0091, -0.0082, -0.0063, -0.0042, -0.0028, -0.0007, 0.0015, 0.0038, 0.0054, 0.0075, 0.0093, 0.0110, 0.0137, 0.0156, 0.0170, 0.0184, 0.0195, 0.0199, 0.0206, 0.0209, 0.0207, 0.0201, 0.0198, 0.0196, 0.0191, 0.0192, 0.0193, 0.0196, 0.0201, 0.0206, 0.0216, 0.0229, 0.0253, 0.0278, 0.0304, 0.0321, 0.0341, 0.0356, 0.0367, 0.0372, 0.0379, 0.0383, 0.0394, 0.0396, 0.0397, 0.0391, 0.0382, 0.0362, 0.0346, 0.0334, 0.0325, 0.0325, 0.0325, 0.0323, 0.0316, 0.0321, 0.0328, 0.0338, 0.0352, 0.0366, 0.0378, 0.0389, 0.0401, 0.0403, 0.0406, 0.0419, 0.0425, 0.0424, 0.0421, 0.0415, 0.0407, 0.0406, 0.0411, 0.0413, 0.0419, 0.0420, 0.0416, 0.0415, 0.0402, 0.0388, 0.0377, 0.0368, 0.0357, 0.0353, 0.0351, 0.0348, 0.0347, 0.0340, 0.0326, 0.0314, 0.0306, 0.0295, 0.0288, 0.0275, 0.0257, 0.0234, 0.0209, 0.0188, 0.0169, 0.0151, 0.0132, 0.0114, 0.0093, 0.0076, 0.0055, 0.0026, -0.0008, -0.0038, -0.0069, -0.0097, -0.0118, -0.0132, -0.0148, -0.0164, -0.0183, -0.0202, -0.0213, -0.0228, -0.0253, -0.0280, -0.0299, -0.0315, -0.0329, -0.0343, -0.0349, -0.0364, -0.0373, -0.0383, -0.0391, -0.0400, -0.0401, -0.0404, -0.0414, -0.0422, -0.0425, -0.0425, -0.0420, -0.0419, -0.0412, -0.0412, -0.0408, -0.0400, -0.0396, -0.0397, -0.0400, -0.0401, -0.0405, -0.0412, -0.0410, -0.0401, -0.0389, -0.0377, -0.0372, -0.0369, -0.0364, -0.0356, -0.0347, -0.0345, -0.0346, -0.0353, -0.0359, -0.0367, -0.0376, -0.0385, -0.0387, -0.0391, -0.0401, -0.0400, -0.0398, -0.0395, -0.0391, -0.0385, -0.0383, -0.0377, -0.0374, -0.0371, -0.0361, -0.0357, -0.0355, -0.0342, -0.0324, -0.0303, -0.0280, -0.0257, -0.0232, -0.0205, -0.0179, -0.0147, -0.0113, -0.0091, -0.0059, -0.0019, 0.0024, 0.0071, 0.0120, 0.0177, 0.0219, 0.0248, 0.0273, 0.0290, 0.0302, 0.0318, 0.0325, 0.0331, 0.0334, 0.0344, 0.0360, 0.0387, 0.0414, 0.0437, 0.0456, 0.0471, 0.0491, 0.0511, 0.0526, 0.0536, 0.0550, 0.0570, 0.0597, 0.0622, 0.0647, 0.0672, 0.0696, 0.0710, 0.0725, 0.0748, 0.0777, 0.0806, 0.0832, 0.0854, 0.0874, 0.0893, 0.0906, 0.0914, 0.0927, 0.0937, 0.0944, 0.0959, 0.0970, 0.0970, 0.0967, 0.0955, 0.0945, 0.0934, 0.0927, 0.0921, 0.0916, 0.0909, 0.0901, 0.0893, 0.0884, 0.0881, 0.0877, 0.0870, 0.0870, 0.0871, 0.0872, 0.0867, 0.0860, 0.0845, 0.0821, 0.0800, 0.0775, 0.0752, 0.0728, 0.0703, 0.0676, 0.0655, 0.0637, 0.0618, 0.0593, 0.0558, 0.0518, 0.0483, 0.0445, 0.0414, 0.0384, 0.0365, 0.0349, 0.0338, 0.0330, 0.0323, 0.0320, 0.0319, 0.0320, 0.0323, 0.0324, 0.0324, 0.0322, 0.0327, 0.0328, 0.0320, 0.0314, 0.0295, 0.0279, 0.0258, 0.0243, 0.0229, 0.0211, 0.0195, 0.0176, 0.0163, 0.0151, 0.0136, 0.0123, 0.0108, 0.0088, 0.0064, 0.0041, 0.0021, 0.0002, -0.0020, -0.0051, -0.0078, -0.0106, -0.0137, -0.0169, -0.0195, -0.0218, -0.0235, -0.0253, -0.0269, -0.0281, -0.0290, -0.0299, -0.0315, -0.0328, -0.0336, -0.0344, -0.0352, -0.0354, -0.0353, -0.0358, -0.0355, -0.0347, -0.0344, -0.0338, -0.0335, -0.0324, -0.0319, -0.0319, -0.0322, -0.0334, -0.0351, -0.0365, -0.0381, -0.0401, -0.0424, -0.0449, -0.0472, -0.0491, -0.0506, -0.0522, -0.0533, -0.0544, -0.0553, -0.0553, -0.0557, -0.0558, -0.0563, -0.0573, -0.0587, -0.0602, -0.0617, -0.0636, -0.0666, -0.0699, -0.0734, -0.0760, -0.0791, -0.0817, -0.0840, -0.0857, -0.0869, -0.0881, -0.0882, -0.0884, -0.0888, -0.0893, -0.0889, -0.0876, -0.0869, -0.0858, -0.0851, -0.0836, -0.0825, -0.0813, -0.0807, -0.0800, -0.0802, -0.0805, -0.0807, -0.0813, -0.0818, -0.0819, -0.0822, -0.0829, -0.0825, -0.0829, -0.0838, -0.0840, -0.0836, -0.0824, -0.0810, -0.0799, -0.0789, -0.0785, -0.0785, -0.0774, -0.0770, -0.0761, -0.0755, -0.0752, -0.0746, -0.0744, -0.0741, -0.0729, -0.0718, -0.0710, -0.0694, -0.0673, -0.0649, -0.0627, -0.0606, -0.0592, -0.0582, -0.0571, -0.0555, -0.0539, -0.0527, -0.0516, -0.0508, -0.0503, -0.0508, -0.0518, -0.0527, -0.0536, -0.0544, -0.0545, -0.0541, -0.0547, -0.0553, -0.0561, -0.0573, -0.0587, -0.0600, -0.0608, -0.0616, -0.0624, -0.0626, -0.0631, -0.0638, -0.0630, -0.0621, -0.0612, -0.0603, -0.0594, -0.0577, -0.0564, -0.0551, -0.0535, -0.0511, -0.0485, -0.0455, -0.0424, -0.0396, -0.0374, -0.0360, -0.0351, -0.0343, -0.0336, -0.0324, -0.0311, -0.0296, -0.0276, -0.0259, -0.0240, -0.0223, -0.0212, -0.0205, -0.0201, -0.0203, -0.0214, -0.0232, -0.0261, -0.0285, -0.0304, -0.0320, -0.0342, -0.0365, -0.0381, -0.0394, -0.0416, -0.0434, -0.0443, -0.0450, -0.0465, -0.0482, -0.0490, -0.0497, -0.0508, -0.0506, -0.0509, -0.0514, -0.0522, -0.0525, -0.0532, -0.0547, -0.0553, -0.0575, -0.0597, -0.0629, -0.0666, -0.0702, -0.0737, -0.0773, -0.0805, -0.0835, -0.0847, -0.0875, -0.0893, -0.0913, -0.0929, -0.0935, -0.0943, -0.0949, -0.0957, -0.0962, -0.0977, -0.0994, -0.1011, -0.1031, -0.1057, -0.1086, -0.1114, -0.1141, -0.1158, -0.1174, -0.1197, -0.1219, -0.1234, -0.1250, -0.1267, -0.1273, -0.1270, -0.1268, -0.1263, -0.1257, -0.1240, -0.1220, -0.1210, -0.1197, -0.1197, -0.1188, -0.1171, -0.1147, -0.1122, -0.1101, -0.1077, -0.1052, -0.1031, -0.1020, -0.1008, -0.0983, -0.0960, -0.0934, -0.0908, -0.0889, -0.0865, -0.0836, -0.0799, -0.0767, -0.0740, -0.0716, -0.0692, -0.0663, -0.0628, -0.0596, -0.0567, -0.0535, -0.0508, -0.0472, -0.0435, -0.0394, -0.0347, -0.0301, -0.0253, -0.0210, -0.0165, -0.0126, -0.0095, -0.0072, -0.0046, -0.0020, -0.0001, 0.0023, 0.0050, 0.0077, 0.0102, 0.0135, 0.0175, 0.0212, 0.0245, 0.0280, 0.0309, 0.0336, 0.0365, 0.0397, 0.0433, 0.0474, 0.0511, 0.0546, 0.0574, 0.0602, 0.0634, 0.0663, 0.0695, 0.0729, 0.0752, 0.0762, 0.0773, 0.0781, 0.0790, 0.0806, 0.0836, 0.0857, 0.0879, 0.0896, 0.0920, 0.0949, 0.0975, 0.1002, ])
  2271. # COMPOSITE types:
  2272. # MassSpringForce: Line(horizontal), Spring, Rectangle, Arrow/Line(w/arrow)
  2273. # must be easy to find the tip of the arrow
  2274. # Maybe extra dict: self.name['mass'] = Rectangle object - YES!
  2275. class ArbitraryVolume(Shape):
  2276. """
  2277. An arbitrary closed volume with an optional normal vector and a
  2278. vector field to be used in derivation of continuum mechanical
  2279. equations.
  2280. """
  2281. def __init__(self, position, width=1,
  2282. volume_symbol='V',
  2283. volume_symbol_fontsize='18',
  2284. normal_vector_symbol='n',
  2285. vector_field_symbol=None):
  2286. """
  2287. ============================ ====================================
  2288. Name Description
  2289. ============================ ====================================
  2290. position center point of volume
  2291. width width of volume (about 3 is best)
  2292. normal_vector_symbol symbol of None (no boundary normal)
  2293. volume_symbol None (no center symbol) or character
  2294. volume_symbol_fontsize fontsize of volume symbol
  2295. vector_field_symbol None (no vector) or symbol
  2296. ============================ ====================================
  2297. """
  2298. self.position, self.width = position, width
  2299. self.vector_symbol = vector_field_symbol
  2300. self.normal_symbol = normal_vector_symbol
  2301. ellipse, normal, vector = self._perturbed_unit_ellipse()
  2302. self.shapes = {'closed_curve': ellipse}
  2303. if normal_vector_symbol:
  2304. self.shapes['normal'] = normal
  2305. if vector_field_symbol is not None:
  2306. self.shapes['vector'] = vector
  2307. # Scale and translate
  2308. self.rotate(20, (0,0))
  2309. self.scale(width/2.0)
  2310. self.translate(position)
  2311. # Must be placed at position after translation:
  2312. if volume_symbol:
  2313. self.shapes['name'] = Text('$%s$' % volume_symbol, position,
  2314. fontsize=volume_symbol_fontsize)
  2315. def _perturbed_unit_ellipse(self):
  2316. """Draw the volume as a perturbed ellipse of about unit size."""
  2317. a0 = 1.0
  2318. b0 = 0.75
  2319. eps_a = 0.2
  2320. eps_b = 0.1
  2321. a = lambda t: a0 + eps_a*sin(1*t)
  2322. b = lambda t: b0 + eps_b*cos(1*t)
  2323. x = lambda t: a(t)*cos(t)
  2324. y = lambda t: b(t)*sin(t)
  2325. t = linspace(0, 2*pi, 101) # parameter
  2326. ellipse = Curve(x(t), y(t))
  2327. # Make normal vector
  2328. tx = lambda t: eps_a*cos(t)*cos(t) - a(t)*sin(t)
  2329. ty = lambda t: -eps_b*sin(t)*sin(t) + b(t)*cos(t)
  2330. t0 = pi/5
  2331. nx = ty(t0)
  2332. ny = -tx(t0)
  2333. nx = nx/sqrt(nx**2 + ny**2)
  2334. ny = ny/sqrt(nx**2 + ny**2)
  2335. Px = x(t0)
  2336. Py = y(t0)
  2337. start = point(x(t0), y(t0))
  2338. end = start + point(0.75*b0*nx, 0.75*b0*ny)
  2339. normal = Force(start, end, '$\\boldsymbol{%s}$' % self.normal_symbol,
  2340. text_spacing=1./60,
  2341. text_pos='end',
  2342. text_alignment='center')
  2343. end = start + point(0.75*b0/3*nx, 0.75*b0*4*ny)
  2344. vector = Force(start, end, '$\\boldsymbol{%s}$' % self.vector_symbol,
  2345. text_spacing=1./60,
  2346. text_pos='end',
  2347. text_alignment='center')
  2348. return ellipse, normal, vector
  2349. def geometric_features(self):
  2350. """
  2351. Recorded geometric features:
  2352. ==================== =============================================
  2353. Attribute Description
  2354. ==================== =============================================
  2355. position center point of volume
  2356. normal_vector_start start of normal vector
  2357. normal_vector_end end of normal vector
  2358. ==================== =============================================
  2359. """
  2360. d = {'position': self.position}
  2361. if 'normal' in self.shapes:
  2362. d['normal_vector_start'] = self.shapes['normal'].geometric_features()['start']
  2363. d['normal_vector_end'] = self.shapes['normal'].geometric_features()['end']
  2364. return d
  2365. def _test1():
  2366. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2367. l1 = Line((0,0), (1,1))
  2368. l1.draw()
  2369. eval(input(': '))
  2370. c1 = Circle((5,2), 1)
  2371. c2 = Circle((6,2), 1)
  2372. w1 = Wheel((7,2), 1)
  2373. c1.draw()
  2374. c2.draw()
  2375. w1.draw()
  2376. hardcopy()
  2377. display() # show the plot
  2378. def _test2():
  2379. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2380. l1 = Line((0,0), (1,1))
  2381. l1.draw()
  2382. eval(input(': '))
  2383. c1 = Circle((5,2), 1)
  2384. c2 = Circle((6,2), 1)
  2385. w1 = Wheel((7,2), 1)
  2386. filled_curves(True)
  2387. set_linecolor('blue')
  2388. c1.draw()
  2389. set_linecolor('aqua')
  2390. c2.draw()
  2391. filled_curves(False)
  2392. set_linecolor('red')
  2393. w1.draw()
  2394. hardcopy()
  2395. display() # show the plot
  2396. def _test3():
  2397. """Test example from the book."""
  2398. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2399. l1 = Line(start=(0,0), stop=(1,1)) # define line
  2400. l1.draw() # make plot data
  2401. r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
  2402. r1.draw()
  2403. Circle(center=(5,7), radius=1).draw()
  2404. Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7).draw()
  2405. hardcopy()
  2406. display()
  2407. def _test4():
  2408. """Second example from the book."""
  2409. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2410. r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
  2411. c1 = Circle(center=(5,7), radius=1)
  2412. w1 = Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7)
  2413. c2 = Circle(center=(7,7), radius=1)
  2414. filled_curves(True)
  2415. c1.draw()
  2416. set_linecolor('blue')
  2417. r1.draw()
  2418. set_linecolor('aqua')
  2419. c2.draw()
  2420. # Add thick aqua line around rectangle:
  2421. filled_curves(False)
  2422. set_linewidth(4)
  2423. r1.draw()
  2424. set_linecolor('red')
  2425. # Draw wheel with thick lines:
  2426. w1.draw()
  2427. hardcopy('tmp_colors')
  2428. display()
  2429. def _test5():
  2430. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2431. c = 6. # center point of box
  2432. w = 2. # size of box
  2433. L = 3
  2434. r1 = Rectangle((c-w/2, c-w/2), w, w)
  2435. l1 = Line((c,c-w/2), (c,c-w/2-L))
  2436. linecolor('blue')
  2437. filled_curves(True)
  2438. r1.draw()
  2439. linecolor('aqua')
  2440. filled_curves(False)
  2441. l1.draw()
  2442. hardcopy()
  2443. display() # show the plot
  2444. def rolling_wheel(total_rotation_angle):
  2445. """Animation of a rotating wheel."""
  2446. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2447. import time
  2448. center = (6,2)
  2449. radius = 2.0
  2450. angle = 2.0
  2451. pngfiles = []
  2452. w1 = Wheel(center=center, radius=radius, inner_radius=0.5, nlines=7)
  2453. for i in range(int(total_rotation_angle/angle)):
  2454. w1.draw()
  2455. print('BIG PROBLEM WITH ANIMATE!!!')
  2456. display()
  2457. filename = 'tmp_%03d' % i
  2458. pngfiles.append(filename + '.png')
  2459. hardcopy(filename)
  2460. time.sleep(0.3) # pause
  2461. L = radius*angle*pi/180 # translation = arc length
  2462. w1.rotate(angle, center)
  2463. w1.translate((-L, 0))
  2464. center = (center[0] - L, center[1])
  2465. erase()
  2466. cmd = 'convert -delay 50 -loop 1000 %s tmp_movie.gif' \
  2467. % (' '.join(pngfiles))
  2468. print('converting PNG files to animated GIF:\n', cmd)
  2469. import subprocess
  2470. failure, output = subprocess.getstatusoutput(cmd)
  2471. if failure: print('Could not run', cmd)
  2472. if __name__ == '__main__':
  2473. #rolling_wheel(40)
  2474. #_test1()
  2475. #_test3()
  2476. funcs = [
  2477. #test_Axis,
  2478. test_inclined_plane,
  2479. ]
  2480. for func in funcs:
  2481. func()
  2482. input('Type Return: ')