shapes.py 143 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123
  1. from __future__ import division
  2. from __future__ import unicode_literals
  3. from __future__ import print_function
  4. from __future__ import absolute_import
  5. #from future import standard_library
  6. #standard_library.install_aliases()
  7. from builtins import input
  8. from builtins import zip
  9. from builtins import str
  10. from builtins import range
  11. from builtins import *
  12. from builtins import object
  13. from numpy import linspace, sin, cos, pi, array, asarray, ndarray, sqrt, abs
  14. import pprint, copy, glob, os
  15. from math import radians
  16. from io import BytesIO
  17. from ruamel.yaml import YAML
  18. from PIL import Image
  19. import ast
  20. from collections import *
  21. import requests
  22. from .MatplotlibDraw import MatplotlibDraw
  23. drawing_tool = MatplotlibDraw()
  24. class Sketch():
  25. """
  26. Yaml to Pysketcher native features implementation
  27. """
  28. def __init__(self, container, name="unknown"):
  29. """
  30. create a Sketch class instance providing the holding container
  31. """
  32. self.sketch = OrderedDict([('name',name),('parts',[])]) # repository used to collect all sketch parts
  33. self.container = container # save the container reference as an class instance variable
  34. @staticmethod
  35. def matplotlib2SVG():
  36. """
  37. Save sketch as a svg string
  38. """
  39. f = BytesIO()
  40. drawing_tool.mpl.savefig(f, format="svg")
  41. return f.getvalue()
  42. @staticmethod
  43. def matplotlib2PNG():
  44. """
  45. Save sketch as a python .png image
  46. """
  47. f = BytesIO()
  48. drawing_tool.mpl.gcf().canvas.print_png(f)
  49. img = Image.open(f)
  50. return img
  51. def sVe(self, key, expression, sketchpart):
  52. """
  53. sVe: Validate sketch part expression
  54. given an expression from a sketch part, check if valid or not
  55. provides a string feedback if error else return 1
  56. """
  57. try:
  58. root = ast.parse(expression)
  59. except Exception as e:
  60. return f"{sketchpart}/{key}: '''{expression}''' parse error {str(e)}"
  61. names = {node.id for node in ast.walk(root) if isinstance(node, ast.Name)}
  62. for name in names:
  63. if name not in self.container:
  64. return f"{sketchpart}/{key}: {name} in {expression} is not defined"
  65. return 1
  66. def url2Sketch(self, url):
  67. """
  68. get a sketch given an url
  69. """
  70. r = requests.get(url) #, auth=('user', 'pass'))
  71. if r.status_code == 200:
  72. sketchstring = r.text
  73. return self.string2Sketch(sketchstring)
  74. return False
  75. def sketch2File(self, filePath):
  76. """
  77. dump a sketch to file
  78. """
  79. yaml = YAML()
  80. file = open(filePath,"w")
  81. yaml.dump(self.sketch,file)
  82. file.close()
  83. def file2Sketch(self,filePath):
  84. """
  85. load a sketch from file
  86. """
  87. file = open(filePath, "r")
  88. sketchstring = file.read()
  89. file.close()
  90. return self.string2Sketch(sketchstring)
  91. def sketch2String(self):
  92. """
  93. dump sketch as a string
  94. """
  95. yaml = YAML()
  96. f = BytesIO()
  97. yaml.dump(self.sketch,f)
  98. return f.getvalue()
  99. def string2Sketch(self,sketchstring):
  100. """
  101. load a sketch from string
  102. """
  103. yaml = YAML()
  104. sketch = yaml.load(sketchstring)
  105. self.sketch = OrderedDict([('name',sketch['name']),('parts',[])])
  106. for part in sketch['parts']:
  107. self.sketch['parts'].append(part)
  108. if not self.add(part['name'],part['shapes']):
  109. return False
  110. return True
  111. def append(self, sketchpart):
  112. """
  113. Append and Parse a string sketch part into a sketch and its associated container.
  114. A sketch is just the concatenation of all the sketch parts
  115. name specifices the 'name' of the sketch part
  116. A container is a name space which holds
  117. - all the libraries references needed to create pysketcher shapes
  118. - all the variable providing shapes dimensions or position
  119. - all the shapes needed to create the shapes it defines
  120. """
  121. yaml = YAML()
  122. psketch = yaml.load(sketchpart)
  123. self.sketch['parts'].append(psketch)
  124. sketch_name = psketch["name"]
  125. gwd = psketch['shapes']
  126. return self.add(sketch_name, gwd)
  127. def refresh(self, partname):
  128. exec(self.container['formulas'][partname], self.container)
  129. def normalize(self, input):
  130. # print(input," ",type(input))
  131. t = str(type(input))
  132. if t == "<class 'str'>" or t == "<class 'ruamel.yaml.scalarstring.LiteralScalarString'>":
  133. return "".join([s.strip() for s in input.split("\n")])
  134. else:
  135. return input
  136. def add(self, sketch_name, gwd):
  137. """
  138. actual append work common to various different calls
  139. """
  140. for _k in list(gwd.keys()):
  141. if _k == "stop":
  142. return True
  143. _c = gwd[_k]
  144. _t = str(type(_c))
  145. if _k == "libraries":
  146. for l in _c:
  147. _r = self.sVe(_k, l, sketch_name)
  148. if type(_r) == str:
  149. print(_r)
  150. return False
  151. try:
  152. exec(l,self.container)
  153. except Exception as e:
  154. print(f"{_k} / {l} error: {str(e)}")
  155. return False
  156. #print(_k, _c, _t)
  157. if _t == "<class 'ruamel.yaml.scalarfloat.ScalarFloat'>" or \
  158. _t == "<class 'str'>" or _t == "<class 'int'>" or _t == "<class 'ruamel.yaml.scalarstring.LiteralScalarString'>":
  159. _expression = f"{self.normalize(_c)}".replace("<bslash>","\\")
  160. _formula = f"{_k} = {_expression}"
  161. #print(_formula)
  162. _r = self.sVe(_k, _expression, sketch_name)
  163. if type(_r) == str:
  164. print(_r)
  165. return False
  166. try:
  167. exec(_formula,self.container)
  168. if 'formulas' not in self.container:
  169. self.container['formulas'] = {}
  170. self.container['formulas'][_k] = _formula + "\n"
  171. except Exception as e:
  172. print(f"{_formula} error: {str(e)}")
  173. return False
  174. elif _t == "<class 'ruamel.yaml.comments.CommentedMap'>":
  175. #print(_c)
  176. _keys = list(_c.keys())
  177. #print(_keys)
  178. if 'formula' in _keys:
  179. _expression = f"{self.normalize(_c['formula'])}".replace("<bslash>","\\")
  180. _formula = f"{_k} = {_expression}"
  181. #print(_formula)
  182. _r = self.sVe(_k, _expression, sketch_name)
  183. if type(_r) == str:
  184. print(_r)
  185. return False
  186. try:
  187. exec(_formula,self.container)
  188. if 'formulas' not in self.container:
  189. self.container['formulas'] = {}
  190. self.container['formulas'][_k] = _formula +"\n"
  191. except Exception as e:
  192. print(f"{_formula} error: {str(e)}")
  193. return False
  194. # if the new object is a shape and has the sketch name, set this shape name as the sketch name
  195. if issubclass(type(self.container[_k]), Shape):
  196. if _k == sketch_name:
  197. self.container[_k].set_name(sketch_name)
  198. if 'style' in _keys:
  199. for _style in _c["style"]:
  200. # x_const.set_linestyle('dotted')
  201. _param = _c["style"][_style]
  202. __t = str(type(_param))
  203. #print(__t)
  204. if __t == "<class 'int'>":
  205. if _style == 'shadow':
  206. _style = f"{_k}.set_{_style}(pixel_displacement={_param})"
  207. else:
  208. _style = f"{_k}.set_{_style}({_param})"
  209. try:
  210. exec(_style,self.container)
  211. self.container['formulas'][_k] = self.container['formulas'][_k] + _style + "\n"
  212. except Exception as e:
  213. print(f"{_style} error: {str(e)}")
  214. return False
  215. else:
  216. if 'filled_curves' == _style:
  217. if 'color' in list(_param.keys()):
  218. _style = f"{_k}.set_{_style}(color='{_param['color']}')"
  219. try:
  220. exec(_style,self.container)
  221. self.container['formulas'][_k] = self.container['formulas'][_k] + _style + "\n"
  222. except Exception as e:
  223. print(f"{_style} error: {str(e)}")
  224. return False
  225. if 'pattern' in list(_param.keys()):
  226. _style = f"{_k}.set_{_style}(pattern='{_param['pattern']}')"
  227. try:
  228. exec(_style,self.container)
  229. self.container['formulas'][_k] = self.container['formulas'][_k] + _style + "\n"
  230. except Exception as e:
  231. print(f"{_style} error: {str(e)}")
  232. return False
  233. else:
  234. _style = f"{_k}.set_{_style}('{_param}')"
  235. try:
  236. exec(_style,self.container)
  237. self.container['formulas'][_k] = self.container['formulas'][_k] + _style + "\n"
  238. except Exception as e:
  239. print(f"{_style} error: {str(e)}")
  240. return False
  241. #print(_style)
  242. if 'transform' in _keys:
  243. #print(_c['transform'])
  244. if str(type(_c['transform'])) == "<class 'str'>":
  245. _t = f"{_k}.{self.normalize(_c['transform'])}"
  246. #print(_t)
  247. _r = self.sVe(_k, _formula, sketch_name)
  248. if type(_r) == str:
  249. print(_r)
  250. return False
  251. try:
  252. exec(_t,self.container)
  253. self.container['formulas'][_k] = self.container['formulas'][_k] + _t + "\n"
  254. except Exception as e:
  255. print(f"{_t} error: {str(e)}")
  256. return False
  257. else:
  258. for _transform in _c["transform"]:
  259. # x_const.rotate(-theta, contact)
  260. _t = f"{_k}.{self.normalize(_transform)}"
  261. #print(_t)
  262. _r = self.sVe(_k, _t, sketch_name)
  263. if type(_r) == str:
  264. print(_r)
  265. return False
  266. try:
  267. exec(_t,self.container)
  268. self.container['formulas'][_k] = self.container['formulas'][_k] + _t + "\n"
  269. except Exception as e:
  270. print(f"{_t} error: {str(e)}")
  271. return False
  272. if "action" in _keys:
  273. _action = self.normalize(_c["action"])
  274. #print(_action)
  275. _r = self.sVe(_k, _action, sketch_name)
  276. if type(_r) == str:
  277. print(_r)
  278. return False
  279. try:
  280. exec(_action,self.container)
  281. except Exception as e:
  282. print(f"{_k}/{_action} error: {str(e)}")
  283. return False
  284. return True
  285. def point(x, y, check_inside=False):
  286. for obj, name in zip([x, y], ['x', 'y']):
  287. if isinstance(obj, (float,int)):
  288. pass
  289. elif isinstance(obj, ndarray):
  290. if obj.size == 1:
  291. pass
  292. else:
  293. raise TypeError('%s=%s of type %d has length=%d > 1' %
  294. (name, obj, type(obj), obj.size))
  295. else:
  296. raise TypeError('%s=%s is of wrong type %d' %
  297. (name, obj, type(obj)))
  298. if check_inside:
  299. ok, msg = drawing_tool.inside((x,y), exception=True)
  300. if not ok:
  301. print(msg)
  302. return array((x, y), dtype=float)
  303. def distance(p1, p2):
  304. p1 = arr2D(p1); p2 = arr2D(p2)
  305. d = p2 - p1
  306. return sqrt(d[0]**2 + d[1]**2)
  307. def unit_vec(x, y=None):
  308. """Return unit vector of the vector (x,y), or just x if x is a 2D point."""
  309. if isinstance(x, (float,int)) and isinstance(y, (float,int)):
  310. x = point(x, y)
  311. elif isinstance(x, (list,tuple,ndarray)) and y is None:
  312. return arr2D(x)/sqrt(x[0]**2 + x[1]**2)
  313. else:
  314. raise TypeError('x=%s is %s, must be float or ndarray 2D point' %
  315. (x, type(x)))
  316. def arr2D(x, check_inside=False):
  317. if isinstance(x, (tuple,list,ndarray)):
  318. if len(x) == 2:
  319. pass
  320. else:
  321. raise ValueError('x=%s has length %d, not 2' % (x, len(x)))
  322. else:
  323. if isinstance(x, (Point)):
  324. x = (x.x,x.y)
  325. else:
  326. raise TypeError('x=%s must be list/tuple/ndarray, not %s' %
  327. (x, type(x)))
  328. if check_inside:
  329. ok, msg = drawing_tool.inside(x, exception=True)
  330. if not ok:
  331. print(msg)
  332. return asarray(x, dtype=float)
  333. def _is_sequence(seq, length=None,
  334. can_be_None=False, error_message=True):
  335. if can_be_None:
  336. legal_types = (list,tuple,ndarray,None)
  337. else:
  338. legal_types = (list,tuple,ndarray)
  339. if isinstance(seq, legal_types):
  340. if length is not None:
  341. if length == len(seq):
  342. return True
  343. elif error_message:
  344. raise TypeError('sequence %s is not a sequence but %s; must be %s of length %d' %
  345. (str(seq), type(seq),
  346. ', '.join([str(t) for t in legal_types]),
  347. len(seq)))
  348. else:
  349. return False
  350. else:
  351. return True
  352. elif error_message:
  353. raise TypeError('sequence %s is not a sequence but %s, %s; must be %s' %
  354. (str(seq), seq.__class__.__name__, type(seq),
  355. ','.join([str(t)[5:-1] for t in legal_types])))
  356. else:
  357. return False
  358. def is_sequence(*sequences, **kwargs):
  359. length = kwargs.get('length', 2)
  360. can_be_None = kwargs.get('can_be_None', False)
  361. error_message = kwargs.get('error_message', True)
  362. check_inside = kwargs.get('check_inside', False)
  363. for x in sequences:
  364. _is_sequence(x, length=length, can_be_None=can_be_None,
  365. error_message=error_message)
  366. if check_inside:
  367. ok, msg = drawing_tool.inside(x, exception=True)
  368. if not ok:
  369. print(msg)
  370. def animate(fig, time_points, action, moviefiles=False,
  371. pause_per_frame=0.5, show_screen_graphics=True,
  372. title=None,
  373. **action_kwargs):
  374. if moviefiles:
  375. # Clean up old frame files
  376. framefilestem = 'tmp_frame_'
  377. framefiles = glob.glob('%s*.png' % framefilestem)
  378. for framefile in framefiles:
  379. os.remove(framefile)
  380. for n, t in enumerate(time_points):
  381. drawing_tool.erase()
  382. action(t, fig, **action_kwargs)
  383. #could demand returning fig, but in-place modifications
  384. #are done anyway
  385. #fig = action(t, fig)
  386. #if fig is None:
  387. # raise TypeError(
  388. # 'animate: action returns None, not fig\n'
  389. # '(a Shape object with the whole figure)')
  390. fig.draw()
  391. drawing_tool.display(title=title, show=show_screen_graphics)
  392. if moviefiles:
  393. drawing_tool.savefig('%s%04d.png' % (framefilestem, n),
  394. crop=False)
  395. if moviefiles:
  396. return '%s%%04d.png' % framefilestem
  397. """
  398. def save():
  399. os.system("ffmpeg -r 1 -i img%01d.png -vcodec mpeg4 -y movie.mp4")
  400. """
  401. class Shape(object):
  402. """
  403. Superclass for drawing different geometric shapes.
  404. Subclasses define shapes, but drawing, rotation, translation,
  405. etc. are done in generic functions in this superclass.
  406. """
  407. def __init__(self):
  408. """
  409. Never to be called from subclasses.
  410. """
  411. raise NotImplementedError(
  412. 'class %s must implement __init__,\nwhich defines '
  413. 'self.shapes as a dict (or list) of Shape objects\n'
  414. 'Do not call Shape.__init__!' % \
  415. self.__class__.__name__)
  416. def set_name(self, name):
  417. self.name = name
  418. return self
  419. def get_name(self):
  420. return self.name if hasattr(self, 'name') else 'no_name'
  421. def __iter__(self):
  422. # We iterate over self.shapes many places, and will
  423. # get here if self.shapes is just a Shape object and
  424. # not the assumed dict/list.
  425. print('Warning: class %s does not define self.shapes\n'\
  426. 'as a dict of Shape objects')
  427. return [self] # Make the iteration work
  428. def copy(self):
  429. return copy.deepcopy(self)
  430. def __getitem__(self, name):
  431. """
  432. Allow indexing like::
  433. obj1['name1']['name2']
  434. all the way down to ``Curve`` or ``Point`` (``Text``)
  435. objects.
  436. """
  437. if hasattr(self, 'shapes'):
  438. if name in self.shapes:
  439. return self.shapes[name]
  440. else:
  441. for shape in self.shapes:
  442. if isinstance(self.shapes[shape], (Curve,Point)):
  443. # Indexing of Curve/Point/Text is not possible
  444. raise TypeError(
  445. 'Index "%s" (%s) is illegal' %
  446. (name, self.__class__.__name__))
  447. return self.shapes[shape][name]
  448. else:
  449. raise Exception('This is a bug in __getitem__')
  450. def __setitem__(self, name, value):
  451. """
  452. Allow assignment like::
  453. obj1['name1']['name2'] = value
  454. all the way down to ``Curve`` or ``Point`` (``Text``)
  455. objects.
  456. """
  457. if hasattr(self, 'shapes'):
  458. self.shapes[name] = value
  459. else:
  460. raise Exception('Cannot assign')
  461. def _for_all_shapes(self, func, *args, **kwargs):
  462. verbose = kwargs.get('verbose', 0)
  463. if not hasattr(self, 'shapes'):
  464. # When self.shapes is lacking, we either come to
  465. # a special implementation of func or we come here
  466. # because Shape.func is just inherited. This is
  467. # an error if the class is not Curve or Point
  468. if isinstance(self, (Curve, Point)):
  469. return # ok: no shapes, but object is a curve or point end leaf
  470. else:
  471. raise AttributeError('class %s has no shapes attribute!' %
  472. self.__class__.__name__)
  473. is_dict = True if isinstance(self.shapes, dict) else False
  474. for k, shape in enumerate(self.shapes):
  475. if is_dict:
  476. shape_name = shape
  477. shape = self.shapes[shape]
  478. else:
  479. shape_name = k # use index as name if list (not dict)
  480. if not isinstance(shape, Shape):
  481. if isinstance(shape, dict):
  482. raise TypeError(
  483. 'class %s has a self.shapes member "%s" that is just\n'
  484. 'a plain dictionary,\n%s\n'
  485. 'Did you mean to embed this dict in a Composition\n'
  486. 'object?' % (self.__class__.__name__, shape_name,
  487. str(shape)))
  488. elif isinstance(shape, (list,tuple)):
  489. raise TypeError(
  490. 'class %s has self.shapes member "%s" containing\n'
  491. 'a %s object %s,\n'
  492. 'Did you mean to embed this list in a Composition\n'
  493. 'object?' % (self.__class__.__name__, shape_name,
  494. type(shape), str(shape)))
  495. elif shape is None:
  496. raise TypeError(
  497. 'class %s has a self.shapes member "%s" that is None.\n'
  498. 'Some variable name is wrong, or some function\n'
  499. 'did not return the right object...' \
  500. % (self.__class__.__name__, shape_name))
  501. else:
  502. raise TypeError(
  503. 'class %s has a self.shapes member "%s" of %s which '
  504. 'is not a Shape object\n%s' %
  505. (self.__class__.__name__, shape_name, type(shape),
  506. pprint.pformat(self.shapes)))
  507. if isinstance(shape, Curve):
  508. shape.name = shape_name
  509. if verbose > 0:
  510. print('calling %s.%s' % (shape_name, func))
  511. getattr(shape, func)(*args, **kwargs)
  512. def draw(self, verbose=0):
  513. self._for_all_shapes('draw', verbose=verbose)
  514. return self
  515. def draw_dimensions(self):
  516. if hasattr(self, 'dimensions'):
  517. for shape in self.dimensions:
  518. self.dimensions[shape].draw()
  519. return self
  520. else:
  521. #raise AttributeError('no self.dimensions dict for defining dimensions of class %s' % self.__classname__.__name__)
  522. return self
  523. def rotate(self, angle, center):
  524. is_sequence(center, length=2)
  525. self._for_all_shapes('rotate', angle, center)
  526. return self
  527. def translate(self, vec):
  528. is_sequence(vec, length=2)
  529. self._for_all_shapes('translate', vec)
  530. return self
  531. def scale(self, factor):
  532. self._for_all_shapes('scale', factor)
  533. return self
  534. def deform(self, displacement_function):
  535. self._for_all_shapes('deform', displacement_function)
  536. return self
  537. def minmax_coordinates(self, minmax=None):
  538. if minmax is None:
  539. minmax = {'xmin': 1E+20, 'xmax': -1E+20,
  540. 'ymin': 1E+20, 'ymax': -1E+20}
  541. self._for_all_shapes('minmax_coordinates', minmax)
  542. return minmax
  543. def recurse(self, name, indent=0):
  544. if not isinstance(self.shapes, dict):
  545. raise TypeError('recurse works only with dict self.shape, not %s' %
  546. type(self.shapes))
  547. space = ' '*indent
  548. print(space, '%s: %s.shapes has entries' % \
  549. (self.__class__.__name__, name), \
  550. str(list(self.shapes.keys()))[1:-1])
  551. for shape in self.shapes:
  552. print(space, end=' ')
  553. print('call %s.shapes["%s"].recurse("%s", %d)' % \
  554. (name, shape, shape, indent+2))
  555. self.shapes[shape].recurse(shape, indent+2)
  556. def graphviz_dot(self, name, classname=True):
  557. if not isinstance(self.shapes, dict):
  558. raise TypeError('recurse works only with dict self.shape, not %s' %
  559. type(self.shapes))
  560. dotfile = name + '.dot'
  561. pngfile = name + '.png'
  562. if classname:
  563. name = r"%s:\n%s" % (self.__class__.__name__, name)
  564. couplings = self._object_couplings(name, classname=classname)
  565. # Insert counter for similar names
  566. from collections import defaultdict
  567. count = defaultdict(lambda: 0)
  568. couplings2 = []
  569. for i in range(len(couplings)):
  570. parent, child = couplings[i]
  571. count[child] += 1
  572. parent += ' (%d)' % count[parent]
  573. child += ' (%d)' % count[child]
  574. couplings2.append((parent, child))
  575. print('graphviz', couplings, count)
  576. # Remove counter for names there are only one of
  577. for i in range(len(couplings)):
  578. parent2, child2 = couplings2[i]
  579. parent, child = couplings[i]
  580. if count[parent] > 1:
  581. parent = parent2
  582. if count[child] > 1:
  583. child = child2
  584. couplings[i] = (parent, child)
  585. print(couplings)
  586. f = open(dotfile, 'w')
  587. f.write('digraph G {\n')
  588. for parent, child in couplings:
  589. f.write('"%s" -> "%s";\n' % (parent, child))
  590. f.write('}\n')
  591. f.close()
  592. print('Run dot -Tpng -o %s %s' % (pngfile, dotfile))
  593. def _object_couplings(self, parent, couplings=[], classname=True):
  594. """Find all couplings of parent and child objects in a figure."""
  595. for shape in self.shapes:
  596. if classname:
  597. childname = r"%s:\n%s" % \
  598. (self.shapes[shape].__class__.__name__, shape)
  599. else:
  600. childname = shape
  601. couplings.append((parent, childname))
  602. self.shapes[shape]._object_couplings(childname, couplings,
  603. classname)
  604. return couplings
  605. def set_linestyle(self, style):
  606. styles = ('solid', 'dashed', 'dashdot', 'dotted')
  607. if style not in styles:
  608. raise ValueError('%s: style=%s must be in %s' %
  609. (self.__class__.__name__ + '.set_linestyle:',
  610. style, str(styles)))
  611. self._for_all_shapes('set_linestyle', style)
  612. return self
  613. def set_linewidth(self, width):
  614. if not isinstance(width, int) and width >= 0:
  615. raise ValueError('%s: width=%s must be positive integer' %
  616. (self.__class__.__name__ + '.set_linewidth:',
  617. width))
  618. self._for_all_shapes('set_linewidth', width)
  619. return self
  620. def set_linecolor(self, color):
  621. if color in drawing_tool.line_colors:
  622. color = drawing_tool.line_colors[color]
  623. elif color in list(drawing_tool.line_colors.values()):
  624. pass # color is ok
  625. else:
  626. raise ValueError('%s: invalid color "%s", must be in %s' %
  627. (self.__class__.__name__ + '.set_linecolor:',
  628. color, list(drawing_tool.line_colors.keys())))
  629. self._for_all_shapes('set_linecolor', color)
  630. return self
  631. def set_arrow(self, style):
  632. styles = ('->', '<-', '<->')
  633. if not style in styles:
  634. raise ValueError('%s: style=%s must be in %s' %
  635. (self.__class__.__name__ + '.set_arrow:',
  636. style, styles))
  637. self._for_all_shapes('set_arrow', style)
  638. return self
  639. def set_filled_curves(self, color='', pattern=''):
  640. if color in drawing_tool.line_colors:
  641. color = drawing_tool.line_colors[color]
  642. elif color in list(drawing_tool.line_colors.values()):
  643. pass # color is ok
  644. else:
  645. raise ValueError('%s: invalid color "%s", must be in %s' %
  646. (self.__class__.__name__ + '.set_filled_curves:',
  647. color, list(drawing_tool.line_colors.keys())))
  648. self._for_all_shapes('set_filled_curves', color, pattern)
  649. return self
  650. def set_shadow(self, pixel_displacement=3):
  651. self._for_all_shapes('set_shadow', pixel_displacement)
  652. return self
  653. def show_hierarchy(self, indent=0, format='std'):
  654. """Recursive pretty print of hierarchy of objects."""
  655. if not isinstance(self.shapes, dict):
  656. print('cannot print hierarchy when %s.shapes is not a dict' % \
  657. self.__class__.__name__)
  658. s = ''
  659. if format == 'dict':
  660. s += '{'
  661. for shape in self.shapes:
  662. if format == 'dict':
  663. shape_str = repr(shape) + ':'
  664. elif format == 'plain':
  665. shape_str = shape
  666. else:
  667. shape_str = shape + ':'
  668. if format == 'dict' or format == 'plain':
  669. class_str = ''
  670. else:
  671. class_str = ' (%s)' % \
  672. self.shapes[shape].__class__.__name__
  673. s += '\n%s%s%s %s,' % (
  674. ' '*indent,
  675. shape_str,
  676. class_str,
  677. self.shapes[shape].show_hierarchy(indent+4, format))
  678. if format == 'dict':
  679. s += '}'
  680. return s
  681. def __str__(self):
  682. """Display hierarchy with minimum information (just object names)."""
  683. return self.show_hierarchy(format='plain')
  684. def __repr__(self):
  685. """Display hierarchy as a dictionary."""
  686. return self.show_hierarchy(format='dict')
  687. #return pprint.pformat(self.shapes)
  688. class Curve(Shape):
  689. """General curve as a sequence of (x,y) coordinates."""
  690. def __init__(self, x, y):
  691. """
  692. `x`, `y`: arrays holding the coordinates of the curve.
  693. """
  694. self.x = asarray(x, dtype=float)
  695. self.y = asarray(y, dtype=float)
  696. #self.shapes must not be defined in this class
  697. #as self.shapes holds children objects:
  698. #Curve has no children (end leaf of self.shapes tree)
  699. self.linestyle = None
  700. self.linewidth = None
  701. self.linecolor = None
  702. self.fillcolor = None
  703. self.fillpattern = None
  704. self.arrow = None
  705. self.shadow = False
  706. self.name = None # name of object that this Curve represents
  707. def inside_plot_area(self, verbose=True):
  708. """Check that all coordinates are within drawing_tool's area."""
  709. xmin, xmax = self.x.min(), self.x.max()
  710. ymin, ymax = self.y.min(), self.y.max()
  711. t = drawing_tool
  712. inside = True
  713. if not hasattr(t, 'xmin'):
  714. return None # drawing area is not defined
  715. if xmin < t.xmin:
  716. inside = False
  717. if verbose:
  718. print('x_min=%g < plot area x_min=%g' % (xmin, t.xmin))
  719. if xmax > t.xmax:
  720. inside = False
  721. if verbose:
  722. print('x_max=%g > plot area x_max=%g' % (xmax, t.xmax))
  723. if ymin < t.ymin:
  724. inside = False
  725. if verbose:
  726. print('y_min=%g < plot area y_min=%g' % (ymin, t.ymin))
  727. if ymax > t.ymax:
  728. inside = False
  729. if verbose:
  730. print('y_max=%g > plot area y_max=%g' % (ymax, t.ymax))
  731. return inside
  732. def draw(self, verbose=0):
  733. """
  734. Send the curve to the plotting engine. That is, convert
  735. coordinate information in self.x and self.y, together
  736. with optional settings of linestyles, etc., to
  737. plotting commands for the chosen engine.
  738. """
  739. self.inside_plot_area()
  740. drawing_tool.plot_curve(
  741. self.x, self.y,
  742. self.linestyle, self.linewidth, self.linecolor,
  743. self.arrow, self.fillcolor, self.fillpattern,
  744. self.shadow, self.name)
  745. if verbose:
  746. print('drawing Curve object with %d points' % len(self.x))
  747. def rotate(self, angle, center):
  748. """
  749. Rotate all coordinates: `angle` is measured in degrees and
  750. (`x`,`y`) is the "origin" of the rotation.
  751. """
  752. angle = radians(angle)
  753. x, y = center
  754. c = cos(angle); s = sin(angle)
  755. xnew = x + (self.x - x)*c - (self.y - y)*s
  756. ynew = y + (self.x - x)*s + (self.y - y)*c
  757. self.x = xnew
  758. self.y = ynew
  759. return self
  760. def scale(self, factor):
  761. """Scale all coordinates by `factor`: ``x = factor*x``, etc."""
  762. self.x = factor*self.x
  763. self.y = factor*self.y
  764. return self
  765. def translate(self, vec):
  766. """Translate all coordinates by a vector `vec`."""
  767. self.x += vec[0]
  768. self.y += vec[1]
  769. return self
  770. def deform(self, displacement_function):
  771. """Displace all coordinates according to displacement_function(x,y)."""
  772. for i in range(len(self.x)):
  773. self.x[i], self.y[i] = displacement_function(self.x[i], self.y[i])
  774. return self
  775. def minmax_coordinates(self, minmax=None):
  776. if minmax is None:
  777. minmax = {'xmin': [], 'xmax': [], 'ymin': [], 'ymax': []}
  778. minmax['xmin'] = min(self.x.min(), minmax['xmin'])
  779. minmax['xmax'] = max(self.x.max(), minmax['xmax'])
  780. minmax['ymin'] = min(self.y.min(), minmax['ymin'])
  781. minmax['ymax'] = max(self.y.max(), minmax['ymax'])
  782. return minmax
  783. def recurse(self, name, indent=0):
  784. space = ' '*indent
  785. print(space, 'reached "bottom" object %s' % \
  786. self.__class__.__name__)
  787. def _object_couplings(self, parent, couplings=[], classname=True):
  788. return
  789. def set_linecolor(self, color):
  790. self.linecolor = color
  791. return self
  792. def set_linewidth(self, width):
  793. self.linewidth = width
  794. return self
  795. def set_linestyle(self, style):
  796. self.linestyle = style
  797. return self
  798. def set_arrow(self, style=None):
  799. self.arrow = style
  800. return self
  801. def set_filled_curves(self, color='', pattern=''):
  802. self.fillcolor = color
  803. self.fillpattern = pattern
  804. return self
  805. def set_shadow(self, pixel_displacement=3):
  806. self.shadow = pixel_displacement
  807. return self
  808. def show_hierarchy(self, indent=0, format='std'):
  809. if format == 'dict':
  810. return '"%s"' % str(self)
  811. elif format == 'plain':
  812. return ''
  813. else:
  814. return str(self)
  815. def __str__(self):
  816. """Compact pretty print of a Curve object."""
  817. s = '%d (x,y) coords' % self.x.size
  818. inside = self.inside_plot_area(verbose=False)
  819. if inside is None:
  820. pass # no info about the plotting area
  821. elif not inside:
  822. s += ', some coordinates are outside plotting area!\n'
  823. props = ('linecolor', 'linewidth', 'linestyle', 'arrow',
  824. 'fillcolor', 'fillpattern')
  825. for prop in props:
  826. value = getattr(self, prop)
  827. if value is not None:
  828. s += ' %s=%s' % (prop, repr(value))
  829. return s
  830. def __repr__(self):
  831. return str(self)
  832. class Trajectory(Curve):
  833. def __init__(self,points):
  834. self.x = asarray([p.x for p in points], dtype=float)
  835. self.y = asarray([p.y for p in points], dtype=float)
  836. self.linestyle = None
  837. self.linewidth = None
  838. self.linecolor = None
  839. self.fillcolor = None
  840. self.fillpattern = None
  841. self.arrow = None
  842. self.shadow = False
  843. self.name = None # name of object that this Curve represents
  844. class Spline(Shape):
  845. # Note: UnivariateSpline interpolation may not work if
  846. # the x[i] points are far from uniformly spaced
  847. def __init__(self, x, y, degree=3, resolution=501):
  848. from scipy.interpolate import UnivariateSpline
  849. self.smooth = UnivariateSpline(x, y, s=0, k=degree)
  850. self.xcoor = linspace(x[0], x[-1], resolution)
  851. ycoor = self.smooth(self.xcoor)
  852. self.shapes = {'smooth': Curve(self.xcoor, ycoor)}
  853. def geometric_features(self):
  854. s = self.shapes['smooth']
  855. return {'start': point(s.x[0], s.y[0]),
  856. 'end': point(s.x[-1], s.y[-1]),
  857. 'interval': [s.x[0], s.x[-1]]}
  858. def __call__(self, x):
  859. return self.smooth(x)
  860. # Can easily find the derivative and the integral as
  861. # self.smooth.derivative(n=1) and self.smooth.antiderivative()
  862. class SketchyFunc1(Spline):
  863. """
  864. A typical function curve used to illustrate an "arbitrary" function.
  865. """
  866. domain = [1, 6]
  867. def __init__(self, name=None, name_pos='start',
  868. xmin=0, xmax=6, ymin=0, ymax=2):
  869. x = array([0, 2, 3, 4, 5, 6])
  870. y = array([1, 1.8, 1.2, 0.7, 0.8, 0.85])
  871. #y = array([5, 3.5, 3.8, 3, 2.5, 2.4])
  872. # Scale x and y
  873. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  874. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  875. Spline.__init__(self, x, y)
  876. self.shapes['smooth'].set_linecolor('black')
  877. if name is not None:
  878. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  879. class SketchyFunc3(Spline):
  880. """
  881. A typical function curve used to illustrate an "arbitrary" function.
  882. """
  883. domain = [0, 6]
  884. def __init__(self, name=None, name_pos='start',
  885. xmin=0, xmax=6, ymin=0.5, ymax=3.8):
  886. x = array([0, 2, 3, 4, 5, 6])
  887. y = array([0.5, 3.5, 3.8, 2, 2.5, 3.5])
  888. # Scale x and y
  889. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  890. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  891. Spline.__init__(self, x, y)
  892. self.shapes['smooth'].set_linecolor('black')
  893. if name is not None:
  894. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  895. class SketchyFunc4(Spline):
  896. """
  897. A typical function curve used to illustrate an "arbitrary" function.
  898. Can be a companion function to SketchyFunc3.
  899. """
  900. domain = [1, 6]
  901. def __init__(self, name=None, name_pos='start',
  902. xmin=0, xmax=6, ymin=0.5, ymax=1.8):
  903. x = array([0, 2, 3, 4, 5, 6])
  904. y = array([1.5, 1.3, 0.7, 0.5, 0.6, 0.8])
  905. # Scale x and y
  906. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  907. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  908. Spline.__init__(self, x, y)
  909. self.shapes['smooth'].set_linecolor('black')
  910. if name is not None:
  911. self.shapes['name'] = Text(name, self.geometric_features()[name_pos] + point(0,0.1))
  912. class SketchyFunc2(Shape):
  913. """
  914. A typical function curve used to illustrate an "arbitrary" function.
  915. """
  916. domain = [0, 2.25]
  917. def __init__(self, name=None, name_pos='end',
  918. xmin=0, xmax=2.25, ymin=0.046679703125, ymax=1.259375):
  919. a = 0; b = 2.25
  920. resolution = 100
  921. x = linspace(a, b, resolution+1)
  922. f = self # for calling __call__
  923. y = f(x)
  924. # Scale x and y
  925. x = xmin - x.min() + x*(xmax - xmin)/(x.max()-x.min())
  926. y = ymin - y.min() + y*(ymax - ymin)/(y.max()-y.min())
  927. self.shapes = {'smooth': Curve(x, y)}
  928. self.shapes['smooth'].set_linecolor('black')
  929. pos = point(a, f(a)) if name_pos == 'start' else point(b, f(b))
  930. if name is not None:
  931. self.shapes['name'] = Text(name, pos + point(0,0.1))
  932. def __call__(self, x):
  933. return 0.5+x*(2-x)*(0.9-x) # on [0, 2.25]
  934. class Point(Shape):
  935. """A point (x,y) which can be rotated, translated, and scaled."""
  936. def __init__(self, x, y):
  937. self.x, self.y = x, y
  938. #self.shapes is not needed in this class
  939. def __add__(self, other):
  940. if isinstance(other, (list,tuple)):
  941. other = Point(other)
  942. return Point(self.x+other.x, self.y+other.y)
  943. def __sub__(self, other):
  944. if isinstance(other, (list,tuple)):
  945. other = Point(other)
  946. return Point(self.x-other.x, self.y-other.y)
  947. # class Point is an abstract class - only subclasses are useful
  948. # and must implement draw
  949. def draw(self, verbose=0):
  950. raise NotImplementedError(
  951. 'class %s must implement the draw method' %
  952. self.__class__.__name__)
  953. def rotate(self, angle, center):
  954. """Rotate point an `angle` (in degrees) around (`x`,`y`)."""
  955. angle = angle*pi/180
  956. x, y = center
  957. c = cos(angle); s = sin(angle)
  958. xnew = x + (self.x - x)*c - (self.y - y)*s
  959. ynew = y + (self.x - x)*s + (self.y - y)*c
  960. self.x = xnew
  961. self.y = ynew
  962. return self
  963. def scale(self, factor):
  964. """Scale point coordinates by `factor`: ``x = factor*x``, etc."""
  965. self.x = factor*self.x
  966. self.y = factor*self.y
  967. return self
  968. def translate(self, vec):
  969. """Translate point by a vector `vec`."""
  970. self.x += vec[0]
  971. self.y += vec[1]
  972. return self
  973. def deform(self, displacement_function):
  974. """Displace coordinates according to displacement_function(x,y)."""
  975. for i in range(len(self.x)):
  976. self.x, self.y = displacement_function(self.x, self.y)
  977. return self
  978. def minmax_coordinates(self, minmax=None):
  979. if minmax is None:
  980. minmax = {'xmin': [], 'xmax': [], 'ymin': [], 'ymax': []}
  981. minmax['xmin'] = min(self.x, minmax['xmin'])
  982. minmax['xmax'] = max(self.x, minmax['xmax'])
  983. minmax['ymin'] = min(self.y, minmax['ymin'])
  984. minmax['ymax'] = max(self.y, minmax['ymax'])
  985. return minmax
  986. def recurse(self, name, indent=0):
  987. space = ' '*indent
  988. print(space, 'reached "bottom" object %s' % \
  989. self.__class__.__name__)
  990. def _object_couplings(self, parent, couplings=[], classname=True):
  991. return
  992. # No need for set_linecolor etc since self._for_all_shapes, which
  993. # is always called for these functions, makes a test and stops
  994. # calls if self.shapes is missing and the object is Point or Curve
  995. def show_hierarchy(self, indent=0, format='std'):
  996. s = '%s at (%g,%g)' % (self.__class__.__name__, self.x, self.y)
  997. if format == 'dict':
  998. return '"%s"' % s
  999. elif format == 'plain':
  1000. return ''
  1001. else:
  1002. return s
  1003. # no need to store input data as they are invalid after rotations etc.
  1004. class Rectangle(Shape):
  1005. """
  1006. Rectangle specified by the point `lower_left_corner`, `width`,
  1007. and `height`.
  1008. """
  1009. def __init__(self, lower_left_corner, width, height):
  1010. is_sequence(lower_left_corner)
  1011. p = arr2D(lower_left_corner) # short form
  1012. x = [p[0], p[0] + width,
  1013. p[0] + width, p[0], p[0]]
  1014. y = [p[1], p[1], p[1] + height,
  1015. p[1] + height, p[1]]
  1016. self.shapes = {'rectangle': Curve(x,y)}
  1017. # Dimensions
  1018. dims = {
  1019. 'width': Distance_wText(p + point(0, -height/5.),
  1020. p + point(width, -height/5.),
  1021. 'width'),
  1022. 'height': Distance_wText(p + point(width + width/5., 0),
  1023. p + point(width + width/5., height),
  1024. 'height'),
  1025. 'lower_left_corner': Text_wArrow('lower_left_corner',
  1026. p - point(width/5., height/5.), p)
  1027. }
  1028. self.dimensions = dims
  1029. def geometric_features(self):
  1030. """
  1031. Return dictionary with
  1032. ==================== =============================================
  1033. Attribute Description
  1034. ==================== =============================================
  1035. lower_left Lower left corner point.
  1036. upper_left Upper left corner point.
  1037. lower_right Lower right corner point.
  1038. upper_right Upper right corner point.
  1039. lower_mid Middle point on lower side.
  1040. upper_mid Middle point on upper side.
  1041. center Center point
  1042. ==================== =============================================
  1043. """
  1044. r = self.shapes['rectangle']
  1045. d = {'lower_left': point(r.x[0], r.y[0]),
  1046. 'lower_right': point(r.x[1], r.y[1]),
  1047. 'upper_right': point(r.x[2], r.y[2]),
  1048. 'upper_left': point(r.x[3], r.y[3])}
  1049. d['lower_mid'] = 0.5*(d['lower_left'] + d['lower_right'])
  1050. d['upper_mid'] = 0.5*(d['upper_left'] + d['upper_right'])
  1051. d['left_mid'] = 0.5*(d['lower_left'] + d['upper_left'])
  1052. d['right_mid'] = 0.5*(d['lower_right'] + d['upper_right'])
  1053. d['center'] = point(d['lower_mid'][0], d['left_mid'][1])
  1054. return d
  1055. class Triangle(Shape):
  1056. """
  1057. Triangle defined by its three vertices p1, p2, and p3.
  1058. Recorded geometric features:
  1059. ==================== =============================================
  1060. Attribute Description
  1061. ==================== =============================================
  1062. p1, p2, p3 Corners as given to the constructor.
  1063. ==================== =============================================
  1064. """
  1065. def __init__(self, p1, p2, p3):
  1066. is_sequence(p1, p2, p3)
  1067. x = [p1[0], p2[0], p3[0], p1[0]]
  1068. y = [p1[1], p2[1], p3[1], p1[1]]
  1069. self.shapes = {'triangle': Curve(x,y)}
  1070. # Dimensions
  1071. self.dimensions = {'p1': Text('p1', p1),
  1072. 'p2': Text('p2', p2),
  1073. 'p3': Text('p3', p3)}
  1074. def geometric_features(self):
  1075. t = self.shapes['triangle']
  1076. return {'p1': point(t.x[0], t.y[0]),
  1077. 'p2': point(t.x[1], t.y[1]),
  1078. 'p3': point(t.x[2], t.y[2])}
  1079. class Line(Shape):
  1080. def __init__(self, start, end):
  1081. is_sequence(start, end, length=2)
  1082. if isinstance(start, (list,tuple)):
  1083. start = array(start)
  1084. if isinstance(end, (list,tuple)):
  1085. end = array(end)
  1086. if (start == end).all():
  1087. # Introduce a very small perturbation since identical points
  1088. # give drawing error
  1089. end[0] = start[0] + 1E-10
  1090. x = [start[0], end[0]]
  1091. y = [start[1], end[1]]
  1092. self.shapes = {'line': Curve(x, y)}
  1093. def geometric_features(self):
  1094. line = self.shapes['line']
  1095. return {'start': point(line.x[0], line.y[0]),
  1096. 'end': point(line.x[1], line.y[1]),}
  1097. def compute_formulas(self):
  1098. x, y = self.shapes['line'].x, self.shapes['line'].y
  1099. # Define equations for line:
  1100. # y = a*x + b, x = c*y + d
  1101. try:
  1102. self.a = (y[1] - y[0])/(x[1] - x[0])
  1103. self.b = y[0] - self.a*x[0]
  1104. except ZeroDivisionError:
  1105. # Vertical line, y is not a function of x
  1106. self.a = None
  1107. self.b = None
  1108. try:
  1109. if self.a is None:
  1110. self.c = 0
  1111. else:
  1112. self.c = 1/float(self.a)
  1113. if self.b is None:
  1114. self.d = x[1]
  1115. except ZeroDivisionError:
  1116. # Horizontal line, x is not a function of y
  1117. self.c = None
  1118. self.d = None
  1119. def __call__(self, x=None, y=None):
  1120. """Given x, return y on the line, or given y, return x."""
  1121. self.compute_formulas()
  1122. if x is not None and self.a is not None:
  1123. return self.a*x + self.b
  1124. elif y is not None and self.c is not None:
  1125. return self.c*y + self.d
  1126. else:
  1127. raise ValueError(
  1128. 'Line.__call__(x=%s, y=%s) not meaningful' % \
  1129. (x, y))
  1130. def new_interval(self, x=None, y=None):
  1131. """Redefine current Line to cover interval in x or y."""
  1132. if x is not None:
  1133. is_sequence(x, length=2)
  1134. xL, xR = x
  1135. new_line = Line((xL, self(x=xL)), (xR, self(x=xR)))
  1136. elif y is not None:
  1137. is_sequence(y, length=2)
  1138. yL, yR = y
  1139. new_line = Line((xL, self(y=xL)), (xR, self(y=xR)))
  1140. self.shapes['line'] = new_line['line']
  1141. return self
  1142. # First implementation of class Circle
  1143. class Circle(Shape):
  1144. def __init__(self, center, radius, resolution=180):
  1145. self.center, self.radius = center, radius
  1146. self.resolution = resolution
  1147. t = linspace(0, 2*pi, resolution+1)
  1148. x0 = center[0]; y0 = center[1]
  1149. R = radius
  1150. x = x0 + R*cos(t)
  1151. y = y0 + R*sin(t)
  1152. self.shapes = {'circle': Curve(x, y)}
  1153. def __call__(self, theta):
  1154. """
  1155. Return (x, y) point corresponding to angle theta.
  1156. Not valid after a translation, rotation, or scaling.
  1157. """
  1158. return self.center[0] + self.radius*cos(theta), \
  1159. self.center[1] + self.radius*sin(theta)
  1160. class Arc(Shape):
  1161. def __init__(self, center, radius,
  1162. start_angle, arc_angle,
  1163. resolution=180):
  1164. is_sequence(center)
  1165. # Must record some parameters for __call__
  1166. self.center = arr2D(center)
  1167. self.radius = radius
  1168. self.start_angle = radians(start_angle)
  1169. self.arc_angle = radians(arc_angle)
  1170. self.resolution = resolution
  1171. self.setCurve()
  1172. def setCurve(self):
  1173. t = linspace(self.start_angle,
  1174. self.start_angle + self.arc_angle,
  1175. self.resolution+1)
  1176. x0 = self.center[0]; y0 = self.center[1]
  1177. R = self.radius
  1178. x = x0 + R*cos(t)
  1179. y = y0 + R*sin(t)
  1180. self.shapes = {'arc': Curve(x, y)}
  1181. # Cannot set dimensions (Arc_wText recurses into this
  1182. # constructor forever). Set in test_Arc instead.
  1183. def geometric_features(self):
  1184. a = self.shapes['arc']
  1185. m = len(a.x)//2 # mid point in array
  1186. d = {'start': point(a.x[0], a.y[0]),
  1187. 'end': point(a.x[-1], a.y[-1]),
  1188. 'mid': point(a.x[m], a.y[m])}
  1189. return d
  1190. def __call__(self, theta):
  1191. """
  1192. Return (x,y) point at start_angle + theta.
  1193. Not valid after translation, rotation, or scaling.
  1194. """
  1195. theta = radians(theta)
  1196. t = self.start_angle + theta
  1197. x0 = self.center[0]
  1198. y0 = self.center[1]
  1199. R = self.radius
  1200. x = x0 + R*cos(t)
  1201. y = y0 + R*sin(t)
  1202. return (x, y)
  1203. # Alternative for small arcs: Parabola
  1204. class Parabola(Shape):
  1205. def __init__(self, start, mid, stop, resolution=21):
  1206. self.p1, self.p2, self.p3 = start, mid, stop
  1207. # y as function of x? (no point on line x=const?)
  1208. tol = 1E-14
  1209. if abs(self.p1[0] - self.p2[0]) > tol and \
  1210. abs(self.p2[0] - self.p3[0]) > tol and \
  1211. abs(self.p3[0] - self.p1[0]) > tol:
  1212. self.y_of_x = True
  1213. else:
  1214. self.y_of_x = False
  1215. # x as function of y? (no point on line y=const?)
  1216. tol = 1E-14
  1217. if abs(self.p1[1] - self.p2[1]) > tol and \
  1218. abs(self.p2[1] - self.p3[1]) > tol and \
  1219. abs(self.p3[1] - self.p1[1]) > tol:
  1220. self.x_of_y = True
  1221. else:
  1222. self.x_of_y = False
  1223. if self.y_of_x:
  1224. x = linspace(start[0], end[0], resolution)
  1225. y = self(x=x)
  1226. elif self.x_of_y:
  1227. y = linspace(start[1], end[1], resolution)
  1228. x = self(y=y)
  1229. else:
  1230. raise ValueError(
  1231. 'Parabola: two or more points lie on x=const '
  1232. 'or y=const - not allowed')
  1233. self.shapes = {'parabola': Curve(x, y)}
  1234. def __call__(self, x=None, y=None):
  1235. if x is not None and self.y_of_x:
  1236. return self._L2x(self.p1, self.p2)*self.p3[1] + \
  1237. self._L2x(self.p2, self.p3)*self.p1[1] + \
  1238. self._L2x(self.p3, self.p1)*self.p2[1]
  1239. elif y is not None and self.x_of_y:
  1240. return self._L2y(self.p1, self.p2)*self.p3[0] + \
  1241. self._L2y(self.p2, self.p3)*self.p1[0] + \
  1242. self._L2y(self.p3, self.p1)*self.p2[0]
  1243. else:
  1244. raise ValueError(
  1245. 'Parabola.__call__(x=%s, y=%s) not meaningful' % \
  1246. (x, y))
  1247. def _L2x(self, x, pi, pj, pk):
  1248. return (x - pi[0])*(x - pj[0])/((pk[0] - pi[0])*(pk[0] - pj[0]))
  1249. def _L2y(self, y, pi, pj, pk):
  1250. return (y - pi[1])*(y - pj[1])/((pk[1] - pi[1])*(pk[1] - pj[1]))
  1251. class Ellipse(Shape):
  1252. def __init__(self, center, width, height, resolution=180):
  1253. self.center = center
  1254. self.width = width
  1255. self.height = height
  1256. self.resolution = resolution
  1257. t = linspace(0, 2*pi, resolution+1)
  1258. x0 = center[0]; y0 = center[1]
  1259. x = x0 + self.width*cos(t)
  1260. y = y0 + self.height*sin(t)
  1261. self.shapes = {'ellipse': Curve(x, y)}
  1262. def __call__(self, theta):
  1263. """
  1264. Return (x, y) point corresponding to angle theta.
  1265. Not valid after a translation, rotation, or scaling.
  1266. """
  1267. return self.center[0] + self.width*cos(theta), \
  1268. self.center[1] + self.height*sin(theta)
  1269. class Wall(Shape):
  1270. """
  1271. defines an hached box given starting, ending point and thickness, filled with a pattern
  1272. """
  1273. def __init__(self, x, y, thickness, pattern='/', transparent=False):
  1274. from numpy import concatenate
  1275. is_sequence(x, y, length=len(x))
  1276. if isinstance(x[0], (tuple,list,ndarray)):
  1277. # x is list of curves
  1278. x1 = concatenate(x)
  1279. else:
  1280. x1 = asarray(x, float)
  1281. if isinstance(y[0], (tuple,list,ndarray)):
  1282. # x is list of curves
  1283. y1 = concatenate(y)
  1284. else:
  1285. y1 = asarray(y, float)
  1286. self.x1 = x1; self.y1 = y1
  1287. # Displaced curve (according to thickness)
  1288. x2 = x1
  1289. y2 = y1 + thickness
  1290. # Combine x1,y1 with x2,y2 reversed
  1291. x = concatenate((x1, x2[-1::-1]))
  1292. y = concatenate((y1, y2[-1::-1]))
  1293. wall = Curve(x, y)
  1294. wall.set_filled_curves(color='white', pattern=pattern)
  1295. x = [x1[-1]] + x2[-1::-1].tolist() + [x1[0]]
  1296. y = [y1[-1]] + y2[-1::-1].tolist() + [y1[0]]
  1297. self.shapes = {'wall': wall}
  1298. from collections import OrderedDict
  1299. self.shapes = OrderedDict()
  1300. self.shapes['wall'] = wall
  1301. if transparent:
  1302. white_eraser = Curve(x, y)
  1303. white_eraser.set_linecolor('white')
  1304. self.shapes['eraser'] = white_eraser
  1305. def geometric_features(self):
  1306. d = {'start': point(self.x1[0], self.y1[0]),
  1307. 'end': point(self.x1[-1], self.y1[-1])}
  1308. return d
  1309. class Wall2(Shape):
  1310. def __init__(self, x, y, thickness, pattern='/'):
  1311. from numpy import concatenate
  1312. is_sequence(x, y, length=len(x))
  1313. if isinstance(x[0], (tuple,list,ndarray)):
  1314. # x is list of curves
  1315. x1 = concatenate(x)
  1316. else:
  1317. x1 = asarray(x, float)
  1318. if isinstance(y[0], (tuple,list,ndarray)):
  1319. # x is list of curves
  1320. y1 = concatenate(y)
  1321. else:
  1322. y1 = asarray(y, float)
  1323. self.x1 = x1; self.y1 = y1
  1324. # Displaced curve (according to thickness)
  1325. x2 = x1.copy()
  1326. y2 = y1.copy()
  1327. def displace(idx, idx_m, idx_p):
  1328. # Find tangent and normal
  1329. tangent = point(x1[idx_m], y1[idx_m]) - point(x1[idx_p], y1[idx_p])
  1330. tangent = unit_vec(tangent)
  1331. normal = point(tangent[1], -tangent[0])
  1332. # Displace length "thickness" in "positive" normal direction
  1333. displaced_pt = point(x1[idx], y1[idx]) + thickness*normal
  1334. x2[idx], y2[idx] = displaced_pt
  1335. for i in range(1, len(x1)-1):
  1336. displace(i-1, i+1, i) # centered difference for normal comp.
  1337. # One-sided differences at the end points
  1338. i = 0
  1339. displace(i, i+1, i)
  1340. i = len(x1)-1
  1341. displace(i-1, i, i)
  1342. # Combine x1,y1 with x2,y2 reversed
  1343. from numpy import concatenate
  1344. x = concatenate((x1, x2[-1::-1]))
  1345. y = concatenate((y1, y2[-1::-1]))
  1346. wall = Curve(x, y)
  1347. wall.set_filled_curves(color='white', pattern=pattern)
  1348. x = [x1[-1]] + x2[-1::-1].tolist() + [x1[0]]
  1349. y = [y1[-1]] + y2[-1::-1].tolist() + [y1[0]]
  1350. self.shapes['wall'] = wall
  1351. def geometric_features(self):
  1352. d = {'start': point(self.x1[0], self.y1[0]),
  1353. 'end': point(self.x1[-1], self.y1[-1])}
  1354. return d
  1355. class VelocityProfile(Shape):
  1356. def __init__(self, start, height, profile, num_arrows, scaling=1):
  1357. # vx, vy = profile(y)
  1358. shapes = {}
  1359. # Draw left line
  1360. shapes['start line'] = Line(start, (start[0], start[1]+height))
  1361. # Draw velocity arrows
  1362. dy = float(height)/(num_arrows-1)
  1363. x = start[0]
  1364. y = start[1]
  1365. r = profile(y) # Test on return type
  1366. if not isinstance(r, (list,tuple,ndarray)) and len(r) != 2:
  1367. raise TypeError('VelocityProfile constructor: profile(y) function must return velocity vector (vx,vy), not %s' % type(r))
  1368. for i in range(num_arrows):
  1369. y = start[1] + i*dy
  1370. vx, vy = profile(y)
  1371. if abs(vx) < 1E-8:
  1372. continue
  1373. vx *= scaling
  1374. vy *= scaling
  1375. arr = Arrow1((x,y), (x+vx, y+vy), '->')
  1376. shapes['arrow%d' % i] = arr
  1377. # Draw smooth profile
  1378. xs = []
  1379. ys = []
  1380. n = 100
  1381. dy = float(height)/n
  1382. for i in range(n+2):
  1383. y = start[1] + i*dy
  1384. vx, vy = profile(y)
  1385. vx *= scaling
  1386. vy *= scaling
  1387. xs.append(x+vx)
  1388. ys.append(y+vy)
  1389. shapes['smooth curve'] = Curve(xs, ys)
  1390. self.shapes = shapes
  1391. class Arrow1(Shape):
  1392. """Draw a Line with arrow(s)."""
  1393. def __init__(self, start, end, style='->'):
  1394. arrow = Line(start, end)
  1395. arrow.set_arrow(style)
  1396. # Note:
  1397. self.shapes = {'arrow': arrow}
  1398. def geometric_features(self):
  1399. return self.shapes['arrow'].geometric_features()
  1400. class Arrow3(Shape):
  1401. """
  1402. Build a vertical line and arrow head from Line objects.
  1403. Then rotate `rotation_angle`.
  1404. """
  1405. def __init__(self, start, length, rotation_angle=0):
  1406. self.bottom = start
  1407. self.length = length
  1408. self.angle = rotation_angle
  1409. top = (self.bottom[0], self.bottom[1] + self.length)
  1410. main = Line(self.bottom, top)
  1411. #head_length = self.length/8.0
  1412. head_length = drawing_tool.xrange/50.
  1413. head_degrees = radians(30)
  1414. head_left_pt = (top[0] - head_length*sin(head_degrees),
  1415. top[1] - head_length*cos(head_degrees))
  1416. head_right_pt = (top[0] + head_length*sin(head_degrees),
  1417. top[1] - head_length*cos(head_degrees))
  1418. head_left = Line(head_left_pt, top)
  1419. head_right = Line(head_right_pt, top)
  1420. head_left.set_linestyle('solid')
  1421. head_right.set_linestyle('solid')
  1422. self.shapes = {'line': main, 'head left': head_left,
  1423. 'head right': head_right}
  1424. # rotate goes through self.shapes so self.shapes
  1425. # must be initialized first
  1426. self.rotate(rotation_angle, start)
  1427. def geometric_features(self):
  1428. return self.shapes['line'].geometric_features()
  1429. class Cross(Shape):
  1430. """
  1431. Place a cross at the (x,y) point `position`.
  1432. The cross fits in a 0.2 square which center is (x,y).
  1433. the color is black
  1434. the linewidth is 1
  1435. """
  1436. def __init__(self,c):
  1437. l = 0.1
  1438. line1 = Line(c+point(-l,l),c+point(l,-l))
  1439. line2 = Line(c+point(l,l), c+point(-l,-l))
  1440. cross = Composition({'line1': line1, 'line2': line2})
  1441. cross.set_linecolor('black')
  1442. cross.set_linewidth(1)
  1443. self.shapes = {'cross': cross}
  1444. class Text(Point):
  1445. """
  1446. Place `text` at the (x,y) point `position`, with the given
  1447. fontsize (0 indicates that the default fontsize set in drawing_tool
  1448. is to be used). The text is centered around `position` if `alignment` is
  1449. 'center'; if 'left', the text starts at `position`, and if
  1450. 'right', the right and of the text is located at `position`.
  1451. """
  1452. def __init__(self, text, position, alignment='center', fontsize=0,
  1453. bgcolor=None, fgcolor=None, fontfamily=None):
  1454. """
  1455. fontfamily can be (e.g.) 'serif' or 'monospace' (for code!).
  1456. """
  1457. is_sequence(position)
  1458. is_sequence(position, length=2, can_be_None=True)
  1459. self.text = text
  1460. self.position = position
  1461. self.alignment = alignment
  1462. self.fontsize = fontsize
  1463. self.bgcolor = bgcolor
  1464. self.fgcolor = fgcolor
  1465. self.fontfamily = fontfamily
  1466. Point.__init__(self, position[0], position[1])
  1467. #no need for self.shapes here
  1468. def draw(self, verbose=0):
  1469. drawing_tool.text(
  1470. self.text, (self.x, self.y),
  1471. self.alignment, self.fontsize,
  1472. arrow_tip=None, bgcolor=self.bgcolor, fgcolor=self.fgcolor,
  1473. fontfamily=self.fontfamily)
  1474. if verbose > 0:
  1475. print('drawing Text "%s"' % self.text)
  1476. def __str__(self):
  1477. return 'text "%s" at (%g,%g)' % (self.text, self.x, self.y)
  1478. def __repr__(self):
  1479. return repr(str(self))
  1480. class Text_wArrow(Text):
  1481. """
  1482. As class Text, but an arrow is drawn from the mid part of the text
  1483. to some point `arrow_tip`.
  1484. """
  1485. def __init__(self, text, position, arrow_tip,
  1486. alignment='center', fontsize=0):
  1487. is_sequence(arrow_tip, length=2, can_be_None=True)
  1488. is_sequence(position)
  1489. self.arrow_tip = arrow_tip
  1490. Text.__init__(self, text, position, alignment, fontsize)
  1491. def draw(self, verbose=0):
  1492. drawing_tool.text(
  1493. self.text, self.position,
  1494. self.alignment, self.fontsize,
  1495. arrow_tip=self.arrow_tip,
  1496. bgcolor=self.bgcolor, fgcolor=self.fgcolor,
  1497. fontfamily=self.fontfamily)
  1498. if verbose > 0:
  1499. print('drawing Text_wArrow "%s"' % self.text)
  1500. def __str__(self):
  1501. return 'annotation "%s" at (%g,%g) with arrow to (%g,%g)' % \
  1502. (self.text, self.x, self.y,
  1503. self.arrow_tip[0], self.arrow_tip[1])
  1504. def __repr__(self):
  1505. return repr(str(self))
  1506. class Axis(Shape):
  1507. def __init__(self, start, length, label,
  1508. rotation_angle=0, fontsize=0,
  1509. label_spacing=1./45, label_alignment='left'):
  1510. """
  1511. Draw axis from start with `length` to the right
  1512. (x axis). Place label at the end of the arrow tip.
  1513. Then return `rotation_angle` (in degrees).
  1514. The `label_spacing` denotes the space between the label
  1515. and the arrow tip as a fraction of the length of the plot
  1516. in x direction. A tuple can be given to adjust the position
  1517. in both the x and y directions (with one parameter, the
  1518. x position is adjusted).
  1519. With `label_alignment` one can place
  1520. the axis label text such that the arrow tip is to the 'left',
  1521. 'right', or 'center' with respect to the text field.
  1522. The `label_spacing` and `label_alignment`parameters can
  1523. be used to fine-tune the location of the label.
  1524. """
  1525. # Arrow is vertical arrow, make it horizontal
  1526. arrow = Arrow3(start, length, rotation_angle=-90)
  1527. arrow.rotate(rotation_angle, start)
  1528. if isinstance(label_spacing, (list,tuple)) and len(label_spacing) == 2:
  1529. x_spacing = drawing_tool.xrange*label_spacing[0]
  1530. y_spacing = drawing_tool.yrange*label_spacing[1]
  1531. elif isinstance(label_spacing, (int,float)):
  1532. # just x spacing
  1533. x_spacing = drawing_tool.xrange*label_spacing
  1534. y_spacing = 0
  1535. # should increase spacing for downward pointing axis
  1536. label_pos = [start[0] + length + x_spacing, start[1] + y_spacing]
  1537. label = Text(label, position=label_pos, fontsize=fontsize)
  1538. label.rotate(rotation_angle, start)
  1539. self.shapes = {'arrow': arrow, 'label': label}
  1540. def geometric_features(self):
  1541. return self.shapes['arrow'].geometric_features()
  1542. # Maybe Axis3 with label below/above?
  1543. class Force(Arrow1):
  1544. """
  1545. Indication of a force by an arrow and a text (symbol). Draw an
  1546. arrow, starting at `start` and with the tip at `end`. The symbol
  1547. is placed at `text_pos`, which can be 'start', 'end' or the
  1548. coordinates of a point. If 'end' or 'start', the text is placed at
  1549. a distance `text_spacing` times the width of the total plotting
  1550. area away from the specified point.
  1551. """
  1552. def __init__(self, start, end, text, text_spacing=1./60,
  1553. fontsize=0, text_pos='start', text_alignment='center'):
  1554. Arrow1.__init__(self, start, end, style='->')
  1555. if isinstance(text_spacing, (tuple,list)):
  1556. if len(text_spacing) == 2:
  1557. spacing = point(drawing_tool.xrange*text_spacing[0],
  1558. drawing_tool.xrange*text_spacing[1])
  1559. else:
  1560. spacing = drawing_tool.xrange*text_spacing[0]
  1561. else:
  1562. # just a number, this is x spacing
  1563. spacing = drawing_tool.xrange*text_spacing
  1564. start, end = arr2D(start), arr2D(end)
  1565. # Two cases: label at bottom of line or top, need more
  1566. # spacing if bottom
  1567. downward = (end-start)[1] < 0
  1568. upward = not downward # for easy code reading
  1569. if isinstance(text_pos, (str,bytes)):
  1570. if text_pos == 'start':
  1571. spacing_dir = unit_vec(start - end)
  1572. if upward:
  1573. spacing *= 1.7
  1574. if isinstance(spacing, (int, float)):
  1575. text_pos = start + spacing*spacing_dir
  1576. else:
  1577. text_pos = start + spacing
  1578. elif text_pos == 'end':
  1579. spacing_dir = unit_vec(end - start)
  1580. if downward:
  1581. spacing *= 1.7
  1582. if isinstance(spacing, (int, float)):
  1583. text_pos = end + spacing*spacing_dir
  1584. else:
  1585. text_pos = end + spacing
  1586. self.shapes['text'] = Text(text, text_pos, fontsize=fontsize,
  1587. alignment=text_alignment)
  1588. def geometric_features(self):
  1589. d = Arrow1.geometric_features(self)
  1590. d['symbol_location'] = self.shapes['text'].position
  1591. return d
  1592. class Axis2(Force):
  1593. def __init__(self, start, length, label,
  1594. rotation_angle=0, fontsize=0,
  1595. label_spacing=1./45, label_alignment='left'):
  1596. direction = point(cos(radians(rotation_angle)),
  1597. sin(radians(rotation_angle)))
  1598. Force.__init__(start=start, end=length*direction, text=label,
  1599. text_spacing=label_spacing,
  1600. fontsize=fontsize, text_pos='end',
  1601. text_alignment=label_alignment)
  1602. # Substitute text by label for axis
  1603. self.shapes['label'] = self.shapes['text']
  1604. del self.shapes['text']
  1605. # geometric features from Force is ok
  1606. class Gravity(Axis):
  1607. """Downward-pointing gravity arrow with the symbol g."""
  1608. def __init__(self, start, length, fontsize=0):
  1609. Axis.__init__(self, start, length, '$g$', below=False,
  1610. rotation_angle=-90, label_spacing=1./30,
  1611. fontsize=fontsize)
  1612. self.shapes['arrow'].set_linecolor('black')
  1613. class Gravity(Force):
  1614. """Downward-pointing gravity arrow with the symbol g."""
  1615. def __init__(self, start, length, text='$g$', fontsize=0):
  1616. Force.__init__(self, start, (start[0], start[1]-length),
  1617. text, text_spacing=1./60,
  1618. fontsize=0, text_pos='end')
  1619. self.shapes['arrow'].set_linecolor('black')
  1620. class Distance_wText(Shape):
  1621. """
  1622. Arrow <-> with text (usually a symbol) at the midpoint, used for
  1623. identifying a some distance in a figure. The text is placed
  1624. slightly to the right of vertical-like arrows, with text displaced
  1625. `text_spacing` times to total distance in x direction of the plot
  1626. area. The text is by default aligned 'left' in this case. For
  1627. horizontal-like arrows, the text is placed the same distance
  1628. above, but aligned 'center' by default (when `alignment` is None).
  1629. """
  1630. def __init__(self, start, end, text, fontsize=0, text_spacing=1/60.,
  1631. alignment=None, text_pos='mid'):
  1632. start = arr2D(start)
  1633. end = arr2D(end)
  1634. # Decide first if we have a vertical or horizontal arrow
  1635. vertical = abs(end[0]-start[0]) < 2*abs(end[1]-start[1])
  1636. if vertical:
  1637. # Assume end above start
  1638. if end[1] < start[1]:
  1639. start, end = end, start
  1640. if alignment is None:
  1641. alignment = 'left'
  1642. else: # horizontal arrow
  1643. # Assume start to the right of end
  1644. if start[0] < end[0]:
  1645. start, end = end, start
  1646. if alignment is None:
  1647. alignment = 'center'
  1648. tangent = end - start
  1649. # Tangeng goes always to the left and upward
  1650. normal = unit_vec([tangent[1], -tangent[0]])
  1651. mid = 0.5*(start + end) # midpoint of start-end line
  1652. if text_pos == 'mid':
  1653. text_pos = mid + normal*drawing_tool.xrange*text_spacing
  1654. text = Text(text, text_pos, fontsize=fontsize,
  1655. alignment=alignment)
  1656. else:
  1657. is_sequence(text_pos, length=2)
  1658. text = Text_wArrow(text, text_pos, mid, alignment='left',
  1659. fontsize=fontsize)
  1660. arrow = Arrow1(start, end, style='<->')
  1661. arrow.set_linecolor('black')
  1662. arrow.set_linewidth(1)
  1663. self.shapes = {'arrow': arrow, 'text': text}
  1664. def geometric_features(self):
  1665. d = self.shapes['arrow'].geometric_features()
  1666. d['text_position'] = self.shapes['text'].position
  1667. return d
  1668. class Arc_wText(Shape):
  1669. """
  1670. Arc with text positionned at the left of arc half-way
  1671. """
  1672. def __init__(self, text, center, radius,
  1673. start_angle, arc_angle, fontsize=0,
  1674. resolution=180, text_spacing=1/60.):
  1675. self.text = text
  1676. self.center = center
  1677. self.radius = radius
  1678. self.fontsize=fontsize
  1679. self.resolution=resolution
  1680. self.text_spacing=text_spacing
  1681. self.start_angle = start_angle
  1682. self.arc_angle = arc_angle
  1683. self.setArc()
  1684. def setArc(self):
  1685. arc = Arc(self.center, self.radius, self.start_angle, self.arc_angle,
  1686. self.resolution)
  1687. mid = arr2D(arc(self.arc_angle/2.))
  1688. normal = unit_vec(mid - arr2D(self.center))
  1689. text_pos = mid + normal*drawing_tool.xrange*self.text_spacing
  1690. if hasattr(self, 'linewidth'):
  1691. arc.set_linewidth(self.linewidth)
  1692. self.shapes = {'arc': arc,
  1693. 'text': Text(self.text, text_pos, fontsize=self.fontsize)}
  1694. def changeAngle(self,start_angle,arc_angle):
  1695. self.arc_angle = arc_angle
  1696. self.start_angle = start_angle
  1697. self.setArc()
  1698. def set_linewidth(self, width):
  1699. self.linewidth = width
  1700. self.change_linewidth()
  1701. def change_linewidth(self):
  1702. super().set_linewidth(self.linewidth)
  1703. class Composition(Shape):
  1704. def __init__(self, shapes):
  1705. """shapes: list or dict of Shape objects."""
  1706. if isinstance(shapes, (tuple,list)):
  1707. # Convert to dict using the type of the list element as key
  1708. # (add a counter to make the keys unique)
  1709. shapes = {s.__class__.__name__ + '_' + str(i): s
  1710. for i, s in enumerate(shapes)}
  1711. self.shapes = shapes
  1712. # can make help methods: Line.midpoint, Line.normal(pt, dir='left') -> (x,y)
  1713. # list annotations in each class? contains extra annotations for explaining
  1714. # important parameters to the constructor, e.g., Line.annotations holds
  1715. # start and end as Text objects. Shape.demo calls shape.draw and
  1716. # for annotation in self.demo: annotation.draw() YES!
  1717. # Can make overall demo of classes by making objects and calling demo
  1718. # Could include demo fig in each constructor
  1719. class SimplySupportedBeam(Shape):
  1720. def __init__(self, pos, size):
  1721. pos = arr2D(pos)
  1722. P0 = (pos[0] - size/2., pos[1]-size)
  1723. P1 = (pos[0] + size/2., pos[1]-size)
  1724. triangle = Triangle(P0, P1, pos)
  1725. gap = size/5.
  1726. h = size/4. # height of rectangle
  1727. P2 = (P0[0], P0[1]-gap-h)
  1728. rectangle = Rectangle(P2, size, h).set_filled_curves(pattern='/')
  1729. self.shapes = {'triangle': triangle, 'rectangle': rectangle}
  1730. self.dimensions = {'pos': Text('pos', pos),
  1731. 'size': Distance_wText((P2[0], P2[1]-size),
  1732. (P2[0]+size, P2[1]-size),
  1733. 'size')}
  1734. def geometric_features(self):
  1735. t = self.shapes['triangle']
  1736. r = self.shapes['rectangle']
  1737. d = {'pos': t.geometric_features()['p2'],
  1738. 'mid_support': r.geometric_features()['lower_mid']}
  1739. return d
  1740. class ConstantBeamLoad(Shape):
  1741. """
  1742. Downward-pointing arrows indicating a vertical load.
  1743. The arrows are of equal length and filling a rectangle
  1744. specified as in the :class:`Rectangle` class.
  1745. Recorded geometric features:
  1746. ==================== =============================================
  1747. Attribute Description
  1748. ==================== =============================================
  1749. mid_top Middle point at the top of the row of
  1750. arrows (often used for positioning a text).
  1751. ==================== =============================================
  1752. """
  1753. def __init__(self, lower_left_corner, width, height, num_arrows=10):
  1754. box = Rectangle(lower_left_corner, width, height)
  1755. self.shapes = {'box': box}
  1756. dx = float(width)/(num_arrows-1)
  1757. y_top = lower_left_corner[1] + height
  1758. y_tip = lower_left_corner[1]
  1759. for i in range(num_arrows):
  1760. x = lower_left_corner[0] + i*dx
  1761. self.shapes['arrow%d' % i] = Arrow1((x, y_top), (x, y_tip))
  1762. def geometric_features(self):
  1763. return {'mid_top': self.shapes['box'].geometric_features()['upper_mid']}
  1764. class Moment(Arc_wText):
  1765. """
  1766. defines a Moment arrow with text given text, center and radius
  1767. default direction is counter_clockwise, fontsize and text spacing as optional parameters
  1768. """
  1769. def __init__(self, text, center, radius,
  1770. left=True, counter_clockwise=True,
  1771. fontsize=0, text_spacing=1/60.):
  1772. style = '->' if counter_clockwise else '<-'
  1773. start_angle = 90 if left else -90
  1774. Arc_wText.__init__(self, text, center, radius,
  1775. start_angle=start_angle,
  1776. arc_angle=180, fontsize=fontsize,
  1777. text_spacing=text_spacing,
  1778. resolution=180)
  1779. self.shapes['arc']['arc'].set_arrow(style) # Curve object
  1780. class Wheel(Shape):
  1781. """
  1782. Hub and spokes Wheel given center, radius, spokes (default 10), inner_radius(default 1/5 of radius)
  1783. """
  1784. def __init__(self, center, radius, inner_radius=None, nlines=10):
  1785. if inner_radius is None:
  1786. inner_radius = radius/5.0
  1787. outer = Circle(center, radius)
  1788. inner = Circle(center, inner_radius)
  1789. lines = []
  1790. self.nlines = nlines
  1791. # Draw nlines+1 since the first and last coincide
  1792. # (then nlines lines will be visible)
  1793. t = linspace(0, 2*pi, self.nlines+1)
  1794. Ri = inner_radius; Ro = radius
  1795. x0 = center[0]; y0 = center[1]
  1796. xinner = x0 + Ri*cos(t)
  1797. yinner = y0 + Ri*sin(t)
  1798. xouter = x0 + Ro*cos(t)
  1799. youter = y0 + Ro*sin(t)
  1800. lines = [Line((xi,yi),(xo,yo)) for xi, yi, xo, yo in \
  1801. zip(xinner, yinner, xouter, youter)]
  1802. self.shapes = {'inner': inner, 'outer': outer,
  1803. 'spokes': Composition(
  1804. {'spoke%d' % i: lines[i]
  1805. for i in range(len(lines))})}
  1806. class SineWave(Shape):
  1807. def __init__(self, xstart, xstop,
  1808. wavelength, amplitude, mean_level):
  1809. self.xstart = xstart
  1810. self.xstop = xstop
  1811. self.wavelength = wavelength
  1812. self.amplitude = amplitude
  1813. self.mean_level = mean_level
  1814. npoints = (self.xstop - self.xstart)/(self.wavelength/61.0)
  1815. x = linspace(self.xstart, self.xstop, npoints)
  1816. k = 2*pi/self.wavelength # frequency
  1817. y = self.mean_level + self.amplitude*sin(k*x)
  1818. self.shapes = {'waves': Curve(x,y)}
  1819. class Spring(Shape):
  1820. """
  1821. Specify a *vertical* spring, starting at `start` and with `length`
  1822. as total vertical length. In the middle of the spring there are
  1823. `num_windings` circular windings to illustrate the spring. If
  1824. `teeth` is true, the spring windings look like saw teeth,
  1825. otherwise the windings are smooth circles. The parameters `width`
  1826. (total width of spring) and `bar_length` (length of first and last
  1827. bar are given sensible default values if they are not specified
  1828. (these parameters can later be extracted as attributes, see table
  1829. below).
  1830. """
  1831. spring_fraction = 1./2 # fraction of total length occupied by spring
  1832. def __init__(self, start, length, width=None, bar_length=None,
  1833. num_windings=11, teeth=False):
  1834. B = start
  1835. n = num_windings - 1 # n counts teeth intervals
  1836. if n <= 6:
  1837. n = 7
  1838. # n must be odd:
  1839. if n % 2 == 0:
  1840. n = n+1
  1841. L = length
  1842. if width is None:
  1843. w = L/10.
  1844. else:
  1845. w = width/2.0
  1846. s = bar_length
  1847. # [0, x, L-x, L], f = (L-2*x)/L
  1848. # x = L*(1-f)/2.
  1849. # B: start point
  1850. # w: half-width
  1851. # L: total length
  1852. # s: length of first bar
  1853. # P0: start of dashpot (B[0]+s)
  1854. # P1: end of dashpot
  1855. # P2: end point
  1856. shapes = {}
  1857. if s is None:
  1858. f = Spring.spring_fraction
  1859. s = L*(1-f)/2. # start of spring
  1860. self.bar_length = s # record
  1861. self.width = 2*w
  1862. P0 = (B[0], B[1] + s)
  1863. P1 = (B[0], B[1] + L-s)
  1864. P2 = (B[0], B[1] + L)
  1865. if s >= L:
  1866. raise ValueError('length of first bar: %g is larger than total length: %g' % (s, L))
  1867. shapes['bar1'] = Line(B, P0)
  1868. spring_length = L - 2*s
  1869. t = spring_length/n # height increment per winding
  1870. if teeth:
  1871. resolution = 4
  1872. else:
  1873. resolution = 90
  1874. q = linspace(0, n, n*resolution + 1)
  1875. x = P0[0] + w*sin(2*pi*q)
  1876. y = P0[1] + q*t
  1877. shapes['spiral'] = Curve(x, y)
  1878. shapes['bar2'] = Line(P1,P2)
  1879. self.shapes = shapes
  1880. # Dimensions
  1881. start = Text_wArrow('start', (B[0]-1.5*w,B[1]-1.5*w), B)
  1882. width = Distance_wText((B[0]-w, B[1]-3.5*w), (B[0]+w, B[1]-3.5*w),
  1883. 'width')
  1884. length = Distance_wText((B[0]+3*w, B[1]), (B[0]+3*w, B[1]+L),
  1885. 'length')
  1886. num_windings = Text_wArrow('num_windings',
  1887. (B[0]+2*w,P2[1]+w),
  1888. (B[0]+1.2*w, B[1]+L/2.))
  1889. blength1 = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
  1890. 'bar_length',
  1891. text_pos=(P0[0]-7*w, P0[1]+w))
  1892. blength2 = Distance_wText((P1[0]-2*w, P1[1]), (P2[0]-2*w, P2[1]),
  1893. 'bar_length',
  1894. text_pos=(P2[0]-7*w, P2[1]+w))
  1895. dims = {'start': start, 'width': width, 'length': length,
  1896. 'num_windings': num_windings, 'bar_length1': blength1,
  1897. 'bar_length2': blength2}
  1898. self.dimensions = dims
  1899. def geometric_features(self):
  1900. """
  1901. Recorded geometric features:
  1902. ==================== =============================================
  1903. Attribute Description
  1904. ==================== =============================================
  1905. start Start point of spring.
  1906. end End point of spring.
  1907. width Total width of spring.
  1908. bar_length Length of first (and last) bar part.
  1909. ==================== =============================================
  1910. """
  1911. b1 = self.shapes['bar1']
  1912. d = {'start': b1.geometric_features()['start'],
  1913. 'end': self.shapes['bar2'].geometric_features()['end'],
  1914. 'bar_length': self.bar_length,
  1915. 'width': self.width}
  1916. return d
  1917. class Dashpot(Shape):
  1918. """
  1919. Specify a vertical dashpot of height `total_length` and `start` as
  1920. bottom/starting point. The first bar part has length `bar_length`.
  1921. Then comes the dashpot as a rectangular construction of total
  1922. width `width` and height `dashpot_length`. The position of the
  1923. piston inside the rectangular dashpot area is given by
  1924. `piston_pos`, which is the distance between the first bar (given
  1925. by `bar_length`) to the piston.
  1926. If some of `dashpot_length`, `bar_length`, `width` or `piston_pos`
  1927. are not given, suitable default values are calculated. Their
  1928. values can be extracted as keys in the dict returned from
  1929. ``geometric_features``.
  1930. """
  1931. dashpot_fraction = 1./2 # fraction of total_length
  1932. piston_gap_fraction = 1./6 # fraction of width
  1933. piston_thickness_fraction = 1./8 # fraction of dashplot_length
  1934. def __init__(self, start, total_length, bar_length=None,
  1935. width=None, dashpot_length=None, piston_pos=None):
  1936. B = start
  1937. L = total_length
  1938. if width is None:
  1939. w = L/10. # total width 1/5 of length
  1940. else:
  1941. w = width/2.0
  1942. s = bar_length
  1943. # [0, x, L-x, L], f = (L-2*x)/L
  1944. # x = L*(1-f)/2.
  1945. # B: start point
  1946. # w: half-width
  1947. # L: total length
  1948. # s: length of first bar
  1949. # P0: start of dashpot (B[0]+s)
  1950. # P1: end of dashpot
  1951. # P2: end point
  1952. shapes = {}
  1953. # dashpot is P0-P1 in y and width 2*w
  1954. if dashpot_length is None:
  1955. if s is None:
  1956. f = Dashpot.dashpot_fraction
  1957. s = L*(1-f)/2. # default
  1958. P1 = (B[0], B[1]+L-s)
  1959. dashpot_length = f*L
  1960. else:
  1961. if s is None:
  1962. f = 1./2 # the bar lengths are taken as f*dashpot_length
  1963. s = f*dashpot_length # default
  1964. P1 = (B[0], B[1]+s+dashpot_length)
  1965. P0 = (B[0], B[1]+s)
  1966. P2 = (B[0], B[1]+L)
  1967. if P2[1] > P1[1] > P0[1]:
  1968. pass # ok
  1969. else:
  1970. 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]))
  1971. shapes['line start'] = Line(B, P0)
  1972. shapes['pot'] = Curve([P1[0]-w, P0[0]-w, P0[0]+w, P1[0]+w],
  1973. [P1[1], P0[1], P0[1], P1[1]])
  1974. piston_thickness = dashpot_length*Dashpot.piston_thickness_fraction
  1975. if piston_pos is None:
  1976. piston_pos = 1/3.*dashpot_length
  1977. if piston_pos < 0:
  1978. piston_pos = 0
  1979. elif piston_pos > dashpot_length:
  1980. piston_pos = dashpot_length - piston_thickness
  1981. abs_piston_pos = P0[1] + piston_pos
  1982. gap = w*Dashpot.piston_gap_fraction
  1983. shapes['piston'] = Composition(
  1984. {'line': Line(P2, (B[0], abs_piston_pos + piston_thickness)),
  1985. 'rectangle': Rectangle((B[0] - w+gap, abs_piston_pos),
  1986. 2*w-2*gap, piston_thickness),
  1987. })
  1988. shapes['piston']['rectangle'].set_filled_curves(pattern='X')
  1989. self.shapes = shapes
  1990. self.bar_length = s
  1991. self.width = 2*w
  1992. self.piston_pos = piston_pos
  1993. self.dashpot_length = dashpot_length
  1994. # Dimensions
  1995. start = Text_wArrow('start', (B[0]-1.5*w,B[1]-1.5*w), B)
  1996. width = Distance_wText((B[0]-w, B[1]-3.5*w), (B[0]+w, B[1]-3.5*w),
  1997. 'width')
  1998. dplength = Distance_wText((B[0]+2*w, P0[1]), (B[0]+2*w, P1[1]),
  1999. 'dashpot_length', text_pos=(B[0]+w,B[1]-w))
  2000. blength = Distance_wText((B[0]-2*w, B[1]), (B[0]-2*w, P0[1]),
  2001. 'bar_length', text_pos=(B[0]-6*w,P0[1]-w))
  2002. ppos = Distance_wText((B[0]-2*w, P0[1]), (B[0]-2*w, P0[1]+piston_pos),
  2003. 'piston_pos', text_pos=(B[0]-6*w,P0[1]+piston_pos-w))
  2004. tlength = Distance_wText((B[0]+4*w, B[1]), (B[0]+4*w, B[1]+L),
  2005. 'total_length',
  2006. text_pos=(B[0]+4.5*w, B[1]+L-2*w))
  2007. line = Line((B[0]+w, abs_piston_pos), (B[0]+7*w, abs_piston_pos)).set_linestyle('dashed').set_linecolor('black').set_linewidth(1)
  2008. pp = Text('abs_piston_pos', (B[0]+7*w, abs_piston_pos), alignment='left')
  2009. dims = {'start': start, 'width': width, 'dashpot_length': dplength,
  2010. 'bar_length': blength, 'total_length': tlength,
  2011. 'piston_pos': ppos,}
  2012. #'abs_piston_pos': Composition({'line': line, 'text': pp})}
  2013. self.dimensions = dims
  2014. def geometric_features(self):
  2015. """
  2016. Recorded geometric features:
  2017. ==================== =============================================
  2018. Attribute Description
  2019. ==================== =============================================
  2020. start Start point of dashpot.
  2021. end End point of dashpot.
  2022. bar_length Length of first bar (from start to spring).
  2023. dashpot_length Length of dashpot middle part.
  2024. width Total width of dashpot.
  2025. piston_pos Position of piston in dashpot, relative to
  2026. start[1] + bar_length.
  2027. ==================== =============================================
  2028. """
  2029. d = {'start': self.shapes['line start'].geometric_features()['start'],
  2030. 'end': self.shapes['piston']['line'].geometric_features()['start'],
  2031. 'bar_length': self.bar_length,
  2032. 'piston_pos': self.piston_pos,
  2033. 'width': self.width,
  2034. 'dashpot_length': self.dashpot_length,
  2035. }
  2036. return d
  2037. class Wavy(Shape):
  2038. """
  2039. A wavy graph consisting of a user-given main curve y=f(x) with
  2040. additional sinusoidal waves of given (constant) amplitude,
  2041. but varying wavelength (a characteristic wavelength is specified).
  2042. """
  2043. def __init__(self, main_curve, interval, wavelength_of_perturbations,
  2044. amplitude_of_perturbations, smoothness):
  2045. """
  2046. ============================ ====================================
  2047. Name Description
  2048. ============================ ====================================
  2049. main_curve f(x) Python function
  2050. interval interval for main_curve
  2051. wavelength_of_perturbations dominant wavelength perturbed waves
  2052. amplitude_of_perturbations amplitude of perturbed waves
  2053. smoothness in [0, 1]: smooth=0, rough=1
  2054. ============================ ====================================
  2055. """
  2056. xmin, xmax = interval
  2057. L = wavelength_of_perturbations
  2058. k_0 = 2*pi/L # main frequency of waves
  2059. k_p = k_0*0.5
  2060. k_k = k_0/2*smoothness
  2061. A_0 = amplitude_of_perturbations
  2062. A_p = 0.3*A_0
  2063. A_k = k_0/2
  2064. x = linspace(xmin, xmax, 2001)
  2065. def w(x):
  2066. A = A_0 + A_p*sin(A_k*x)
  2067. k = k_0 + k_p*sin(k_k*x)
  2068. y = main_curve(x) + A*sin(k*x)
  2069. return y
  2070. self.shapes = {'wavy': Curve(x, w(x))}
  2071. # Use closure w to define __call__ - then we do not need
  2072. # to store all the parameters A_0, A_k, etc. as attributes
  2073. self.__call__ = w
  2074. class StochasticWavyCurve(object):
  2075. """
  2076. Precomputed stochastic wavy graphs.
  2077. There are three graphs with different look.
  2078. Curve 0:
  2079. ----------------------------------------------------------------------
  2080. |
  2081. |
  2082. *|
  2083. * |
  2084. * |
  2085. * |
  2086. * |
  2087. * |
  2088. * |
  2089. * |
  2090. * |
  2091. * |
  2092. * |
  2093. * |
  2094. |*
  2095. | *
  2096. | *
  2097. | *
  2098. | *
  2099. | *
  2100. | *
  2101. | *
  2102. | *
  2103. | *
  2104. | *
  2105. | *
  2106. | *
  2107. | *
  2108. | *
  2109. | *
  2110. | *
  2111. | *
  2112. | *
  2113. | *
  2114. | *
  2115. | *
  2116. | *
  2117. | *
  2118. | *
  2119. | *
  2120. | *
  2121. | *
  2122. | *
  2123. | *
  2124. | *
  2125. | *
  2126. | *
  2127. | *
  2128. | *
  2129. | *
  2130. | *
  2131. | *
  2132. | *
  2133. | *
  2134. | *
  2135. | *
  2136. | *
  2137. | *
  2138. | *
  2139. | *
  2140. | *
  2141. | *
  2142. | *
  2143. | *
  2144. | *
  2145. | *
  2146. | *
  2147. | *
  2148. | *
  2149. | *
  2150. | *
  2151. | *
  2152. |*
  2153. *|
  2154. * |
  2155. * |
  2156. * |
  2157. * |
  2158. * |
  2159. * |
  2160. * |
  2161. * |
  2162. * |
  2163. * |
  2164. * |
  2165. * |
  2166. * |
  2167. * |
  2168. * |
  2169. |*
  2170. | *
  2171. | *
  2172. | *
  2173. | *
  2174. | *
  2175. | *
  2176. | *
  2177. | *
  2178. | *
  2179. | *
  2180. | *
  2181. | *
  2182. | *
  2183. | *
  2184. | *
  2185. | *
  2186. | *
  2187. | *
  2188. | *
  2189. | *
  2190. | *
  2191. | *
  2192. | *
  2193. | *
  2194. | *
  2195. | *
  2196. | *
  2197. | *
  2198. | *
  2199. | *
  2200. | *
  2201. | *
  2202. | *
  2203. | *
  2204. | *
  2205. | *
  2206. | *
  2207. | *
  2208. | *
  2209. | *
  2210. | *
  2211. | *
  2212. | *
  2213. | *
  2214. |
  2215. * |
  2216. * |
  2217. * |
  2218. * |
  2219. * |
  2220. * |
  2221. * |
  2222. * |
  2223. * |
  2224. * |
  2225. * |
  2226. * |
  2227. * |
  2228. * |
  2229. * |
  2230. * |
  2231. * |
  2232. * |
  2233. * |
  2234. * |
  2235. * |
  2236. * |
  2237. * |
  2238. * |
  2239. * |
  2240. * |
  2241. * |
  2242. * |
  2243. * |
  2244. * |
  2245. Curve 2:
  2246. ----------------------------------------------------------------------
  2247. |
  2248. |
  2249. |
  2250. |*
  2251. |*
  2252. |*
  2253. |
  2254. |
  2255. *|
  2256. |*
  2257. | *
  2258. | *
  2259. | *
  2260. | *
  2261. | *
  2262. | *
  2263. | *
  2264. | *
  2265. | *
  2266. | *
  2267. | *
  2268. | *
  2269. | *
  2270. | *
  2271. | *
  2272. | *
  2273. | *
  2274. | *
  2275. | *
  2276. | *
  2277. | *
  2278. | *
  2279. | *
  2280. | *
  2281. | *
  2282. | *
  2283. | *
  2284. | *
  2285. | *
  2286. | *
  2287. | *
  2288. | *
  2289. | *
  2290. | *
  2291. | *
  2292. | *
  2293. | *
  2294. | *
  2295. | *
  2296. | *
  2297. | *
  2298. | *
  2299. | *
  2300. | *
  2301. | *
  2302. | *
  2303. | *
  2304. | *
  2305. | *
  2306. | *
  2307. | *
  2308. |
  2309. * |
  2310. * |
  2311. * |
  2312. * |
  2313. * |
  2314. * |
  2315. * |
  2316. * |
  2317. * |
  2318. * |
  2319. * |
  2320. * |
  2321. * |
  2322. * |
  2323. * |
  2324. * |
  2325. * |
  2326. * |
  2327. * |
  2328. * |
  2329. * |
  2330. * |
  2331. * |
  2332. * |
  2333. * |
  2334. * |
  2335. * |
  2336. * |
  2337. * |
  2338. |
  2339. | *
  2340. | *
  2341. | *
  2342. | *
  2343. | *
  2344. | *
  2345. | *
  2346. | *
  2347. | *
  2348. | *
  2349. | *
  2350. | *
  2351. | *
  2352. | *
  2353. | *
  2354. | *
  2355. | *
  2356. | *
  2357. | *
  2358. | *
  2359. | *
  2360. | *
  2361. |*
  2362. |*
  2363. |
  2364. |
  2365. |
  2366. |*
  2367. | *
  2368. | *
  2369. |*
  2370. |
  2371. *|
  2372. |*
  2373. | *
  2374. | *
  2375. | *
  2376. | *
  2377. | *
  2378. | *
  2379. | *
  2380. | *
  2381. | *
  2382. | *
  2383. | *
  2384. | *
  2385. | *
  2386. | *
  2387. | *
  2388. | *
  2389. | *
  2390. | *
  2391. | *
  2392. | *
  2393. | *
  2394. | *
  2395. | *
  2396. | *
  2397. | *
  2398. | *
  2399. | *
  2400. | *
  2401. | *
  2402. | *
  2403. | *
  2404. | *
  2405. | *
  2406. | *
  2407. | *
  2408. | *
  2409. | *
  2410. | *
  2411. | *
  2412. Curve 2:
  2413. ----------------------------------------------------------------------
  2414. |
  2415. |
  2416. |
  2417. |
  2418. |*
  2419. | *
  2420. | *
  2421. | *
  2422. | *
  2423. | *
  2424. | *
  2425. | *
  2426. | *
  2427. | *
  2428. | *
  2429. | *
  2430. | *
  2431. | *
  2432. | *
  2433. | *
  2434. | *
  2435. | *
  2436. | *
  2437. | *
  2438. | *
  2439. | *
  2440. | *
  2441. | *
  2442. |*
  2443. |
  2444. * |
  2445. * |
  2446. * |
  2447. * |
  2448. * |
  2449. * |
  2450. * |
  2451. * |
  2452. * |
  2453. * |
  2454. |*
  2455. | *
  2456. | *
  2457. | *
  2458. | *
  2459. | *
  2460. | *
  2461. | *
  2462. | *
  2463. | *
  2464. | *
  2465. | *
  2466. | *
  2467. | *
  2468. | *
  2469. | *
  2470. | *
  2471. | *
  2472. | *
  2473. | *
  2474. *|
  2475. * |
  2476. * |
  2477. * |
  2478. * |
  2479. * |
  2480. * |
  2481. * |
  2482. * |
  2483. * |
  2484. * |
  2485. * |
  2486. * |
  2487. * |
  2488. * |
  2489. * |
  2490. * |
  2491. |
  2492. | *
  2493. | *
  2494. | *
  2495. | *
  2496. | *
  2497. | *
  2498. | *
  2499. | *
  2500. | *
  2501. | *
  2502. | *
  2503. | *
  2504. | *
  2505. | *
  2506. | *
  2507. | *
  2508. | *
  2509. | *
  2510. | *
  2511. | *
  2512. | *
  2513. | *
  2514. |*
  2515. *|
  2516. * |
  2517. * |
  2518. * |
  2519. * |
  2520. * |
  2521. * |
  2522. * |
  2523. * |
  2524. * |
  2525. * |
  2526. * |
  2527. * |
  2528. * |
  2529. * |
  2530. * |
  2531. * |
  2532. * |
  2533. * |
  2534. * |
  2535. * |
  2536. * |
  2537. * |
  2538. * |
  2539. * |
  2540. * |
  2541. * |
  2542. * |
  2543. * |
  2544. * |
  2545. * |
  2546. * |
  2547. * |
  2548. * |
  2549. * |
  2550. * |
  2551. * |
  2552. * |
  2553. * |
  2554. * |
  2555. * |
  2556. * |
  2557. * |
  2558. * |
  2559. * |
  2560. * |
  2561. * |
  2562. * |
  2563. * |
  2564. * |
  2565. * |
  2566. * |
  2567. * |
  2568. * |
  2569. * |
  2570. * |
  2571. *|
  2572. |*
  2573. | *
  2574. | *
  2575. | *
  2576. | *
  2577. | *
  2578. | *
  2579. See also hplgit.github.io/pysketcher/doc/src/tut/fig-tut/StochasticWavyCurve.png (and .pdf)
  2580. """
  2581. # The curves were generated by the script generate_road_profiles.py and
  2582. # the code below were generated by plot_roads.py. Both scripts are
  2583. # found doc/src/src-bumpy in the repo git@github.com:hplgit/bumpy.git
  2584. def __init__(self, curve_no=0, percentage=100):
  2585. """
  2586. ============= ===================================================
  2587. Argument Explanation
  2588. ============= ===================================================
  2589. curve_no 0, 1, or 2: chooses one out of three shapes.
  2590. percentage The percentage of the defined curve to be used.
  2591. ============= ===================================================
  2592. """
  2593. self._define_curves()
  2594. self.curve_no = curve_no
  2595. m = int(len(self.x)/float(percentage)*100)
  2596. self.shapes = {'wavy': Curve(self.x[:m], self.y[curve_no][:m])}
  2597. def __call__(self, x):
  2598. raise NotImplementedError
  2599. def _define_curves(self):
  2600. 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, ])
  2601. self.y = [None]*3
  2602. 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, ])
  2603. 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, ])
  2604. 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, ])
  2605. # COMPOSITE types:
  2606. # MassSpringForce: Line(horizontal), Spring, Rectangle, Arrow/Line(w/arrow)
  2607. # must be easy to find the tip of the arrow
  2608. # Maybe extra dict: self.name['mass'] = Rectangle object - YES!
  2609. class ArbitraryVolume(Shape):
  2610. """
  2611. An arbitrary closed volume with an optional normal vector and a
  2612. vector field to be used in derivation of continuum mechanical
  2613. equations.
  2614. """
  2615. def __init__(self, position, width=1,
  2616. volume_symbol='V',
  2617. volume_symbol_fontsize='18',
  2618. normal_vector_symbol='n',
  2619. vector_field_symbol=None):
  2620. """
  2621. ============================ ====================================
  2622. Name Description
  2623. ============================ ====================================
  2624. position center point of volume
  2625. width width of volume (about 3 is best)
  2626. normal_vector_symbol symbol of None (no boundary normal)
  2627. volume_symbol None (no center symbol) or character
  2628. volume_symbol_fontsize fontsize of volume symbol
  2629. vector_field_symbol None (no vector) or symbol
  2630. ============================ ====================================
  2631. """
  2632. self.position, self.width = position, width
  2633. self.vector_symbol = vector_field_symbol
  2634. self.normal_symbol = normal_vector_symbol
  2635. ellipse, normal, vector = self._perturbed_unit_ellipse()
  2636. self.shapes = {'closed_curve': ellipse}
  2637. if normal_vector_symbol:
  2638. self.shapes['normal'] = normal
  2639. if vector_field_symbol is not None:
  2640. self.shapes['vector'] = vector
  2641. # Scale and translate
  2642. self.rotate(20, (0,0))
  2643. self.scale(width/2.0)
  2644. self.translate(position)
  2645. # Must be placed at position after translation:
  2646. if volume_symbol:
  2647. self.shapes['name'] = Text('$%s$' % volume_symbol, position,
  2648. fontsize=volume_symbol_fontsize)
  2649. def _perturbed_unit_ellipse(self):
  2650. """Draw the volume as a perturbed ellipse of about unit size."""
  2651. a0 = 1.0
  2652. b0 = 0.75
  2653. eps_a = 0.2
  2654. eps_b = 0.1
  2655. a = lambda t: a0 + eps_a*sin(1*t)
  2656. b = lambda t: b0 + eps_b*cos(1*t)
  2657. x = lambda t: a(t)*cos(t)
  2658. y = lambda t: b(t)*sin(t)
  2659. t = linspace(0, 2*pi, 101) # parameter
  2660. ellipse = Curve(x(t), y(t))
  2661. # Make normal vector
  2662. tx = lambda t: eps_a*cos(t)*cos(t) - a(t)*sin(t)
  2663. ty = lambda t: -eps_b*sin(t)*sin(t) + b(t)*cos(t)
  2664. t0 = pi/5
  2665. nx = ty(t0)
  2666. ny = -tx(t0)
  2667. nx = nx/sqrt(nx**2 + ny**2)
  2668. ny = ny/sqrt(nx**2 + ny**2)
  2669. Px = x(t0)
  2670. Py = y(t0)
  2671. start = point(x(t0), y(t0))
  2672. end = start + point(0.75*b0*nx, 0.75*b0*ny)
  2673. normal = Force(start, end, '$\\boldsymbol{%s}$' % self.normal_symbol,
  2674. text_spacing=1./60,
  2675. text_pos='end',
  2676. text_alignment='center')
  2677. end = start + point(0.75*b0/3*nx, 0.75*b0*4*ny)
  2678. vector = Force(start, end, '$\\boldsymbol{%s}$' % self.vector_symbol,
  2679. text_spacing=1./60,
  2680. text_pos='end',
  2681. text_alignment='center')
  2682. return ellipse, normal, vector
  2683. def geometric_features(self):
  2684. """
  2685. Recorded geometric features:
  2686. ==================== =============================================
  2687. Attribute Description
  2688. ==================== =============================================
  2689. position center point of volume
  2690. normal_vector_start start of normal vector
  2691. normal_vector_end end of normal vector
  2692. ==================== =============================================
  2693. """
  2694. d = {'position': self.position}
  2695. if 'normal' in self.shapes:
  2696. d['normal_vector_start'] = self.shapes['normal'].geometric_features()['start']
  2697. d['normal_vector_end'] = self.shapes['normal'].geometric_features()['end']
  2698. return d
  2699. def _test1():
  2700. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2701. l1 = Line((0,0), (1,1))
  2702. l1.draw()
  2703. eval(input(': '))
  2704. c1 = Circle((5,2), 1)
  2705. c2 = Circle((6,2), 1)
  2706. w1 = Wheel((7,2), 1)
  2707. c1.draw()
  2708. c2.draw()
  2709. w1.draw()
  2710. hardcopy()
  2711. display() # show the plot
  2712. def _test2():
  2713. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2714. l1 = Line((0,0), (1,1))
  2715. l1.draw()
  2716. eval(input(': '))
  2717. c1 = Circle((5,2), 1)
  2718. c2 = Circle((6,2), 1)
  2719. w1 = Wheel((7,2), 1)
  2720. filled_curves(True)
  2721. set_linecolor('blue')
  2722. c1.draw()
  2723. set_linecolor('aqua')
  2724. c2.draw()
  2725. filled_curves(False)
  2726. set_linecolor('red')
  2727. w1.draw()
  2728. hardcopy()
  2729. display() # show the plot
  2730. def _test3():
  2731. """Test example from the book."""
  2732. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2733. l1 = Line(start=(0,0), stop=(1,1)) # define line
  2734. l1.draw() # make plot data
  2735. r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
  2736. r1.draw()
  2737. Circle(center=(5,7), radius=1).draw()
  2738. Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7).draw()
  2739. hardcopy()
  2740. display()
  2741. def _test4():
  2742. """Second example from the book."""
  2743. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2744. r1 = Rectangle(lower_left_corner=(0,1), width=3, height=5)
  2745. c1 = Circle(center=(5,7), radius=1)
  2746. w1 = Wheel(center=(6,2), radius=2, inner_radius=0.5, nlines=7)
  2747. c2 = Circle(center=(7,7), radius=1)
  2748. filled_curves(True)
  2749. c1.draw()
  2750. set_linecolor('blue')
  2751. r1.draw()
  2752. set_linecolor('aqua')
  2753. c2.draw()
  2754. # Add thick aqua line around rectangle:
  2755. filled_curves(False)
  2756. set_linewidth(4)
  2757. r1.draw()
  2758. set_linecolor('red')
  2759. # Draw wheel with thick lines:
  2760. w1.draw()
  2761. hardcopy('tmp_colors')
  2762. display()
  2763. def _test5():
  2764. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2765. c = 6. # center point of box
  2766. w = 2. # size of box
  2767. L = 3
  2768. r1 = Rectangle((c-w/2, c-w/2), w, w)
  2769. l1 = Line((c,c-w/2), (c,c-w/2-L))
  2770. linecolor('blue')
  2771. filled_curves(True)
  2772. r1.draw()
  2773. linecolor('aqua')
  2774. filled_curves(False)
  2775. l1.draw()
  2776. hardcopy()
  2777. display() # show the plot
  2778. def rolling_wheel(total_rotation_angle):
  2779. """Animation of a rotating wheel."""
  2780. set_coordinate_system(xmin=0, xmax=10, ymin=0, ymax=10)
  2781. import time
  2782. center = (6,2)
  2783. radius = 2.0
  2784. angle = 2.0
  2785. pngfiles = []
  2786. w1 = Wheel(center=center, radius=radius, inner_radius=0.5, nlines=7)
  2787. for i in range(int(total_rotation_angle/angle)):
  2788. w1.draw()
  2789. print('BIG PROBLEM WITH ANIMATE!!!')
  2790. display()
  2791. filename = 'tmp_%03d' % i
  2792. pngfiles.append(filename + '.png')
  2793. hardcopy(filename)
  2794. time.sleep(0.3) # pause
  2795. L = radius*angle*pi/180 # translation = arc length
  2796. w1.rotate(angle, center)
  2797. w1.translate((-L, 0))
  2798. center = (center[0] - L, center[1])
  2799. erase()
  2800. cmd = 'convert -delay 50 -loop 1000 %s tmp_movie.gif' \
  2801. % (' '.join(pngfiles))
  2802. print('converting PNG files to animated GIF:\n', cmd)
  2803. import subprocess
  2804. failure, output = subprocess.getstatusoutput(cmd)
  2805. if failure: print('Could not run', cmd)
  2806. if __name__ == '__main__':
  2807. #rolling_wheel(40)
  2808. #_test1()
  2809. #_test3()
  2810. funcs = [
  2811. #test_Axis,
  2812. test_inclined_plane,
  2813. ]
  2814. for func in funcs:
  2815. func()
  2816. input('Type Return: ')