shapes.py 132 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885
  1. from numpy import linspace, sin, cos, pi, array, asarray, ndarray, sqrt, abs
  2. import pprint, copy, glob, os
  3. from math import radians
  4. from MatplotlibDraw import MatplotlibDraw
  5. drawing_tool = MatplotlibDraw()
  6. def point(x, y, check_inside=False):
  7. for obj, name in zip([x, y], ['x', 'y']):
  8. if isinstance(obj, (float,int)):
  9. pass
  10. elif isinstance(obj, ndarray):
  11. if obj.size == 1:
  12. pass
  13. else:
  14. raise TypeError('%s=%s of type %d has length=%d > 1' %
  15. (name, obj, type(obj), obj.size))
  16. else:
  17. raise TypeError('%s=%s is of wrong type %d' %
  18. (name, obj, type(obj)))
  19. if check_inside:
  20. ok, msg = drawing_tool.inside((x,y), exception=True)
  21. if not ok:
  22. print msg
  23. return array((x, y), dtype=float)
  24. def distance(p1, p2):
  25. p1 = arr2D(p1); p2 = arr2D(p2)
  26. d = p2 - p1
  27. return sqrt(d[0]**2 + d[1]**2)
  28. def unit_vec(x, y=None):
  29. """Return unit vector of the vector (x,y), or just x if x is a 2D point."""
  30. if isinstance(x, (float,int)) and isinstance(y, (float,int)):
  31. x = point(x, y)
  32. elif isinstance(x, (list,tuple,ndarray)) and y is None:
  33. return arr2D(x)/sqrt(x[0]**2 + x[1]**2)
  34. else:
  35. raise TypeError('x=%s is %s, must be float or ndarray 2D point' %
  36. (x, type(x)))
  37. def arr2D(x, check_inside=False):
  38. if isinstance(x, (tuple,list,ndarray)):
  39. if len(x) == 2:
  40. pass
  41. else:
  42. raise ValueError('x=%s has length %d, not 2' % (x, len(x)))
  43. else:
  44. raise TypeError('x=%s must be list/tuple/ndarray, not %s' %
  45. (x, type(x)))
  46. if check_inside:
  47. ok, msg = drawing_tool.inside(x, exception=True)
  48. if not ok:
  49. print msg
  50. return asarray(x, dtype=float)
  51. def _is_sequence(seq, length=None,
  52. can_be_None=False, error_message=True):
  53. if can_be_None:
  54. legal_types = (list,tuple,ndarray,None)
  55. else:
  56. legal_types = (list,tuple,ndarray)
  57. if isinstance(seq, legal_types):
  58. if length is not None:
  59. if length == len(seq):
  60. return True
  61. elif error_message:
  62. raise TypeError('%s is %s; must be %s of length %d' %
  63. (str(seq), type(seq),
  64. ', '.join([str(t) for t in legal_types]),
  65. len(seq)))
  66. else:
  67. return False
  68. else:
  69. return True
  70. elif error_message:
  71. raise TypeError('%s is %s, %s; must be %s' %
  72. (str(seq), seq.__class__.__name__, type(seq),
  73. ','.join([str(t)[5:-1] for t in legal_types])))
  74. else:
  75. return False
  76. def is_sequence(*sequences, **kwargs):
  77. length = kwargs.get('length', 2)
  78. can_be_None = kwargs.get('can_be_None', False)
  79. error_message = kwargs.get('error_message', True)
  80. check_inside = kwargs.get('check_inside', False)
  81. for x in sequences:
  82. _is_sequence(x, length=length, can_be_None=can_be_None,
  83. error_message=error_message)
  84. if check_inside:
  85. ok, msg = drawing_tool.inside(x, exception=True)
  86. if not ok:
  87. print msg
  88. def animate(fig, time_points, action, moviefiles=False,
  89. pause_per_frame=0.5, show_screen_graphics=True,
  90. title=None,
  91. **action_kwargs):
  92. if moviefiles:
  93. # Clean up old frame files
  94. framefilestem = 'tmp_frame_'
  95. framefiles = glob.glob('%s*.png' % framefilestem)
  96. for framefile in framefiles:
  97. os.remove(framefile)
  98. for n, t in enumerate(time_points):
  99. drawing_tool.erase()
  100. action(t, fig, **action_kwargs)
  101. #could demand returning fig, but in-place modifications
  102. #are done anyway
  103. #fig = action(t, fig)
  104. #if fig is None:
  105. # raise TypeError(
  106. # 'animate: action returns None, not fig\n'
  107. # '(a Shape object with the whole figure)')
  108. fig.draw()
  109. drawing_tool.display(title=title, show=show_screen_graphics)
  110. if moviefiles:
  111. drawing_tool.savefig('%s%04d.png' % (framefilestem, n))
  112. if moviefiles:
  113. return '%s%%04d.png' % framefilestem
  114. class Shape:
  115. """
  116. Superclass for drawing different geometric shapes.
  117. Subclasses define shapes, but drawing, rotation, translation,
  118. etc. are done in generic functions in this superclass.
  119. """
  120. def __init__(self):
  121. """
  122. Never to be called from subclasses.
  123. """
  124. raise NotImplementedError(
  125. 'class %s must implement __init__,\nwhich defines '
  126. 'self.shapes as a dict (or list) of Shape objects\n'
  127. 'Do not call Shape.__init__!' % \
  128. self.__class__.__name__)
  129. def set_name(self, name):
  130. self.name = name
  131. return self
  132. def get_name(self):
  133. return self.name if hasattr(self, 'name') else 'no_name'
  134. def __iter__(self):
  135. # We iterate over self.shapes many places, and will
  136. # get here if self.shapes is just a Shape object and
  137. # not the assumed dict/list.
  138. print 'Warning: class %s does not define self.shapes\n'\
  139. 'as a dict of Shape objects'
  140. return [self] # Make the iteration work
  141. def copy(self):
  142. return copy.deepcopy(self)
  143. def __getitem__(self, name):
  144. """
  145. Allow indexing like::
  146. obj1['name1']['name2']
  147. all the way down to ``Curve`` or ``Point`` (``Text``)
  148. objects.
  149. """
  150. if hasattr(self, 'shapes'):
  151. if name in self.shapes:
  152. return self.shapes[name]
  153. else:
  154. for shape in self.shapes:
  155. if isinstance(self.shapes[shape], (Curve,Point)):
  156. # Indexing of Curve/Point/Text is not possible
  157. raise TypeError(
  158. 'Index "%s" (%s) is illegal' %
  159. (name, self.__class__.__name__))
  160. return self.shapes[shape][name]
  161. else:
  162. raise Exception('This is a bug in __getitem__')
  163. def __setitem__(self, name, value):
  164. """
  165. Allow assignment like::
  166. obj1['name1']['name2'] = value
  167. all the way down to ``Curve`` or ``Point`` (``Text``)
  168. objects.
  169. """
  170. if hasattr(self, 'shapes'):
  171. self.shapes[name] = value
  172. else:
  173. raise Exception('Cannot assign')
  174. def _for_all_shapes(self, func, *args, **kwargs):
  175. if not hasattr(self, 'shapes'):
  176. # When self.shapes is lacking, we either come to
  177. # a special implementation of func or we come here
  178. # because Shape.func is just inherited. This is
  179. # an error if the class is not Curve or Point
  180. if isinstance(self, (Curve, Point)):
  181. return # ok: no shapes and no func
  182. else:
  183. raise AttributeError('class %s has no shapes attribute!' %
  184. self.__class__.__name__)
  185. is_dict = True if isinstance(self.shapes, dict) else False
  186. for k, shape in enumerate(self.shapes):
  187. if is_dict:
  188. shape_name = shape
  189. shape = self.shapes[shape]
  190. else:
  191. shape_name = k # use index as name if list
  192. if not isinstance(shape, Shape):
  193. if isinstance(shape, dict):
  194. raise TypeError(
  195. 'class %s has a self.shapes member "%s" that is just\n'
  196. 'a plain dictionary,\n%s\n'
  197. 'Did you mean to embed this dict in a Composition\n'
  198. 'object?' % (self.__class__.__name__, shape_name,
  199. str(shape)))
  200. elif isinstance(shape, (list,tuple)):
  201. raise TypeError(
  202. 'class %s has self.shapes member "%s" containing\n'
  203. 'a %s object %s,\n'
  204. 'Did you mean to embed this list in a Composition\n'
  205. 'object?' % (self.__class__.__name__, shape_name,
  206. type(shape), str(shape)))
  207. elif shape is None:
  208. raise TypeError(
  209. 'class %s has a self.shapes member "%s" that is None.\n'
  210. 'Some variable name is wrong, or some function\n'
  211. 'did not return the right object...' \
  212. % (self.__class__.__name__, shape_name))
  213. else:
  214. raise TypeError(
  215. 'class %s has a self.shapes member "%s" of %s which '
  216. 'is not a Shape object\n%s' %
  217. (self.__class__.__name__, shape_name, type(shape),
  218. pprint.pformat(self.shapes)))
  219. if isinstance(shape, Curve):
  220. shape.name = shape_name
  221. getattr(shape, func)(*args, **kwargs)
  222. def draw(self):
  223. self._for_all_shapes('draw')
  224. return self
  225. def draw_dimensions(self):
  226. if hasattr(self, 'dimensions'):
  227. for shape in self.dimensions:
  228. self.dimensions[shape].draw()
  229. return self
  230. else:
  231. #raise AttributeError('no self.dimensions dict for defining dimensions of class %s' % self.__classname__.__name__)
  232. return self
  233. def rotate(self, angle, center):
  234. is_sequence(center, length=2)
  235. self._for_all_shapes('rotate', angle, center)
  236. return self
  237. def translate(self, vec):
  238. is_sequence(vec, length=2)
  239. self._for_all_shapes('translate', vec)
  240. return self
  241. def scale(self, factor):
  242. self._for_all_shapes('scale', factor)
  243. return self
  244. def deform(self, displacement_function):
  245. self._for_all_shapes('deform', displacement_function)
  246. return self
  247. def minmax_coordinates(self, minmax=None):
  248. if minmax is None:
  249. minmax = {'xmin': 1E+20, 'xmax': -1E+20,
  250. 'ymin': 1E+20, 'ymax': -1E+20}
  251. self._for_all_shapes('minmax_coordinates', minmax)
  252. return minmax
  253. def recurse(self, name, indent=0):
  254. if not isinstance(self.shapes, dict):
  255. raise TypeError('recurse works only with dict self.shape, not %s' %
  256. type(self.shapes))
  257. space = ' '*indent
  258. print space, '%s: %s.shapes has entries' % \
  259. (self.__class__.__name__, name), \
  260. str(list(self.shapes.keys()))[1:-1]
  261. for shape in self.shapes:
  262. print space,
  263. print 'call %s.shapes["%s"].recurse("%s", %d)' % \
  264. (name, shape, shape, indent+2)
  265. self.shapes[shape].recurse(shape, indent+2)
  266. def graphviz_dot(self, name, classname=True):
  267. if not isinstance(self.shapes, dict):
  268. raise TypeError('recurse works only with dict self.shape, not %s' %
  269. type(self.shapes))
  270. dotfile = name + '.dot'
  271. pngfile = name + '.png'
  272. if classname:
  273. name = r"%s:\n%s" % (self.__class__.__name__, name)
  274. couplings = self._object_couplings(name, classname=classname)
  275. # Insert counter for similar names
  276. from collections import defaultdict
  277. count = defaultdict(lambda: 0)
  278. couplings2 = []
  279. for i in range(len(couplings)):
  280. parent, child = couplings[i]
  281. count[child] += 1
  282. parent += ' (%d)' % count[parent]
  283. child += ' (%d)' % count[child]
  284. couplings2.append((parent, child))
  285. print 'graphviz', couplings, count
  286. # Remove counter for names there are only one of
  287. for i in range(len(couplings)):
  288. parent2, child2 = couplings2[i]
  289. parent, child = couplings[i]
  290. if count[parent] > 1:
  291. parent = parent2
  292. if count[child] > 1:
  293. child = child2
  294. couplings[i] = (parent, child)
  295. print couplings
  296. f = open(dotfile, 'w')
  297. f.write('digraph G {\n')
  298. for parent, child in couplings:
  299. f.write('"%s" -> "%s";\n' % (parent, child))
  300. f.write('}\n')
  301. f.close()
  302. print 'Run dot -Tpng -o %s %s' % (pngfile, dotfile)
  303. def _object_couplings(self, parent, couplings=[], classname=True):
  304. """Find all couplings of parent and child objects in a figure."""
  305. for shape in self.shapes:
  306. if classname:
  307. childname = r"%s:\n%s" % \
  308. (self.shapes[shape].__class__.__name__, shape)
  309. else:
  310. childname = shape
  311. couplings.append((parent, childname))
  312. self.shapes[shape]._object_couplings(childname, couplings,
  313. classname)
  314. return couplings
  315. def set_linestyle(self, style):
  316. styles = ('solid', 'dashed', 'dashdot', 'dotted')
  317. if style not in styles:
  318. raise ValueError('%s: style=%s must be in %s' %
  319. (self.__class__.__name__ + '.set_linestyle:',
  320. style, str(styles)))
  321. self._for_all_shapes('set_linestyle', style)
  322. return self
  323. def set_linewidth(self, width):
  324. if not isinstance(width, int) and width >= 0:
  325. raise ValueError('%s: width=%s must be positive integer' %
  326. (self.__class__.__name__ + '.set_linewidth:',
  327. width))
  328. self._for_all_shapes('set_linewidth', width)
  329. return self
  330. def set_linecolor(self, color):
  331. if color in drawing_tool.line_colors:
  332. color = drawing_tool.line_colors[color]
  333. elif color in drawing_tool.line_colors.values():
  334. pass # color is ok
  335. else:
  336. raise ValueError('%s: invalid color "%s", must be in %s' %
  337. (self.__class__.__name__ + '.set_linecolor:',
  338. color, list(drawing_tool.line_colors.keys())))
  339. self._for_all_shapes('set_linecolor', color)
  340. return self
  341. def set_arrow(self, style):
  342. styles = ('->', '<-', '<->')
  343. if not style in styles:
  344. raise ValueError('%s: style=%s must be in %s' %
  345. (self.__class__.__name__ + '.set_arrow:',
  346. style, styles))
  347. self._for_all_shapes('set_arrow', style)
  348. return self
  349. def set_filled_curves(self, color='', pattern=''):
  350. if color in drawing_tool.line_colors:
  351. color = drawing_tool.line_colors[color]
  352. elif color in drawing_tool.line_colors.values():
  353. pass # color is ok
  354. else:
  355. raise ValueError('%s: invalid color "%s", must be in %s' %
  356. (self.__class__.__name__ + '.set_filled_curves:',
  357. color, list(drawing_tool.line_colors.keys())))
  358. self._for_all_shapes('set_filled_curves', color, pattern)
  359. return self
  360. def set_shadow(self, pixel_displacement=3):
  361. self._for_all_shapes('set_shadow', pixel_displacement)
  362. return self
  363. def show_hierarchy(self, indent=0, format='std'):
  364. """Recursive pretty print of hierarchy of objects."""
  365. if not isinstance(self.shapes, dict):
  366. print 'cannot print hierarchy when %s.shapes is not a dict' % \
  367. self.__class__.__name__
  368. s = ''
  369. if format == 'dict':
  370. s += '{'
  371. for shape in self.shapes:
  372. if format == 'dict':
  373. shape_str = repr(shape) + ':'
  374. elif format == 'plain':
  375. shape_str = shape
  376. else:
  377. shape_str = shape + ':'
  378. if format == 'dict' or format == 'plain':
  379. class_str = ''
  380. else:
  381. class_str = ' (%s)' % \
  382. self.shapes[shape].__class__.__name__
  383. s += '\n%s%s%s %s' % (
  384. ' '*indent,
  385. shape_str,
  386. class_str,
  387. self.shapes[shape].show_hierarchy(indent+4, format))
  388. if format == 'dict':
  389. s += '}'
  390. return s
  391. def __str__(self):
  392. """Display hierarchy with minimum information (just object names)."""
  393. return self.show_hierarchy(format='plain')
  394. def __repr__(self):
  395. """Display hierarchy as a dictionary."""
  396. return self.show_hierarchy(format='dict')
  397. #return pprint.pformat(self.shapes)
  398. class Curve(Shape):
  399. """General curve as a sequence of (x,y) coordintes."""
  400. def __init__(self, x, y):
  401. """
  402. `x`, `y`: arrays holding the coordinates of the curve.
  403. """
  404. self.x = asarray(x, dtype=float)
  405. self.y = asarray(y, dtype=float)
  406. #self.shapes must not be defined in this class
  407. #as self.shapes holds children objects:
  408. #Curve has no children (end leaf of self.shapes tree)
  409. self.linestyle = None
  410. self.linewidth = None
  411. self.linecolor = None
  412. self.fillcolor = None
  413. self.fillpattern = None
  414. self.arrow = None
  415. self.shadow = False
  416. self.name = None # name of object that this Curve represents
  417. def inside_plot_area(self, verbose=True):
  418. """Check that all coordinates are within drawing_tool's area."""
  419. xmin, xmax = self.x.min(), self.x.max()
  420. ymin, ymax = self.y.min(), self.y.max()
  421. t = drawing_tool
  422. inside = True
  423. if not hasattr(t, 'xmin'):
  424. return None # drawing area is not defined
  425. if xmin < t.xmin:
  426. inside = False
  427. if verbose:
  428. print 'x_min=%g < plot area x_min=%g' % (xmin, t.xmin)
  429. if xmax > t.xmax:
  430. inside = False
  431. if verbose:
  432. print 'x_max=%g > plot area x_max=%g' % (xmax, t.xmax)
  433. if ymin < t.ymin:
  434. inside = False
  435. if verbose:
  436. print 'y_min=%g < plot area y_min=%g' % (ymin, t.ymin)
  437. if ymax > t.ymax:
  438. inside = False
  439. if verbose:
  440. print 'y_max=%g > plot area y_max=%g' % (ymax, t.ymax)
  441. return inside
  442. def draw(self):
  443. """
  444. Send the curve to the plotting engine. That is, convert
  445. coordinate information in self.x and self.y, together
  446. with optional settings of linestyles, etc., to
  447. plotting commands for the chosen engine.
  448. """
  449. self.inside_plot_area()
  450. drawing_tool.plot_curve(
  451. self.x, self.y,
  452. self.linestyle, self.linewidth, self.linecolor,
  453. self.arrow, self.fillcolor, self.fillpattern,
  454. self.shadow, self.name)
  455. def rotate(self, angle, center):
  456. """
  457. Rotate all coordinates: `angle` is measured in degrees and
  458. (`x`,`y`) is the "origin" of the rotation.
  459. """
  460. angle = radians(angle)
  461. x, y = center
  462. c = cos(angle); s = sin(angle)
  463. xnew = x + (self.x - x)*c - (self.y - y)*s
  464. ynew = y + (self.x - x)*s + (self.y - y)*c
  465. self.x = xnew
  466. self.y = ynew
  467. return self
  468. def scale(self, factor):
  469. """Scale all coordinates by `factor`: ``x = factor*x``, etc."""
  470. self.x = factor*self.x
  471. self.y = factor*self.y
  472. return self
  473. def translate(self, vec):
  474. """Translate all coordinates by a vector `vec`."""
  475. self.x += vec[0]
  476. self.y += vec[1]
  477. return self
  478. def deform(self, displacement_function):
  479. """Displace all coordinates according to displacement_function(x,y)."""
  480. for i in range(len(self.x)):
  481. self.x[i], self.y[i] = displacement_function(self.x[i], self.y[i])
  482. return self
  483. def minmax_coordinates(self, minmax=None):
  484. if minmax is None:
  485. minmax = {'xmin': [], 'xmax': [], 'ymin': [], 'ymax': []}
  486. minmax['xmin'] = min(self.x.min(), minmax['xmin'])
  487. minmax['xmax'] = max(self.x.max(), minmax['xmax'])
  488. minmax['ymin'] = min(self.y.min(), minmax['ymin'])
  489. minmax['ymax'] = max(self.y.max(), minmax['ymax'])
  490. return minmax
  491. def recurse(self, name, indent=0):
  492. space = ' '*indent
  493. print space, 'reached "bottom" object %s' % \
  494. self.__class__.__name__
  495. def _object_couplings(self, parent, couplings=[], classname=True):
  496. return
  497. def set_linecolor(self, color):
  498. self.linecolor = color
  499. return self
  500. def set_linewidth(self, width):
  501. self.linewidth = width
  502. return self
  503. def set_linestyle(self, style):
  504. self.linestyle = style
  505. return self
  506. def set_arrow(self, style=None):
  507. self.arrow = style
  508. return self
  509. def set_filled_curves(self, color='', pattern=''):
  510. self.fillcolor = color
  511. self.fillpattern = pattern
  512. return self
  513. def set_shadow(self, pixel_displacement=3):
  514. self.shadow = pixel_displacement
  515. return self
  516. def show_hierarchy(self, indent=0, format='std'):
  517. if format == 'dict':
  518. return '"%s"' % str(self)
  519. elif format == 'plain':
  520. return ''
  521. else:
  522. return str(self)
  523. def __str__(self):
  524. """Compact pretty print of a Curve object."""
  525. s = '%d (x,y) coords' % self.x.size
  526. inside = self.inside_plot_area(verbose=False)
  527. if inside is None:
  528. pass # no info about the plotting area
  529. elif not inside:
  530. s += ', some coordinates are outside plotting area!\n'
  531. props = ('linecolor', 'linewidth', 'linestyle', 'arrow',
  532. 'fillcolor', 'fillpattern')
  533. for prop in props:
  534. value = getattr(self, prop)
  535. if value is not None:
  536. s += ' %s=%s' % (prop, repr(value))
  537. return s
  538. def __repr__(self):
  539. return str(self)
  540. class Spline(Shape):
  541. # Note: UnivariateSpline interpolation may not work if
  542. # the x[i] points are far from uniformly spaced
  543. def __init__(self, x, y, degree=3, resolution=501):
  544. from scipy.interpolate import UnivariateSpline
  545. self.smooth = UnivariateSpline(x, y, s=0, k=degree)
  546. self.xcoor = linspace(x[0], x[-1], resolution)
  547. ycoor = self.smooth(self.xcoor)
  548. self.shapes = {'smooth': Curve(self.xcoor, ycoor)}
  549. def geometric_features(self):
  550. s = self.shapes['smooth']
  551. return {'start': point(s.x[0], s.y[0]),
  552. 'end': point(s.x[-1], s.y[-1]),
  553. 'interval': [s.x[0], s.x[-1]]}
  554. def __call__(self, x):
  555. return self.smooth(x)
  556. # Can easily find the derivative and the integral as
  557. # self.smooth.derivative(n=1) and self.smooth.antiderivative()
  558. class SketchyFunc1(Spline):
  559. """
  560. A typical function curve used to illustrate an "arbitrary" function.
  561. """
  562. domain = [1, 6]
  563. def __init__(self, name=None, name_pos='start',
  564. xmin=1, xmax=6, ymin=2.4, ymax=5):
  565. x = array([1, 2, 3, 4, 5, 6])
  566. y = array([5, 3.5, 3.8, 3, 2.5, 2.4])
  567. # Scale x and y
  568. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  569. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  570. Spline.__init__(self, x, y)
  571. self.shapes['smooth'].set_linecolor('black')
  572. if name is not None:
  573. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  574. class SketchyFunc3(Spline):
  575. """
  576. A typical function curve used to illustrate an "arbitrary" function.
  577. """
  578. domain = [0, 6]
  579. def __init__(self, name=None, name_pos='start',
  580. xmin=0, xmax=6, ymin=0.5, ymax=3.8):
  581. x = array([0, 2, 3, 4, 5, 6])
  582. #y = array([2, 3.5, 3.8, 2, 2.5, 2.6])
  583. y = array([0.5, 3.5, 3.8, 2, 2.5, 3.5])
  584. # Scale x and y
  585. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  586. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  587. Spline.__init__(self, x, y)
  588. self.shapes['smooth'].set_linecolor('black')
  589. if name is not None:
  590. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  591. class SketchyFunc4(Spline):
  592. """
  593. A typical function curve used to illustrate an "arbitrary" function.
  594. Can be a companion function to SketchyFunc3.
  595. """
  596. domain = [1, 6]
  597. def __init__(self, name=None, name_pos='start',
  598. xmin=0, xmax=6, ymin=0.5, ymax=1.8):
  599. x = array([0, 2, 3, 4, 5, 6])
  600. y = array([1.5, 1.3, 0.7, 0.5, 0.6, 0.8])
  601. # Scale x and y
  602. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  603. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  604. Spline.__init__(self, x, y)
  605. self.shapes['smooth'].set_linecolor('black')
  606. if name is not None:
  607. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  608. class SketchyFunc2(Shape):
  609. """
  610. A typical function curve used to illustrate an "arbitrary" function.
  611. """
  612. domain = [0, 2.25]
  613. def __init__(self, name=None, name_pos='end',
  614. xmin=0, xmax=2.25, ymin=0.046679703125, ymax=1.259375):
  615. a = 0; b = 2.25
  616. resolution = 100
  617. x = linspace(a, b, resolution+1)
  618. f = self # for calling __call__
  619. y = f(x)
  620. # Scale x and y
  621. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  622. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  623. self.shapes = {'smooth': Curve(x, y)}
  624. self.shapes['smooth'].set_linecolor('black')
  625. pos = point(a, f(a)) if name_pos == 'start' else point(b, f(b))
  626. if name is not None:
  627. self.shapes['name'] = Text(name, pos + point(0,0.1))
  628. def __call__(self, x):
  629. return 0.5+x*(2-x)*(0.9-x) # on [0, 2.25]
  630. class Point(Shape):
  631. """A point (x,y) which can be rotated, translated, and scaled."""
  632. def __init__(self, x, y):
  633. self.x, self.y = x, y
  634. #self.shapes is not needed in this class
  635. def __add__(self, other):
  636. if isinstance(other, (list,tuple)):
  637. other = Point(other)
  638. return Point(self.x+other.x, self.y+other.y)
  639. # class Point is an abstract class - only subclasses are useful
  640. # and must implement draw
  641. def draw(self):
  642. raise NotImplementedError(
  643. 'class %s must implement the draw method' %
  644. self.__class__.__name__)
  645. def rotate(self, angle, center):
  646. """Rotate point an `angle` (in degrees) around (`x`,`y`)."""
  647. angle = angle*pi/180
  648. x, y = center
  649. c = cos(angle); s = sin(angle)
  650. xnew = x + (self.x - x)*c - (self.y - y)*s
  651. ynew = y + (self.x - x)*s + (self.y - y)*c
  652. self.x = xnew
  653. self.y = ynew
  654. return self
  655. def scale(self, factor):
  656. """Scale point coordinates by `factor`: ``x = factor*x``, etc."""
  657. self.x = factor*self.x
  658. self.y = factor*self.y
  659. return self
  660. def translate(self, vec):
  661. """Translate point by a vector `vec`."""
  662. self.x += vec[0]
  663. self.y += vec[1]
  664. return self
  665. def deform(self, displacement_function):
  666. """Displace coordinates according to displacement_function(x,y)."""
  667. for i in range(len(self.x)):
  668. self.x, self.y = displacement_function(self.x, self.y)
  669. return self
  670. def minmax_coordinates(self, minmax=None):
  671. if minmax is None:
  672. minmax = {'xmin': [], 'xmax': [], 'ymin': [], 'ymax': []}
  673. minmax['xmin'] = min(self.x, minmax['xmin'])
  674. minmax['xmax'] = max(self.x, minmax['xmax'])
  675. minmax['ymin'] = min(self.y, minmax['ymin'])
  676. minmax['ymax'] = max(self.y, minmax['ymax'])
  677. return minmax
  678. def recurse(self, name, indent=0):
  679. space = ' '*indent
  680. print space, 'reached "bottom" object %s' % \
  681. self.__class__.__name__
  682. def _object_couplings(self, parent, couplings=[], classname=True):
  683. return
  684. # No need for set_linecolor etc since self._for_all_shapes, which
  685. # is always called for these functions, makes a test and stops
  686. # calls if self.shapes is missing and the object is Point or Curve
  687. def show_hierarchy(self, indent=0, format='std'):
  688. s = '%s at (%g,%g)' % (self.__class__.__name__, self.x, self.y)
  689. if format == 'dict':
  690. return '"%s"' % s
  691. elif format == 'plain':
  692. return ''
  693. else:
  694. return s
  695. # no need to store input data as they are invalid after rotations etc.
  696. class Rectangle(Shape):
  697. """
  698. Rectangle specified by the point `lower_left_corner`, `width`,
  699. and `height`.
  700. """
  701. def __init__(self, lower_left_corner, width, height):
  702. is_sequence(lower_left_corner)
  703. p = arr2D(lower_left_corner) # short form
  704. x = [p[0], p[0] + width,
  705. p[0] + width, p[0], p[0]]
  706. y = [p[1], p[1], p[1] + height,
  707. p[1] + height, p[1]]
  708. self.shapes = {'rectangle': Curve(x,y)}
  709. # Dimensions
  710. dims = {
  711. 'width': Distance_wText(p + point(0, -height/5.),
  712. p + point(width, -height/5.),
  713. 'width'),
  714. 'height': Distance_wText(p + point(width + width/5., 0),
  715. p + point(width + width/5., height),
  716. 'height'),
  717. 'lower_left_corner': Text_wArrow('lower_left_corner',
  718. p - point(width/5., height/5.), p)
  719. }
  720. self.dimensions = dims
  721. def geometric_features(self):
  722. """
  723. Return dictionary with
  724. ==================== =============================================
  725. Attribute Description
  726. ==================== =============================================
  727. lower_left Lower left corner point.
  728. upper_left Upper left corner point.
  729. lower_right Lower right corner point.
  730. upper_right Upper right corner point.
  731. lower_mid Middle point on lower side.
  732. upper_mid Middle point on upper side.
  733. center Center point
  734. ==================== =============================================
  735. """
  736. r = self.shapes['rectangle']
  737. d = {'lower_left': point(r.x[0], r.y[0]),
  738. 'lower_right': point(r.x[1], r.y[1]),
  739. 'upper_right': point(r.x[2], r.y[2]),
  740. 'upper_left': point(r.x[3], r.y[3])}
  741. d['lower_mid'] = 0.5*(d['lower_left'] + d['lower_right'])
  742. d['upper_mid'] = 0.5*(d['upper_left'] + d['upper_right'])
  743. d['left_mid'] = 0.5*(d['lower_left'] + d['upper_left'])
  744. d['right_mid'] = 0.5*(d['lower_right'] + d['upper_right'])
  745. d['center'] = point(d['lower_mid'][0], d['left_mid'][1])
  746. return d
  747. class Triangle(Shape):
  748. """
  749. Triangle defined by its three vertices p1, p2, and p3.
  750. Recorded geometric features:
  751. ==================== =============================================
  752. Attribute Description
  753. ==================== =============================================
  754. p1, p2, p3 Corners as given to the constructor.
  755. ==================== =============================================
  756. """
  757. def __init__(self, p1, p2, p3):
  758. is_sequence(p1, p2, p3)
  759. x = [p1[0], p2[0], p3[0], p1[0]]
  760. y = [p1[1], p2[1], p3[1], p1[1]]
  761. self.shapes = {'triangle': Curve(x,y)}
  762. # Dimensions
  763. self.dimensions = {'p1': Text('p1', p1),
  764. 'p2': Text('p2', p2),
  765. 'p3': Text('p3', p3)}
  766. def geometric_features(self):
  767. t = self.shapes['triangle']
  768. return {'p1': point(t.x[0], t.y[0]),
  769. 'p2': point(t.x[1], t.y[1]),
  770. 'p3': point(t.x[2], t.y[2])}
  771. class Line(Shape):
  772. def __init__(self, start, end):
  773. is_sequence(start, end, length=2)
  774. x = [start[0], end[0]]
  775. y = [start[1], end[1]]
  776. self.shapes = {'line': Curve(x, y)}
  777. def geometric_features(self):
  778. line = self.shapes['line']
  779. return {'start': point(line.x[0], line.y[0]),
  780. 'end': point(line.x[1], line.y[1]),}
  781. def compute_formulas(self):
  782. x, y = self.shapes['line'].x, self.shapes['line'].y
  783. # Define equations for line:
  784. # y = a*x + b, x = c*y + d
  785. try:
  786. self.a = (y[1] - y[0])/(x[1] - x[0])
  787. self.b = y[0] - self.a*x[0]
  788. except ZeroDivisionError:
  789. # Vertical line, y is not a function of x
  790. self.a = None
  791. self.b = None
  792. try:
  793. if self.a is None:
  794. self.c = 0
  795. else:
  796. self.c = 1/float(self.a)
  797. if self.b is None:
  798. self.d = x[1]
  799. except ZeroDivisionError:
  800. # Horizontal line, x is not a function of y
  801. self.c = None
  802. self.d = None
  803. def compute_formulas(self):
  804. x, y = self.shapes['line'].x, self.shapes['line'].y
  805. tol = 1E-14
  806. # Define equations for line:
  807. # y = a*x + b, x = c*y + d
  808. if abs(x[1] - x[0]) > tol:
  809. self.a = (y[1] - y[0])/(x[1] - x[0])
  810. self.b = y[0] - self.a*x[0]
  811. else:
  812. # Vertical line, y is not a function of x
  813. self.a = None
  814. self.b = None
  815. if self.a is None:
  816. self.c = 0
  817. elif abs(self.a) > tol:
  818. self.c = 1/float(self.a)
  819. self.d = x[1]
  820. else: # self.a is 0
  821. # Horizontal line, x is not a function of y
  822. self.c = None
  823. self.d = None
  824. def __call__(self, x=None, y=None):
  825. """Given x, return y on the line, or given y, return x."""
  826. self.compute_formulas()
  827. if x is not None and self.a is not None:
  828. return self.a*x + self.b
  829. elif y is not None and self.c is not None:
  830. return self.c*y + self.d
  831. else:
  832. raise ValueError(
  833. 'Line.__call__(x=%s, y=%s) not meaningful' % \
  834. (x, y))
  835. def new_interval(self, x=None, y=None):
  836. """Redefine current Line to cover interval in x or y."""
  837. if x is not None:
  838. is_sequence(x, length=2)
  839. xL, xR = x
  840. new_line = Line((xL, self(x=xL)), (xR, self(x=xR)))
  841. elif y is not None:
  842. is_sequence(y, length=2)
  843. yL, yR = y
  844. new_line = Line((xL, self(y=xL)), (xR, self(y=xR)))
  845. self.shapes['line'] = new_line['line']
  846. return self
  847. # First implementation of class Circle
  848. class Circle(Shape):
  849. def __init__(self, center, radius, resolution=180):
  850. self.center, self.radius = center, radius
  851. self.resolution = resolution
  852. t = linspace(0, 2*pi, resolution+1)
  853. x0 = center[0]; y0 = center[1]
  854. R = radius
  855. x = x0 + R*cos(t)
  856. y = y0 + R*sin(t)
  857. self.shapes = {'circle': Curve(x, y)}
  858. def __call__(self, theta):
  859. """
  860. Return (x, y) point corresponding to angle theta.
  861. Not valid after a translation, rotation, or scaling.
  862. """
  863. return self.center[0] + self.radius*cos(theta), \
  864. self.center[1] + self.radius*sin(theta)
  865. class Arc(Shape):
  866. def __init__(self, center, radius,
  867. start_angle, arc_angle,
  868. resolution=180):
  869. is_sequence(center)
  870. # Must record some parameters for __call__
  871. self.center = arr2D(center)
  872. self.radius = radius
  873. self.start_angle = radians(start_angle)
  874. self.arc_angle = radians(arc_angle)
  875. t = linspace(self.start_angle,
  876. self.start_angle + self.arc_angle,
  877. resolution+1)
  878. x0 = center[0]; y0 = center[1]
  879. R = radius
  880. x = x0 + R*cos(t)
  881. y = y0 + R*sin(t)
  882. self.shapes = {'arc': Curve(x, y)}
  883. # Cannot set dimensions (Arc_wText recurses into this
  884. # constructor forever). Set in test_Arc instead.
  885. # Stored geometric features
  886. def geometric_features(self):
  887. a = self.shapes['arc']
  888. m = len(a.x)/2 # mid point in array
  889. d = {'start': point(a.x[0], a.y[0]),
  890. 'end': point(a.x[-1], a.y[-1]),
  891. 'mid': point(a.x[m], a.y[m])}
  892. return d
  893. def __call__(self, theta):
  894. """
  895. Return (x,y) point at start_angle + theta.
  896. Not valid after translation, rotation, or scaling.
  897. """
  898. theta = radians(theta)
  899. t = self.start_angle + theta
  900. x0 = self.center[0]
  901. y0 = self.center[1]
  902. R = self.radius
  903. x = x0 + R*cos(t)
  904. y = y0 + R*sin(t)
  905. return (x, y)
  906. # Alternative for small arcs: Parabola
  907. class Parabola(Shape):
  908. def __init__(self, start, mid, stop, resolution=21):
  909. self.p1, self.p2, self.p3 = start, mid, stop
  910. # y as function of x? (no point on line x=const?)
  911. tol = 1E-14
  912. if abs(self.p1[0] - self.p2[0]) > 1E-14 and \
  913. abs(self.p2[0] - self.p3[0]) > 1E-14 and \
  914. abs(self.p3[0] - self.p1[0]) > 1E-14:
  915. self.y_of_x = True
  916. else:
  917. self.y_of_x = False
  918. # x as function of y? (no point on line y=const?)
  919. tol = 1E-14
  920. if abs(self.p1[1] - self.p2[1]) > 1E-14 and \
  921. abs(self.p2[1] - self.p3[1]) > 1E-14 and \
  922. abs(self.p3[1] - self.p1[1]) > 1E-14:
  923. self.x_of_y = True
  924. else:
  925. self.x_of_y = False
  926. if self.y_of_x:
  927. x = linspace(start[0], end[0], resolution)
  928. y = self(x=x)
  929. elif self.x_of_y:
  930. y = linspace(start[1], end[1], resolution)
  931. x = self(y=y)
  932. else:
  933. raise ValueError(
  934. 'Parabola: two or more points lie on x=const '
  935. 'or y=const - not allowed')
  936. self.shapes = {'parabola': Curve(x, y)}
  937. def __call__(self, x=None, y=None):
  938. if x is not None and self.y_of_x:
  939. return self._L2x(self.p1, self.p2)*self.p3[1] + \
  940. self._L2x(self.p2, self.p3)*self.p1[1] + \
  941. self._L2x(self.p3, self.p1)*self.p2[1]
  942. elif y is not None and self.x_of_y:
  943. return self._L2y(self.p1, self.p2)*self.p3[0] + \
  944. self._L2y(self.p2, self.p3)*self.p1[0] + \
  945. self._L2y(self.p3, self.p1)*self.p2[0]
  946. else:
  947. raise ValueError(
  948. 'Parabola.__call__(x=%s, y=%s) not meaningful' % \
  949. (x, y))
  950. def _L2x(self, x, pi, pj, pk):
  951. return (x - pi[0])*(x - pj[0])/((pk[0] - pi[0])*(pk[0] - pj[0]))
  952. def _L2y(self, y, pi, pj, pk):
  953. return (y - pi[1])*(y - pj[1])/((pk[1] - pi[1])*(pk[1] - pj[1]))
  954. class Circle(Arc):
  955. def __init__(self, center, radius, resolution=180):
  956. Arc.__init__(self, center, radius, 0, 360, resolution)
  957. class Wall(Shape):
  958. def __init__(self, x, y, thickness, pattern='/', transparent=False):
  959. is_sequence(x, y, length=len(x))
  960. if isinstance(x[0], (tuple,list,ndarray)):
  961. # x is list of curves
  962. x1 = concatenate(x)
  963. else:
  964. x1 = asarray(x, float)
  965. if isinstance(y[0], (tuple,list,ndarray)):
  966. # x is list of curves
  967. y1 = concatenate(y)
  968. else:
  969. y1 = asarray(y, float)
  970. self.x1 = x1; self.y1 = y1
  971. # Displaced curve (according to thickness)
  972. x2 = x1
  973. y2 = y1 + thickness
  974. # Combine x1,y1 with x2,y2 reversed
  975. from numpy import concatenate
  976. x = concatenate((x1, x2[-1::-1]))
  977. y = concatenate((y1, y2[-1::-1]))
  978. wall = Curve(x, y)
  979. wall.set_filled_curves(color='white', pattern=pattern)
  980. x = [x1[-1]] + x2[-1::-1].tolist() + [x1[0]]
  981. y = [y1[-1]] + y2[-1::-1].tolist() + [y1[0]]
  982. self.shapes = {'wall': wall}
  983. from collections import OrderedDict
  984. self.shapes = OrderedDict()
  985. self.shapes['wall'] = wall
  986. if transparent:
  987. white_eraser = Curve(x, y)
  988. white_eraser.set_linecolor('white')
  989. self.shapes['eraser'] = white_eraser
  990. def geometric_features(self):
  991. d = {'start': point(self.x1[0], self.y1[0]),
  992. 'end': point(self.x1[-1], self.y1[-1])}
  993. return d
  994. class Wall2(Shape):
  995. def __init__(self, x, y, thickness, pattern='/'):
  996. is_sequence(x, y, length=len(x))
  997. if isinstance(x[0], (tuple,list,ndarray)):
  998. # x is list of curves
  999. x1 = concatenate(x)
  1000. else:
  1001. x1 = asarray(x, float)
  1002. if isinstance(y[0], (tuple,list,ndarray)):
  1003. # x is list of curves
  1004. y1 = concatenate(y)
  1005. else:
  1006. y1 = asarray(y, float)
  1007. self.x1 = x1; self.y1 = y1
  1008. # Displaced curve (according to thickness)
  1009. x2 = x1.copy()
  1010. y2 = y1.copy()
  1011. def displace(idx, idx_m, idx_p):
  1012. # Find tangent and normal
  1013. tangent = point(x1[idx_m], y1[idx_m]) - point(x1[idx_p], y1[idx_p])
  1014. tangent = unit_vec(tangent)
  1015. normal = point(tangent[1], -tangent[0])
  1016. # Displace length "thickness" in "positive" normal direction
  1017. displaced_pt = point(x1[idx], y1[idx]) + thickness*normal
  1018. x2[idx], y2[idx] = displaced_pt
  1019. for i in range(1, len(x1)-1):
  1020. displace(i-1, i+1, i) # centered difference for normal comp.
  1021. # One-sided differences at the end points
  1022. i = 0
  1023. displace(i, i+1, i)
  1024. i = len(x1)-1
  1025. displace(i-1, i, i)
  1026. # Combine x1,y1 with x2,y2 reversed
  1027. from numpy import concatenate
  1028. x = concatenate((x1, x2[-1::-1]))
  1029. y = concatenate((y1, y2[-1::-1]))
  1030. wall = Curve(x, y)
  1031. wall.set_filled_curves(color='white', pattern=pattern)
  1032. x = [x1[-1]] + x2[-1::-1].tolist() + [x1[0]]
  1033. y = [y1[-1]] + y2[-1::-1].tolist() + [y1[0]]
  1034. self.shapes['wall'] = wall
  1035. def geometric_features(self):
  1036. d = {'start': point(self.x1[0], self.y1[0]),
  1037. 'end': point(self.x1[-1], self.y1[-1])}
  1038. return d
  1039. class VelocityProfile(Shape):
  1040. def __init__(self, start, height, profile, num_arrows, scaling=1):
  1041. # vx, vy = profile(y)
  1042. shapes = {}
  1043. # Draw left line
  1044. shapes['start line'] = Line(start, (start[0], start[1]+height))
  1045. # Draw velocity arrows
  1046. dy = float(height)/(num_arrows-1)
  1047. x = start[0]
  1048. y = start[1]
  1049. r = profile(y) # Test on return type
  1050. if not isinstance(r, (list,tuple,ndarray)) and len(r) != 2:
  1051. raise TypeError('VelocityProfile constructor: profile(y) function must return velocity vector (vx,vy), not %s' % type(r))
  1052. for i in range(num_arrows):
  1053. y = start[1] + i*dy
  1054. vx, vy = profile(y)
  1055. if abs(vx) < 1E-8:
  1056. continue
  1057. vx *= scaling
  1058. vy *= scaling
  1059. arr = Arrow1((x,y), (x+vx, y+vy), '->')
  1060. shapes['arrow%d' % i] = arr
  1061. # Draw smooth profile
  1062. xs = []
  1063. ys = []
  1064. n = 100
  1065. dy = float(height)/n
  1066. for i in range(n+2):
  1067. y = start[1] + i*dy
  1068. vx, vy = profile(y)
  1069. vx *= scaling
  1070. vy *= scaling
  1071. xs.append(x+vx)
  1072. ys.append(y+vy)
  1073. shapes['smooth curve'] = Curve(xs, ys)
  1074. self.shapes = shapes
  1075. class Arrow1(Shape):
  1076. """Draw an arrow as Line with arrow."""
  1077. def __init__(self, start, end, style='->'):
  1078. arrow = Line(start, end)
  1079. arrow.set_arrow(style)
  1080. # Note:
  1081. self.shapes = {'arrow': arrow}
  1082. def geometric_features(self):
  1083. return self.shapes['arrow'].geometric_features()
  1084. class Arrow3(Shape):
  1085. """
  1086. Build a vertical line and arrow head from Line objects.
  1087. Then rotate `rotation_angle`.
  1088. """
  1089. def __init__(self, start, length, rotation_angle=0):
  1090. self.bottom = start
  1091. self.length = length
  1092. self.angle = rotation_angle
  1093. top = (self.bottom[0], self.bottom[1] + self.length)
  1094. main = Line(self.bottom, top)
  1095. #head_length = self.length/8.0
  1096. head_length = drawing_tool.xrange/50.
  1097. head_degrees = radians(30)
  1098. head_left_pt = (top[0] - head_length*sin(head_degrees),
  1099. top[1] - head_length*cos(head_degrees))
  1100. head_right_pt = (top[0] + head_length*sin(head_degrees),
  1101. top[1] - head_length*cos(head_degrees))
  1102. head_left = Line(head_left_pt, top)
  1103. head_right = Line(head_right_pt, top)
  1104. head_left.set_linestyle('solid')
  1105. head_right.set_linestyle('solid')
  1106. self.shapes = {'line': main, 'head left': head_left,
  1107. 'head right': head_right}
  1108. # rotate goes through self.shapes so self.shapes
  1109. # must be initialized first
  1110. self.rotate(rotation_angle, start)
  1111. def geometric_features(self):
  1112. return self.shapes['line'].geometric_features()
  1113. class Text(Point):
  1114. """
  1115. Place `text` at the (x,y) point `position`, with the given
  1116. fontsize (0 indicates that the default fontsize set in drawing_tool
  1117. is to be used). The text is centered around `position` if `alignment` is
  1118. 'center'; if 'left', the text starts at `position`, and if
  1119. 'right', the right and of the text is located at `position`.
  1120. """
  1121. def __init__(self, text, position, alignment='center', fontsize=0):
  1122. is_sequence(position)
  1123. is_sequence(position, length=2, can_be_None=True)
  1124. self.text = text
  1125. self.position = position
  1126. self.alignment = alignment
  1127. self.fontsize = fontsize
  1128. Point.__init__(self, position[0], position[1])
  1129. #no need for self.shapes here
  1130. def draw(self):
  1131. drawing_tool.text(self.text, (self.x, self.y),
  1132. self.alignment, self.fontsize)
  1133. def __str__(self):
  1134. return 'text "%s" at (%g,%g)' % (self.text, self.x, self.y)
  1135. def __repr__(self):
  1136. return str(self)
  1137. class Text_wArrow(Text):
  1138. """
  1139. As class Text, but an arrow is drawn from the mid part of the text
  1140. to some point `arrow_tip`.
  1141. """
  1142. def __init__(self, text, position, arrow_tip,
  1143. alignment='center', fontsize=0):
  1144. is_sequence(arrow_tip, length=2, can_be_None=True)
  1145. is_sequence(position)
  1146. self.arrow_tip = arrow_tip
  1147. Text.__init__(self, text, position, alignment, fontsize)
  1148. def draw(self):
  1149. drawing_tool.text(self.text, self.position,
  1150. self.alignment, self.fontsize,
  1151. self.arrow_tip)
  1152. def __str__(self):
  1153. return 'annotation "%s" at (%g,%g) with arrow to (%g,%g)' % \
  1154. (self.text, self.x, self.y,
  1155. self.arrow_tip[0], self.arrow_tip[1])
  1156. def __repr__(self):
  1157. return str(self)
  1158. class Axis(Shape):
  1159. def __init__(self, start, length, label,
  1160. rotation_angle=0, fontsize=0,
  1161. label_spacing=1./45, label_alignment='left'):
  1162. """
  1163. Draw axis from start with `length` to the right
  1164. (x axis). Place label at the end of the arrow tip.
  1165. Then return `rotation_angle` (in degrees).
  1166. The `label_spacing` denotes the space between the label
  1167. and the arrow tip as a fraction of the length of the plot
  1168. in x direction. A tuple can be given to adjust the position
  1169. in both the x and y directions (with one parameter, the
  1170. x position is adjusted).
  1171. With `label_alignment` one can place
  1172. the axis label text such that the arrow tip is to the 'left',
  1173. 'right', or 'center' with respect to the text field.
  1174. The `label_spacing` and `label_alignment`parameters can
  1175. be used to fine-tune the location of the label.
  1176. """
  1177. # Arrow is vertical arrow, make it horizontal
  1178. arrow = Arrow3(start, length, rotation_angle=-90)
  1179. arrow.rotate(rotation_angle, start)
  1180. if isinstance(label_spacing, (list,tuple)) and len(label_spacing) == 2:
  1181. x_spacing = drawing_tool.xrange*label_spacing[0]
  1182. y_spacing = drawing_tool.yrange*label_spacing[1]
  1183. elif isinstance(label_spacing, (int,float)):
  1184. # just x spacing
  1185. x_spacing = drawing_tool.xrange*label_spacing
  1186. y_spacing = 0
  1187. # should increase spacing for downward pointing axis
  1188. label_pos = [start[0] + length + x_spacing, start[1] + y_spacing]
  1189. label = Text(label, position=label_pos, fontsize=fontsize)
  1190. label.rotate(rotation_angle, start)
  1191. self.shapes = {'arrow': arrow, 'label': label}
  1192. def geometric_features(self):
  1193. return self.shapes['arrow'].geometric_features()
  1194. # Maybe Axis3 with label below/above?
  1195. class Force(Arrow1):
  1196. """
  1197. Indication of a force by an arrow and a text (symbol). Draw an
  1198. arrow, starting at `start` and with the tip at `end`. The symbol
  1199. is placed at `text_pos`, which can be 'start', 'end' or the
  1200. coordinates of a point. If 'end' or 'start', the text is placed at
  1201. a distance `text_spacing` times the width of the total plotting
  1202. area away from the specified point.
  1203. """
  1204. def __init__(self, start, end, text, text_spacing=1./60,
  1205. fontsize=0, text_pos='start', text_alignment='center'):
  1206. Arrow1.__init__(self, start, end, style='->')
  1207. if isinstance(text_spacing, (tuple,list)):
  1208. if len(text_spacing) == 2:
  1209. spacing = point(drawing_tool.xrange*text_spacing[0],
  1210. drawing_tool.xrange*text_spacing[1])
  1211. else:
  1212. spacing = drawing_tool.xrange*text_spacing[0]
  1213. else:
  1214. # just a number, this is x spacing
  1215. spacing = drawing_tool.xrange*text_spacing
  1216. start, end = arr2D(start), arr2D(end)
  1217. # Two cases: label at bottom of line or top, need more
  1218. # spacing if bottom
  1219. downward = (end-start)[1] < 0
  1220. upward = not downward # for easy code reading
  1221. if isinstance(text_pos, str):
  1222. if text_pos == 'start':
  1223. spacing_dir = unit_vec(start - end)
  1224. if upward:
  1225. spacing *= 1.7
  1226. if isinstance(spacing, (int, float)):
  1227. text_pos = start + spacing*spacing_dir
  1228. else:
  1229. text_pos = start + spacing
  1230. elif text_pos == 'end':
  1231. spacing_dir = unit_vec(end - start)
  1232. if downward:
  1233. spacing *= 1.7
  1234. if isinstance(spacing, (int, float)):
  1235. text_pos = end + spacing*spacing_dir
  1236. else:
  1237. text_pos = end + spacing
  1238. self.shapes['text'] = Text(text, text_pos, fontsize=fontsize,
  1239. alignment=text_alignment)
  1240. def geometric_features(self):
  1241. d = Arrow1.geometric_features(self)
  1242. d['symbol_location'] = self.shapes['text'].position
  1243. return d
  1244. class Axis2(Force):
  1245. def __init__(self, start, length, label,
  1246. rotation_angle=0, fontsize=0,
  1247. label_spacing=1./45, label_alignment='left'):
  1248. direction = point(cos(radians(rotation_angle)),
  1249. sin(radians(rotation_angle)))
  1250. Force.__init__(start=start, end=length*direction, text=label,
  1251. text_spacing=label_spacing,
  1252. fontsize=fontsize, text_pos='end',
  1253. text_alignment=label_alignment)
  1254. # Substitute text by label for axis
  1255. self.shapes['label'] = self.shapes['text']
  1256. del self.shapes['text']
  1257. # geometric features from Force is ok
  1258. class Gravity(Axis):
  1259. """Downward-pointing gravity arrow with the symbol g."""
  1260. def __init__(self, start, length, fontsize=0):
  1261. Axis.__init__(self, start, length, '$g$', below=False,
  1262. rotation_angle=-90, label_spacing=1./30,
  1263. fontsize=fontsize)
  1264. self.shapes['arrow'].set_linecolor('black')
  1265. class Gravity(Force):
  1266. """Downward-pointing gravity arrow with the symbol g."""
  1267. def __init__(self, start, length, text='$g$', fontsize=0):
  1268. Force.__init__(self, start, (start[0], start[1]-length),
  1269. text, text_spacing=1./60,
  1270. fontsize=0, text_pos='end')
  1271. self.shapes['arrow'].set_linecolor('black')
  1272. class Distance_wText(Shape):
  1273. """
  1274. Arrow <-> with text (usually a symbol) at the midpoint, used for
  1275. identifying a some distance in a figure. The text is placed
  1276. slightly to the right of vertical-like arrows, with text displaced
  1277. `text_spacing` times to total distance in x direction of the plot
  1278. area. The text is by default aligned 'left' in this case. For
  1279. horizontal-like arrows, the text is placed the same distance
  1280. above, but aligned 'center' by default (when `alignment` is None).
  1281. """
  1282. def __init__(self, start, end, text, fontsize=0, text_spacing=1/60.,
  1283. alignment=None, text_pos='mid'):
  1284. start = arr2D(start)
  1285. end = arr2D(end)
  1286. # Decide first if we have a vertical or horizontal arrow
  1287. vertical = abs(end[0]-start[0]) < 2*abs(end[1]-start[1])
  1288. if vertical:
  1289. # Assume end above start
  1290. if end[1] < start[1]:
  1291. start, end = end, start
  1292. if alignment is None:
  1293. alignment = 'left'
  1294. else: # horizontal arrow
  1295. # Assume start to the right of end
  1296. if start[0] < end[0]:
  1297. start, end = end, start
  1298. if alignment is None:
  1299. alignment = 'center'
  1300. tangent = end - start
  1301. # Tangeng goes always to the left and upward
  1302. normal = unit_vec([tangent[1], -tangent[0]])
  1303. mid = 0.5*(start + end) # midpoint of start-end line
  1304. if text_pos == 'mid':
  1305. text_pos = mid + normal*drawing_tool.xrange*text_spacing
  1306. text = Text(text, text_pos, fontsize=fontsize,
  1307. alignment=alignment)
  1308. else:
  1309. is_sequence(text_pos, length=2)
  1310. text = Text_wArrow(text, text_pos, mid, alignment='left',
  1311. fontsize=fontsize)
  1312. arrow = Arrow1(start, end, style='<->')
  1313. arrow.set_linecolor('black')
  1314. arrow.set_linewidth(1)
  1315. self.shapes = {'arrow': arrow, 'text': text}
  1316. def geometric_features(self):
  1317. d = self.shapes['arrow'].geometric_features()
  1318. d['text_position'] = self.shapes['text'].position
  1319. return d
  1320. class Arc_wText(Shape):
  1321. def __init__(self, text, center, radius,
  1322. start_angle, arc_angle, fontsize=0,
  1323. resolution=180, text_spacing=1/60.):
  1324. arc = Arc(center, radius, start_angle, arc_angle,
  1325. resolution)
  1326. mid = arr2D(arc(arc_angle/2.))
  1327. normal = unit_vec(mid - arr2D(center))
  1328. text_pos = mid + normal*drawing_tool.xrange*text_spacing
  1329. self.shapes = {'arc': arc,
  1330. 'text': Text(text, text_pos, fontsize=fontsize)}
  1331. class Composition(Shape):
  1332. def __init__(self, shapes):
  1333. """shapes: list or dict of Shape objects."""
  1334. if isinstance(shapes, (tuple,list)):
  1335. # Convert to dict using the type of the list element as key
  1336. # (add a counter to make the keys unique)
  1337. shapes = {s.__class__.__name__ + '_' + str(i): s
  1338. for i, s in enumerate(shapes)}
  1339. self.shapes = shapes
  1340. # can make help methods: Line.midpoint, Line.normal(pt, dir='left') -> (x,y)
  1341. # list annotations in each class? contains extra annotations for explaining
  1342. # important parameters to the constructor, e.g., Line.annotations holds
  1343. # start and end as Text objects. Shape.demo calls shape.draw and
  1344. # for annotation in self.demo: annotation.draw() YES!
  1345. # Can make overall demo of classes by making objects and calling demo
  1346. # Could include demo fig in each constructor
  1347. class SimplySupportedBeam(Shape):
  1348. def __init__(self, pos, size):
  1349. pos = arr2D(pos)
  1350. P0 = (pos[0] - size/2., pos[1]-size)
  1351. P1 = (pos[0] + size/2., pos[1]-size)
  1352. triangle = Triangle(P0, P1, pos)
  1353. gap = size/5.
  1354. h = size/4. # height of rectangle
  1355. P2 = (P0[0], P0[1]-gap-h)
  1356. rectangle = Rectangle(P2, size, h).set_filled_curves(pattern='/')
  1357. self.shapes = {'triangle': triangle, 'rectangle': rectangle}
  1358. self.dimensions = {'pos': Text('pos', pos),
  1359. 'size': Distance_wText((P2[0], P2[1]-size),
  1360. (P2[0]+size, P2[1]-size),
  1361. 'size')}
  1362. def geometric_features(self):
  1363. t = self.shapes['triangle']
  1364. r = self.shapes['rectangle']
  1365. d = {'pos': point(t.x[2], t.y[2]), # "p2"/pos
  1366. 'mid_support': r.geometric_features()['lower_mid']}
  1367. return d
  1368. class ConstantBeamLoad(Shape):
  1369. """
  1370. Downward-pointing arrows indicating a vertical load.
  1371. The arrows are of equal length and filling a rectangle
  1372. specified as in the :class:`Rectangle` class.
  1373. Recorded geometric features:
  1374. ==================== =============================================
  1375. Attribute Description
  1376. ==================== =============================================
  1377. mid_point Middle point at the top of the row of
  1378. arrows (often used for positioning a text).
  1379. ==================== =============================================
  1380. """
  1381. def __init__(self, lower_left_corner, width, height, num_arrows=10):
  1382. box = Rectangle(lower_left_corner, width, height)
  1383. self.shapes = {'box': box}
  1384. dx = float(width)/(num_arrows-1)
  1385. y_top = lower_left_corner[1] + height
  1386. y_tip = lower_left_corner[1]
  1387. for i in range(num_arrows):
  1388. x = lower_left_corner[0] + i*dx
  1389. self.shapes['arrow%d' % i] = Arrow1((x, y_top), (x, y_tip))
  1390. def geometric_features(self):
  1391. return {'mid_top': self.shapes['box'].geometric_features()['upper_mid']}
  1392. class Moment(Arc_wText):
  1393. def __init__(self, text, center, radius,
  1394. left=True, counter_clockwise=True,
  1395. fontsize=0, text_spacing=1/60.):
  1396. style = '->' if counter_clockwise else '<-'
  1397. start_angle = 90 if left else -90
  1398. Arc_wText.__init__(self, text, center, radius,
  1399. start_angle=start_angle,
  1400. arc_angle=180, fontsize=fontsize,
  1401. text_spacing=text_spacing,
  1402. resolution=180)
  1403. self.shapes['arc'].set_arrow(style)
  1404. class Wheel(Shape):
  1405. def __init__(self, center, radius, inner_radius=None, nlines=10):
  1406. if inner_radius is None:
  1407. inner_radius = radius/5.0
  1408. outer = Circle(center, radius)
  1409. inner = Circle(center, inner_radius)
  1410. lines = []
  1411. # Draw nlines+1 since the first and last coincide
  1412. # (then nlines lines will be visible)
  1413. t = linspace(0, 2*pi, self.nlines+1)
  1414. Ri = inner_radius; Ro = radius
  1415. x0 = center[0]; y0 = center[1]
  1416. xinner = x0 + Ri*cos(t)
  1417. yinner = y0 + Ri*sin(t)
  1418. xouter = x0 + Ro*cos(t)
  1419. youter = y0 + Ro*sin(t)
  1420. lines = [Line((xi,yi),(xo,yo)) for xi, yi, xo, yo in \
  1421. zip(xinner, yinner, xouter, youter)]
  1422. self.shapes = {'inner': inner, 'outer': outer,
  1423. 'spokes': Composition(
  1424. {'spoke%d' % i: lines[i]
  1425. for i in range(len(lines))})}
  1426. class SineWave(Shape):
  1427. def __init__(self, xstart, xstop,
  1428. wavelength, amplitude, mean_level):
  1429. self.xstart = xstart
  1430. self.xstop = xstop
  1431. self.wavelength = wavelength
  1432. self.amplitude = amplitude
  1433. self.mean_level = mean_level
  1434. npoints = (self.xstop - self.xstart)/(self.wavelength/61.0)
  1435. x = linspace(self.xstart, self.xstop, npoints)
  1436. k = 2*pi/self.wavelength # frequency
  1437. y = self.mean_level + self.amplitude*sin(k*x)
  1438. self.shapes = {'waves': Curve(x,y)}
  1439. class Spring(Shape):
  1440. """
  1441. Specify a *vertical* spring, starting at `start` and with `length`
  1442. as total vertical length. In the middle of the spring there are
  1443. `num_windings` circular windings to illustrate the spring. If
  1444. `teeth` is true, the spring windings look like saw teeth,
  1445. otherwise the windings are smooth circles. The parameters `width`
  1446. (total width of spring) and `bar_length` (length of first and last
  1447. bar are given sensible default values if they are not specified
  1448. (these parameters can later be extracted as attributes, see table
  1449. below).
  1450. """
  1451. spring_fraction = 1./2 # fraction of total length occupied by spring
  1452. def __init__(self, start, length, width=None, bar_length=None,
  1453. num_windings=11, teeth=False):
  1454. B = start
  1455. n = num_windings - 1 # n counts teeth intervals
  1456. if n <= 6:
  1457. n = 7
  1458. # n must be odd:
  1459. if n % 2 == 0:
  1460. n = n+1
  1461. L = length
  1462. if width is None:
  1463. w = L/10.
  1464. else:
  1465. w = width/2.0
  1466. s = bar_length
  1467. # [0, x, L-x, L], f = (L-2*x)/L
  1468. # x = L*(1-f)/2.
  1469. # B: start point
  1470. # w: half-width
  1471. # L: total length
  1472. # s: length of first bar
  1473. # P0: start of dashpot (B[0]+s)
  1474. # P1: end of dashpot
  1475. # P2: end point
  1476. shapes = {}
  1477. if s is None:
  1478. f = Spring.spring_fraction
  1479. s = L*(1-f)/2. # start of spring
  1480. self.bar_length = s # record
  1481. self.width = 2*w
  1482. P0 = (B[0], B[1] + s)
  1483. P1 = (B[0], B[1] + L-s)
  1484. P2 = (B[0], B[1] + L)
  1485. if s >= L:
  1486. raise ValueError('length of first bar: %g is larger than total length: %g' % (s, L))
  1487. shapes['bar1'] = Line(B, P0)
  1488. spring_length = L - 2*s
  1489. t = spring_length/n # height increment per winding
  1490. if teeth:
  1491. resolution = 4
  1492. else:
  1493. resolution = 90
  1494. q = linspace(0, n, n*resolution + 1)
  1495. x = P0[0] + w*sin(2*pi*q)
  1496. y = P0[1] + q*t
  1497. shapes['sprial'] = Curve(x, y)
  1498. shapes['bar2'] = Line(P1,P2)
  1499. self.shapes = shapes
  1500. # Dimensions
  1501. start = Text_wArrow('start', (B[0]-1.5*w,B[1]-1.5*w), B)
  1502. width = Distance_wText((B[0]-w, B[1]-3.5*w), (B[0]+w, B[1]-3.5*w),
  1503. 'width')
  1504. length = Distance_wText((B[0]+3*w, B[1]), (B[0]+3*w, B[1]+L),
  1505. 'length')
  1506. num_windings = Text_wArrow('num_windings',
  1507. (B[0]+2*w,P2[1]+w),
  1508. (B[0]+1.2*w, B[1]+L/2.))
  1509. blength1 = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
  1510. 'bar_length',
  1511. text_pos=(P0[0]-7*w, P0[1]+w))
  1512. blength2 = Distance_wText((P1[0]-2*w, P1[1]), (P2[0]-2*w, P2[1]),
  1513. 'bar_length',
  1514. text_pos=(P2[0]-7*w, P2[1]+w))
  1515. dims = {'start': start, 'width': width, 'length': length,
  1516. 'num_windings': num_windings, 'bar_length1': blength1,
  1517. 'bar_length2': blength2}
  1518. self.dimensions = dims
  1519. def geometric_features(self):
  1520. """
  1521. Recorded geometric features:
  1522. ==================== =============================================
  1523. Attribute Description
  1524. ==================== =============================================
  1525. start Start point of spring.
  1526. end End point of spring.
  1527. width Total width of spring.
  1528. bar_length Length of first (and last) bar part.
  1529. ==================== =============================================
  1530. """
  1531. b1 = self.shapes['bar1']
  1532. d = {'start': b1.geometric_features()['start'],
  1533. 'end': self.shapes['bar2'].geometric_features()['end'],
  1534. 'bar_length': self.bar_length,
  1535. 'width': self.width}
  1536. return d
  1537. class Dashpot(Shape):
  1538. """
  1539. Specify a vertical dashpot of height `total_length` and `start` as
  1540. bottom/starting point. The first bar part has length `bar_length`.
  1541. Then comes the dashpot as a rectangular construction of total
  1542. width `width` and height `dashpot_length`. The position of the
  1543. piston inside the rectangular dashpot area is given by
  1544. `piston_pos`, which is the distance between the first bar (given
  1545. by `bar_length`) to the piston.
  1546. If some of `dashpot_length`, `bar_length`, `width` or `piston_pos`
  1547. are not given, suitable default values are calculated. Their
  1548. values can be extracted as keys in the dict returned from
  1549. ``geometric_features``.
  1550. """
  1551. dashpot_fraction = 1./2 # fraction of total_length
  1552. piston_gap_fraction = 1./6 # fraction of width
  1553. piston_thickness_fraction = 1./8 # fraction of dashplot_length
  1554. def __init__(self, start, total_length, bar_length=None,
  1555. width=None, dashpot_length=None, piston_pos=None):
  1556. B = start
  1557. L = total_length
  1558. if width is None:
  1559. w = L/10. # total width 1/5 of length
  1560. else:
  1561. w = width/2.0
  1562. s = bar_length
  1563. # [0, x, L-x, L], f = (L-2*x)/L
  1564. # x = L*(1-f)/2.
  1565. # B: start point
  1566. # w: half-width
  1567. # L: total length
  1568. # s: length of first bar
  1569. # P0: start of dashpot (B[0]+s)
  1570. # P1: end of dashpot
  1571. # P2: end point
  1572. shapes = {}
  1573. # dashpot is P0-P1 in y and width 2*w
  1574. if dashpot_length is None:
  1575. if s is None:
  1576. f = Dashpot.dashpot_fraction
  1577. s = L*(1-f)/2. # default
  1578. P1 = (B[0], B[1]+L-s)
  1579. dashpot_length = f*L
  1580. else:
  1581. if s is None:
  1582. f = 1./2 # the bar lengths are taken as f*dashpot_length
  1583. s = f*dashpot_length # default
  1584. P1 = (B[0], B[1]+s+dashpot_length)
  1585. P0 = (B[0], B[1]+s)
  1586. P2 = (B[0], B[1]+L)
  1587. if P2[1] > P1[1] > P0[1]:
  1588. pass # ok
  1589. else:
  1590. 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]))
  1591. shapes['line start'] = Line(B, P0)
  1592. shapes['pot'] = Curve([P1[0]-w, P0[0]-w, P0[0]+w, P1[0]+w],
  1593. [P1[1], P0[1], P0[1], P1[1]])
  1594. piston_thickness = dashpot_length*Dashpot.piston_thickness_fraction
  1595. if piston_pos is None:
  1596. piston_pos = 1/3.*dashpot_length
  1597. if piston_pos < 0:
  1598. piston_pos = 0
  1599. elif piston_pos > dashpot_length:
  1600. piston_pos = dashpot_length - piston_thickness
  1601. abs_piston_pos = P0[1] + piston_pos
  1602. gap = w*Dashpot.piston_gap_fraction
  1603. shapes['piston'] = Composition(
  1604. {'line': Line(P2, (B[0], abs_piston_pos + piston_thickness)),
  1605. 'rectangle': Rectangle((B[0] - w+gap, abs_piston_pos),
  1606. 2*w-2*gap, piston_thickness),
  1607. })
  1608. shapes['piston']['rectangle'].set_filled_curves(pattern='X')
  1609. self.shapes = shapes
  1610. self.bar_length = s
  1611. self.width = 2*w
  1612. self.piston_pos = piston_pos
  1613. self.dashpot_length = dashpot_length
  1614. # Dimensions
  1615. start = Text_wArrow('start', (B[0]-1.5*w,B[1]-1.5*w), B)
  1616. width = Distance_wText((B[0]-w, B[1]-3.5*w), (B[0]+w, B[1]-3.5*w),
  1617. 'width')
  1618. dplength = Distance_wText((B[0]+2*w, P0[1]), (B[0]+2*w, P1[1]),
  1619. 'dashpot_length', text_pos=(B[0]+w,B[1]-w))
  1620. blength = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
  1621. 'bar_length', text_pos=(B[0]-6*w,P0[1]-w))
  1622. ppos = Distance_wText((B[0]-2*w, P0[1]), (B[0]-2*w, P0[1]+piston_pos),
  1623. 'piston_pos', text_pos=(B[0]-6*w,P0[1]+piston_pos-w))
  1624. tlength = Distance_wText((B[0]+4*w, B[1]), (B[0]+4*w, B[1]+L),
  1625. 'total_length',
  1626. text_pos=(B[0]+4.5*w, B[1]+L-2*w))
  1627. line = Line((B[0]+w, abs_piston_pos), (B[0]+7*w, abs_piston_pos)).set_linestyle('dashed').set_linecolor('black').set_linewidth(1)
  1628. pp = Text('abs_piston_pos', (B[0]+7*w, abs_piston_pos), alignment='left')
  1629. dims = {'start': start, 'width': width, 'dashpot_length': dplength,
  1630. 'bar_length': blength, 'total_length': tlength,
  1631. 'piston_pos': ppos,}
  1632. #'abs_piston_pos': Composition({'line': line, 'text': pp})}
  1633. self.dimensions = dims
  1634. def geometric_features(self):
  1635. """
  1636. Recorded geometric features:
  1637. ==================== =============================================
  1638. Attribute Description
  1639. ==================== =============================================
  1640. start Start point of dashpot.
  1641. end End point of dashpot.
  1642. bar_length Length of first bar (from start to spring).
  1643. dashpot_length Length of dashpot middle part.
  1644. width Total width of dashpot.
  1645. piston_pos Position of piston in dashpot, relative to
  1646. start[1] + bar_length.
  1647. ==================== =============================================
  1648. """
  1649. d = {'start': self.shapes['line start'].geometric_features()['start'],
  1650. 'end': self.shapes['piston']['line'].geometric_features()['start'],
  1651. 'bar_length': self.bar_length,
  1652. 'piston_pos': self.piston_pos,
  1653. 'width': self.width,
  1654. 'dashpot_length': self.dashpot_length,
  1655. }
  1656. return d
  1657. class Wavy(Shape):
  1658. """
  1659. A wavy graph consisting of a user-given main curve y=f(x) with
  1660. additional sinusoidal waves of given (constant) amplitude,
  1661. but varying wavelength (a characteristic wavelength is specified).
  1662. """
  1663. def __init__(self, main_curve, interval, wavelength_of_perturbations,
  1664. amplitude_of_perturbations, smoothness):
  1665. """
  1666. ============================ ====================================
  1667. Name Description
  1668. ============================ ====================================
  1669. main_curve f(x) Python function
  1670. interval interval for main_curve
  1671. wavelength_of_perturbations dominant wavelength perturbed waves
  1672. amplitude_of_perturbations amplitude of perturbed waves
  1673. smoothness in [0, 1]: smooth=0, rough=1
  1674. ============================ ====================================
  1675. """
  1676. xmin, xmax = interval
  1677. L = wavelength_of_perturbations
  1678. k_0 = 2*pi/L # main frequency of waves
  1679. k_p = k_0*0.5
  1680. k_k = k_0/2*smoothness
  1681. A_0 = amplitude_of_perturbations
  1682. A_p = 0.3*A_0
  1683. A_k = k_0/2
  1684. x = linspace(xmin, xmax, 2001)
  1685. def w(x):
  1686. A = A_0 + A_p*sin(A_k*x)
  1687. k = k_0 + k_p*sin(k_k*x)
  1688. y = main_curve(x) + A*sin(k*x)
  1689. return y
  1690. self.shapes = {'wavy': Curve(x, w(x))}
  1691. # Use closure w to define __call__ - then we do not need
  1692. # to store all the parameters A_0, A_k, etc. as attributes
  1693. self.__call__ = w
  1694. class StochasticWavyCurve:
  1695. """
  1696. Precomputed stochastic wavy graphs.
  1697. There are three graphs with different look.
  1698. Curve 0:
  1699. ----------------------------------------------------------------------
  1700. |
  1701. |
  1702. *|
  1703. * |
  1704. * |
  1705. * |
  1706. * |
  1707. * |
  1708. * |
  1709. * |
  1710. * |
  1711. * |
  1712. * |
  1713. * |
  1714. |*
  1715. | *
  1716. | *
  1717. | *
  1718. | *
  1719. | *
  1720. | *
  1721. | *
  1722. | *
  1723. | *
  1724. | *
  1725. | *
  1726. | *
  1727. | *
  1728. | *
  1729. | *
  1730. | *
  1731. | *
  1732. | *
  1733. | *
  1734. | *
  1735. | *
  1736. | *
  1737. | *
  1738. | *
  1739. | *
  1740. | *
  1741. | *
  1742. | *
  1743. | *
  1744. | *
  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. Curve 2:
  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. | *
  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. Curve 2:
  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. | *
  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. See also hplgit.github.io/pysketcher/doc/src/tut/fig-tut/StochasticWavyCurve.png (and .pdf)
  2200. """
  2201. # The curves were generated by the script generate_road_profiles.py and
  2202. # the code below were generated by plot_roads.py. Both scripts are
  2203. # found doc/src/src-bumpy in the repo git@github.com:hplgit/bumpy.git
  2204. def __init__(self, curve_no=0, percentage=100):
  2205. """
  2206. ============= ===================================================
  2207. Argument Explanation
  2208. ============= ===================================================
  2209. curve_no 0, 1, or 2: chooses one out of three shapes.
  2210. percentage The percentage of the defined curve to be used.
  2211. ============= ===================================================
  2212. """
  2213. self._define_curves()
  2214. self.curve_no = curve_no
  2215. m = int(len(self.x)/float(percentage)*100)
  2216. self.shapes = {'wavy': Curve(self.x[:m], self.y[curve_no][:m])}
  2217. def __call__(self, x):
  2218. raise NotImplementedError
  2219. def _define_curves(self):
  2220. 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, ])
  2221. self.y = [None]*3
  2222. 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, ])
  2223. 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, ])
  2224. 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, ])
  2225. # COMPOSITE types:
  2226. # MassSpringForce: Line(horizontal), Spring, Rectangle, Arrow/Line(w/arrow)
  2227. # must be easy to find the tip of the arrow
  2228. # Maybe extra dict: self.name['mass'] = Rectangle object - YES!
  2229. def test_Axis():
  2230. drawing_tool.set_coordinate_system(
  2231. xmin=0, xmax=15, ymin=-7, ymax=8, axis=True,
  2232. instruction_file='tmp_Axis.py')
  2233. # Draw normal x and y axis with origin at (7.5, 2)
  2234. # in the coordinate system of the sketch: [0,15]x[-7,8]
  2235. x_axis = Axis((7.5,2), 5, 'x', rotation_angle=0)
  2236. y_axis = Axis((7.5,2), 5, 'y', rotation_angle=90)
  2237. system = Composition({'x axis': x_axis, 'y axis': y_axis})
  2238. system.draw()
  2239. drawing_tool.display()
  2240. # Rotate this system 40 degrees counter clockwise
  2241. # and draw it with dashed lines
  2242. system.set_linestyle('dashed')
  2243. system.rotate(40, (7.5,2))
  2244. system.draw()
  2245. drawing_tool.display()
  2246. # Rotate this system another 220 degrees and show
  2247. # with dotted lines
  2248. system.set_linestyle('dotted')
  2249. system.rotate(220, (7.5,2))
  2250. system.draw()
  2251. drawing_tool.display()
  2252. drawing_tool.display('Axis')
  2253. drawing_tool.savefig('tmp_Axis')
  2254. print repr(system)
  2255. def test_Distance_wText():
  2256. drawing_tool.set_coordinate_system(
  2257. xmin=0, xmax=10, ymin=0, ymax=6,
  2258. axis=True, instruction_file='tmp_Distance_wText.py')
  2259. fontsize=14
  2260. t = r'$ 2\pi R^2 $' # sample text
  2261. examples = Composition({
  2262. 'a0': Distance_wText((4,5), (8, 5), t, fontsize),
  2263. 'a6': Distance_wText((4,5), (4, 4), t, fontsize),
  2264. 'a1': Distance_wText((0,2), (2, 4.5), t, fontsize),
  2265. 'a2': Distance_wText((0,2), (2, 0), t, fontsize),
  2266. 'a3': Distance_wText((2,4.5), (0, 5.5), t, fontsize),
  2267. 'a4': Distance_wText((8,4), (10, 3), t, fontsize,
  2268. text_spacing=-1./60),
  2269. 'a5': Distance_wText((8,2), (10, 1), t, fontsize,
  2270. text_spacing=-1./40, alignment='right'),
  2271. 'c1': Text_wArrow('text_spacing=-1./60',
  2272. (4, 3.5), (9, 3.2),
  2273. fontsize=10, alignment='left'),
  2274. 'c2': Text_wArrow('text_spacing=-1./40, alignment="right"',
  2275. (4, 0.5), (9, 1.2),
  2276. fontsize=10, alignment='left'),
  2277. })
  2278. examples.draw()
  2279. drawing_tool.display('Distance_wText and text positioning')
  2280. drawing_tool.savefig('tmp_Distance_wText')
  2281. def test_Rectangle():
  2282. L = 3.0
  2283. W = 4.0
  2284. drawing_tool.set_coordinate_system(
  2285. xmin=0, xmax=2*W, ymin=-L/2, ymax=2*L,
  2286. axis=True, instruction_file='tmp_Rectangle.py')
  2287. drawing_tool.set_linecolor('blue')
  2288. drawing_tool.set_grid(True)
  2289. xpos = W/2
  2290. r = Rectangle(lower_left_corner=(xpos,0), width=W, height=L)
  2291. r.draw()
  2292. r.draw_dimensions()
  2293. drawing_tool.display('Rectangle')
  2294. drawing_tool.savefig('tmp_Rectangle')
  2295. def test_Triangle():
  2296. L = 3.0
  2297. W = 4.0
  2298. drawing_tool.set_coordinate_system(
  2299. xmin=0, xmax=2*W, ymin=-L/2, ymax=1.2*L,
  2300. axis=True, instruction_file='tmp_Triangle.py')
  2301. drawing_tool.set_linecolor('blue')
  2302. drawing_tool.set_grid(True)
  2303. xpos = 1
  2304. t = Triangle(p1=(W/2,0), p2=(3*W/2,W/2), p3=(4*W/5.,L))
  2305. t.draw()
  2306. t.draw_dimensions()
  2307. drawing_tool.display('Triangle')
  2308. drawing_tool.savefig('tmp_Triangle')
  2309. def test_Arc():
  2310. L = 4.0
  2311. W = 4.0
  2312. drawing_tool.set_coordinate_system(
  2313. xmin=-W/2, xmax=W, ymin=-L/2, ymax=1.5*L,
  2314. axis=True, instruction_file='tmp_Arc.py')
  2315. drawing_tool.set_linecolor('blue')
  2316. drawing_tool.set_grid(True)
  2317. center = point(0,0)
  2318. radius = L/2
  2319. start_angle = 60
  2320. arc_angle = 45
  2321. a = Arc(center, radius, start_angle, arc_angle)
  2322. a.set_arrow('->')
  2323. a.draw()
  2324. R1 = 1.25*radius
  2325. R2 = 1.5*radius
  2326. R = 2*radius
  2327. a.dimensions = {
  2328. 'start_angle': Arc_wText(
  2329. 'start_angle', center, R1, start_angle=0,
  2330. arc_angle=start_angle, text_spacing=1/10.),
  2331. 'arc_angle': Arc_wText(
  2332. 'arc_angle', center, R2, start_angle=start_angle,
  2333. arc_angle=arc_angle, text_spacing=1/20.),
  2334. 'r=0': Line(center, center +
  2335. point(R*cos(radians(start_angle)),
  2336. R*sin(radians(start_angle)))),
  2337. 'r=start_angle': Line(center, center +
  2338. point(R*cos(radians(start_angle+arc_angle)),
  2339. R*sin(radians(start_angle+arc_angle)))),
  2340. 'r=start+arc_angle': Line(center, center +
  2341. point(R, 0)).set_linestyle('dashed'),
  2342. 'radius': Distance_wText(center, a(0), 'radius', text_spacing=1/40.),
  2343. 'center': Text('center', center-point(radius/10., radius/10.)),
  2344. }
  2345. for dimension in a.dimensions:
  2346. dim = a.dimensions[dimension]
  2347. dim.set_linestyle('dashed')
  2348. dim.set_linewidth(1)
  2349. dim.set_linecolor('black')
  2350. a.draw_dimensions()
  2351. drawing_tool.display('Arc')
  2352. drawing_tool.savefig('tmp_Arc')
  2353. def test_Spring():
  2354. L = 5.0
  2355. W = 2.0
  2356. drawing_tool.set_coordinate_system(
  2357. xmin=0, xmax=7*W, ymin=-L/2, ymax=1.5*L,
  2358. axis=True, instruction_file='tmp_Spring.py')
  2359. drawing_tool.set_linecolor('blue')
  2360. drawing_tool.set_grid(True)
  2361. xpos = W
  2362. s1 = Spring((W,0), L, teeth=True)
  2363. s1_title = Text('Default Spring',
  2364. s1.geometric_features()['end'] + point(0,L/10))
  2365. s1.draw()
  2366. s1_title.draw()
  2367. #s1.draw_dimensions()
  2368. xpos += 3*W
  2369. s2 = Spring(start=(xpos,0), length=L, width=W/2.,
  2370. bar_length=L/6., teeth=False)
  2371. s2.draw()
  2372. s2.draw_dimensions()
  2373. drawing_tool.display('Spring')
  2374. drawing_tool.savefig('tmp_Spring')
  2375. def test_Dashpot():
  2376. L = 5.0
  2377. W = 2.0
  2378. xpos = 0
  2379. drawing_tool.set_coordinate_system(
  2380. xmin=xpos, xmax=xpos+5.5*W, ymin=-L/2, ymax=1.5*L,
  2381. axis=True, instruction_file='tmp_Dashpot.py')
  2382. drawing_tool.set_linecolor('blue')
  2383. drawing_tool.set_grid(True)
  2384. # Default (simple) dashpot
  2385. xpos = 1.5
  2386. d1 = Dashpot(start=(xpos,0), total_length=L)
  2387. d1_title = Text('Dashpot (default)',
  2388. d1.geometric_features()['end'] + point(0,L/10))
  2389. d1.draw()
  2390. d1_title.draw()
  2391. # Dashpot for animation with fixed bar_length, dashpot_length and
  2392. # prescribed piston_pos
  2393. xpos += 2.5*W
  2394. d2 = Dashpot(start=(xpos,0), total_length=1.2*L, width=W/2,
  2395. bar_length=W, dashpot_length=L/2, piston_pos=2*W)
  2396. d2.draw()
  2397. d2.draw_dimensions()
  2398. drawing_tool.display('Dashpot')
  2399. drawing_tool.savefig('tmp_Dashpot')
  2400. def test_Wavy():
  2401. drawing_tool.set_coordinate_system(xmin=0, xmax=1.5,
  2402. ymin=-0.5, ymax=5,
  2403. axis=True,
  2404. instruction_file='tmp_Wavy.py')
  2405. w = Wavy(main_curve=lambda x: 1 + sin(2*x),
  2406. interval=[0,1.5],
  2407. wavelength_of_perturbations=0.3,
  2408. amplitude_of_perturbations=0.1,
  2409. smoothness=0.05)
  2410. w.draw()
  2411. drawing_tool.display('Wavy')
  2412. drawing_tool.savefig('tmp_Wavy')
  2413. def diff_files(files1, files2, mode='HTML'):
  2414. import difflib, time
  2415. n = 3
  2416. for fromfile, tofile in zip(files1, files2):
  2417. fromdate = time.ctime(os.stat(fromfile).st_mtime)
  2418. todate = time.ctime(os.stat(tofile).st_mtime)
  2419. fromlines = open(fromfile, 'U').readlines()
  2420. tolines = open(tofile, 'U').readlines()
  2421. diff_html = difflib.HtmlDiff().\
  2422. make_file(fromlines,tolines,
  2423. fromfile,tofile,context=True,numlines=n)
  2424. diff_plain = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
  2425. filename_plain = fromfile + '.diff.txt'
  2426. filename_html = fromfile + '.diff.html'
  2427. if os.path.isfile(filename_plain):
  2428. os.remove(filename_plain)
  2429. if os.path.isfile(filename_html):
  2430. os.remove(filename_html)
  2431. f = open(filename_plain, 'w')
  2432. f.writelines(diff_plain)
  2433. f.close()
  2434. size = os.path.getsize(filename_plain)
  2435. if size > 4:
  2436. print 'found differences:', fromfile, tofile
  2437. f = open(filename_html, 'w')
  2438. f.writelines(diff_html)
  2439. f.close()
  2440. def test_test():
  2441. os.chdir('test')
  2442. funcs = [name for name in globals() if name.startswith('test_') and callable(globals()[name])]
  2443. funcs.remove('test_test')
  2444. new_files = []
  2445. res_files = []
  2446. for func in funcs:
  2447. mplfile = func.replace('test_', 'tmp_') + '.py'
  2448. #exec(func + '()')
  2449. new_files.append(mplfile)
  2450. resfile = mplfile.replace('tmp_', 'res_')
  2451. res_files.append(resfile)
  2452. diff_files(new_files, res_files)
  2453. def _test1():
  2454. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2455. l1 = Line((0,0), (1,1))
  2456. l1.draw()
  2457. input(': ')
  2458. c1 = Circle((5,2), 1)
  2459. c2 = Circle((6,2), 1)
  2460. w1 = Wheel((7,2), 1)
  2461. c1.draw()
  2462. c2.draw()
  2463. w1.draw()
  2464. hardcopy()
  2465. display() # show the plot
  2466. def _test2():
  2467. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2468. l1 = Line((0,0), (1,1))
  2469. l1.draw()
  2470. input(': ')
  2471. c1 = Circle((5,2), 1)
  2472. c2 = Circle((6,2), 1)
  2473. w1 = Wheel((7,2), 1)
  2474. filled_curves(True)
  2475. set_linecolor('blue')
  2476. c1.draw()
  2477. set_linecolor('aqua')
  2478. c2.draw()
  2479. filled_curves(False)
  2480. set_linecolor('red')
  2481. w1.draw()
  2482. hardcopy()
  2483. display() # show the plot
  2484. def _test3():
  2485. """Test example from the book."""
  2486. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2487. l1 = Line(start=(0,0), stop=(1,1)) # define line
  2488. l1.draw() # make plot data
  2489. r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
  2490. r1.draw()
  2491. Circle(center=(5,7), radius=1).draw()
  2492. Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7).draw()
  2493. hardcopy()
  2494. display()
  2495. def _test4():
  2496. """Second example from the book."""
  2497. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2498. r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
  2499. c1 = Circle(center=(5,7), radius=1)
  2500. w1 = Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7)
  2501. c2 = Circle(center=(7,7), radius=1)
  2502. filled_curves(True)
  2503. c1.draw()
  2504. set_linecolor('blue')
  2505. r1.draw()
  2506. set_linecolor('aqua')
  2507. c2.draw()
  2508. # Add thick aqua line around rectangle:
  2509. filled_curves(False)
  2510. set_linewidth(4)
  2511. r1.draw()
  2512. set_linecolor('red')
  2513. # Draw wheel with thick lines:
  2514. w1.draw()
  2515. hardcopy('tmp_colors')
  2516. display()
  2517. def _test5():
  2518. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2519. c = 6. # center point of box
  2520. w = 2. # size of box
  2521. L = 3
  2522. r1 = Rectangle((c-w/2, c-w/2), w, w)
  2523. l1 = Line((c,c-w/2), (c,c-w/2-L))
  2524. linecolor('blue')
  2525. filled_curves(True)
  2526. r1.draw()
  2527. linecolor('aqua')
  2528. filled_curves(False)
  2529. l1.draw()
  2530. hardcopy()
  2531. display() # show the plot
  2532. def rolling_wheel(total_rotation_angle):
  2533. """Animation of a rotating wheel."""
  2534. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2535. import time
  2536. center = (6,2)
  2537. radius = 2.0
  2538. angle = 2.0
  2539. pngfiles = []
  2540. w1 = Wheel(center=center, radius=radius, inner_radius=0.5, nlines=7)
  2541. for i in range(int(total_rotation_angle/angle)):
  2542. w1.draw()
  2543. print 'XXXX BIG PROBLEM WITH ANIMATE!!!'
  2544. display()
  2545. filename = 'tmp_%03d' % i
  2546. pngfiles.append(filename + '.png')
  2547. hardcopy(filename)
  2548. time.sleep(0.3) # pause
  2549. L = radius*angle*pi/180 # translation = arc length
  2550. w1.rotate(angle, center)
  2551. w1.translate((-L, 0))
  2552. center = (center[0] - L, center[1])
  2553. erase()
  2554. cmd = 'convert -delay 50 -loop 1000 %s tmp_movie.gif' \
  2555. % (' '.join(pngfiles))
  2556. print 'converting PNG files to animated GIF:\n', cmd
  2557. import commands
  2558. failure, output = commands.getstatusoutput(cmd)
  2559. if failure: print 'Could not run', cmd
  2560. if __name__ == '__main__':
  2561. #rolling_wheel(40)
  2562. #_test1()
  2563. #_test3()
  2564. funcs = [
  2565. #test_Axis,
  2566. test_inclined_plane,
  2567. ]
  2568. for func in funcs:
  2569. func()
  2570. raw_input('Type Return: ')