ptcal.py 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515
  1. '''This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
  2. Copyright 2011 by the AToMPM team and licensed under the LGPL
  3. See COPYING.lesser and README.md in the root of this project for full details'''
  4. import re, json, uuid, cPickle as pickle, threading, itertools, traceback, logging
  5. from random import Random
  6. from utils import Utilities as utils
  7. from tcontext import ModelTransformationContext, ExhaustContext
  8. from tconstants import TConstants as TC
  9. from compiler import ModelAndRuleCompiler
  10. from pytcore.core.himesis import HConstants as HC
  11. from pytcore.rules.ndarule import NDARule
  12. from pytcore.tcore.messages import Packet
  13. from accurate_time import time as clock
  14. from accurate_time import set_start_time
  15. set_start_time()
  16. import cProfile, pstats, StringIO
  17. ''' hergin :: motif-integration start '''
  18. from motifcontext import MotifContext
  19. from tcorecontext import TCoreContext
  20. from pytcore.tcore.messages import Pivots
  21. ''' hergin :: motif-integration end '''
  22. import igraph as ig
  23. #import pydot
  24. import datetime
  25. from random import *
  26. from threading import *
  27. from Queue import *
  28. from barrier import *
  29. from synchgraph import *
  30. from itertools import *
  31. #from petrinet import *
  32. from _abcoll import Iterable
  33. from pprint import isreadable
  34. from math import *
  35. import os
  36. '''
  37. py-t-core abstraction layer
  38. _lock used to synchronize access to self._changelogs
  39. _changelogs used to hold asworker changelogs
  40. _M the himesis graph we're transforming
  41. _mtContexts the current transformation contexts
  42. _transfData a mapping between transformation filenames and
  43. their data
  44. _userTransfs stores the transformations loaded by the user
  45. (needed to return to the pre-run state after
  46. running)
  47. _execmode the current execution mode
  48. _debugOn indicates whether or not debugging is enabled
  49. _debugProgrammedBreak a blocking flag used to freeze the execution of a
  50. transformation until the user explicitly resumes,
  51. at which point we continue precisely where we left
  52. off
  53. _mtContexts2debugClients used to map ModelTransformationContexts to their
  54. associated atompm debugging window, if any
  55. _aswCommTools bundle of properties and functions that enable and
  56. facilitate sending requests to our parent
  57. mtworker's asworker
  58. _aswNextSequenceNumber used to determine if a changelog is received out
  59. of order, and if a pending changelog is now ready
  60. to be handled
  61. username the user's username
  62. defaultDCL the user's preferred designer code language '''
  63. class PyTCoreAbstractionLayer :
  64. def __init__(self,aswCommTools,mtwid) :
  65. self._lock = threading.Condition()
  66. self._changelogs = []
  67. self._M = None
  68. ''' hergin :: motif-integration start '''
  69. self.packet = None
  70. self.globalDeltas = []
  71. self.incUpdates = True
  72. self.sendDeltas = True
  73. ''' hergin :: motif-integration end '''
  74. self._mtContexts = []
  75. self._transfData = {}
  76. self._userTransfs = []
  77. self._execmode = 'STOPPED'
  78. self._debugOn = False
  79. self._debugProgrammedBreak = threading.Event()
  80. self._debugProgrammedBreak.set()
  81. self._mtContexts2debugClients = {}
  82. self._aswCommTools = aswCommTools
  83. self._aswNextSequenceNumber = None
  84. self.username = None
  85. self.defaultDCL = TC.PYTHON
  86. self._mtwid = mtwid
  87. ''' Used only in COMP 522 and comp 621
  88. Petri Net Modules, docomposition of disconnected
  89. _M graph '''
  90. self.modules = {}
  91. ''' Synchronization graph for modular state spaces '''
  92. self.SG = None
  93. self.modStart = None
  94. self.modEnd = None
  95. self.flatStart = None
  96. self.flatEnd = None
  97. ''' State spaces for individual modules '''
  98. self.modIDtoPOS= {}
  99. self.SSs = {}
  100. #Enactment vars
  101. self.bdapiQueue = Queue()
  102. #Enactment, If this is true the OpenModel will append model to the canvas, else reload.
  103. self.loadedModel=False
  104. #Enactment, set of formalisms we load automatically to do the cleanups.
  105. self._loadedMMs = set()
  106. def processQuery(self,query):
  107. result = ""
  108. query = query['query'].replace('"',"'")
  109. qValue = query.split('(')[0].strip()
  110. if qValue == "getCount":
  111. typ = query.split("'")[1].strip()
  112. def f(e):
  113. return e['$ft__'].endswith(typ)
  114. result = "Number of '" + typ +"' in the resulting graph: " + str(len(filter(f,self.packet.graph.vs)))
  115. # I tried to use self.packet.graph.vs.select(tokens_eq=3) but the attribute names starting with $ didnt let me
  116. elif qValue == "toggleSendDelta":
  117. self.sendDeltas = not self.sendDeltas
  118. if self.sendDeltas:
  119. result = "Deltas will be sent at the end!"
  120. else:
  121. result = "Deltas won't be sent at the end!"
  122. elif qValue == "toggleIncUpdate":
  123. self.incUpdates = not self.incUpdates
  124. if self.incUpdates:
  125. result = "Incremental updates are on!"
  126. else:
  127. result = "Incremental updates are off!"
  128. self._aswPrintReq(result)
  129. ''' Use this to put the BDAPI response from client'''
  130. def _queueBDAPI(self,resp):
  131. self.bdapiQueue.put(resp)
  132. ''' send a PUT /GET/console request to the asworker '''
  133. def _aswPrintReq(self,msg) :
  134. return self._aswCommTools['httpReq']('PUT','/GET/console',{'text':msg})
  135. '''
  136. do 'callback()' once we've received the feedback for the last step that
  137. ran, if any, checking for the said feedback and handling any newly
  138. received feedback every TC.WAIT_ON_CHLOG_DELAY seconds '''
  139. def _doWhenLastStepFeedbackReceived(self,callback) :
  140. if self.incUpdates:
  141. def condition() :
  142. self._handleChangelogs()
  143. return self._mtContexts[-1].isLastStepFeedbackReceived()
  144. utils.doWhen(
  145. condition,
  146. TC.WAIT_ON_CHLOG_DELAY,
  147. callback)
  148. else:
  149. callback()
  150. '''
  151. synchronously verify if any changelogs have been received, and if we are
  152. ready to handle them, remove them from self._changelogs and handle them
  153. 1. sort changelogs by ascending sequence#
  154. 2. do nothing if no self._changelogs is empty, if no model has been loaded
  155. yet (i.e., self._aswNextSequenceNumber == None), or if the oldest
  156. changelog is still too new to be handled
  157. 3. crash if we encouter an invalid sequence#
  158. 4. otherwise (we're ready to handle the oldest changelog), handle the
  159. oldest changelog, increment self._aswNextSequenceNumber and recurse
  160. back to step 1
  161. TBI: performance would benefit greatly from caches that map atompm ids to
  162. GUIDs '''
  163. def _handleChangelogs(self) :
  164. '''
  165. handle a single changelog '''
  166. def _handleChangelog(changelog) :
  167. def eq(a,b) : return str(a) == str(b)
  168. for c in changelog :
  169. if c['op'] == 'MKEDGE' :
  170. node1 = \
  171. self._M.vs.select(lambda v : eq(v['$atompmId'],c['id1']))[0]
  172. node2 = \
  173. self._M.vs.select(lambda v : eq(v['$atompmId'],c['id2']))[0]
  174. self._M.add_edges([(node1.index, node2.index)])
  175. elif c['op'] == 'RMEDGE' :
  176. pass
  177. elif c['op'] == 'MKNODE' :
  178. self._compiler.addNode(self._M, json.loads(c['node']), c['id'])
  179. elif c['op'] == 'RMNODE' :
  180. node = \
  181. self._M.vs.select(lambda v : eq(v['$atompmId'],c['id']))[0]
  182. self._M.delete_nodes([node.index])
  183. elif c['op'] == 'CHATTR' :
  184. node = \
  185. self._M.vs.select(lambda v : eq(v['$atompmId'],c['id']))[0]
  186. self._M.vs[node.index][c['attr']] = c['new_val']
  187. elif c['op'] == 'LOADMM' :
  188. self._compiler.parseMetamodel(
  189. c['name'],
  190. utils.fread(
  191. '/users/%s/%s.metamodel'%(self.username,c['name'])),
  192. loadMM=True)
  193. elif c['op'] == 'DUMPMM' :
  194. self._compiler.unloadMetamodel(c['name'])
  195. elif c['op'] == 'RESETM' :
  196. self._M = self._compiler.compileModel(c['new_model'])
  197. self._M.mmTypeData = self._compiler.getMMTypeData()
  198. elif c['op'] == 'SYSOUT' :
  199. ''' hergin :: motif-integration :: modify :: added startsWith functions '''
  200. if c['text'].startswith(TC.RULE_SUCCESS_MSG) or \
  201. c['text'].startswith(TC.RULE_NOT_APPLICABLE_MSG) or \
  202. c['text'].startswith(TC.RULE_FAILURE_MSG) or \
  203. c['text'].startswith(TC.TRANSFORMATION_DONE) or \
  204. c['text'].startswith(TC.REMOTE_APPLICATION_FAILURE) :
  205. self._mtContexts[-1].setLastStepFeedbackReceived()
  206. self._lock.acquire()
  207. self._changelogs.sort(key=lambda c : utils.sn2int(c['sequence#']))
  208. if len(self._changelogs) == 0 or \
  209. self._aswNextSequenceNumber == None or \
  210. utils.sn2int(self._changelogs[0]['sequence#']) > \
  211. utils.sn2int(self._aswNextSequenceNumber) :
  212. self._lock.release()
  213. else :
  214. sn = self._changelogs[0]['sequence#']
  215. if utils.sn2int(sn) < utils.sn2int(self._aswNextSequenceNumber) :
  216. raise ValueError('invalid changelog sequence# :: '+sn)
  217. else :
  218. logging.debug('++ ('+sn+') '+str(self._changelogs[0]['changelog']))
  219. _handleChangelog(self._changelogs.pop(0)['changelog'])
  220. self._aswNextSequenceNumber = \
  221. utils.incrementSequenceNumber(self._aswNextSequenceNumber)
  222. self._lock.release()
  223. self._handleChangelogs()
  224. '''
  225. load a model (and its metamodels)
  226. 1. compile the provided model into a himesis graph and save it to self._M
  227. 2. synchronize self._M's mmTypeData with that of self._compiler's so that
  228. it gets updated as new metamodels are loaded
  229. 3. initialize self._aswNextSequenceNumber based on the sequence# 'sn' of
  230. the provided model, and forget any already received out-of-date
  231. changelogs
  232. NOTE: this function should only get called once (when the asworker
  233. initially sets up this mtworker) '''
  234. def loadModel(self,m,mms,sn) :
  235. assert self._M == None, 'ptcal.loadModel() should only be called once'
  236. self._compiler = ModelAndRuleCompiler(
  237. self.username,
  238. self._aswCommTools['wid'],
  239. self.defaultDCL,
  240. self._mtwid)
  241. self._M = self._compiler.compileModel(m,mmsData=mms)
  242. self._M.mmTypeData = self._compiler.getMMTypeData()
  243. ''' hergin :: motif-integration start '''
  244. self.packet = Packet(self._M)
  245. ''' hergin :: motif-integration end '''
  246. self._aswNextSequenceNumber = utils.incrementSequenceNumber(sn)
  247. self._lock.acquire()
  248. self._changelogs = \
  249. filter(lambda c : utils.sn2int(sn) < utils.sn2int(c['sequence#']),
  250. self._changelogs)
  251. self._lock.release()
  252. '''
  253. load a PN model (and its metamodels)
  254. 1. compile the provided model into a himesis graph and save it to self._M
  255. 2. synchronize self._M's mmTypeData with that of self._compiler's so that
  256. it gets updated as new metamodels are loaded
  257. 3. initialize self._aswNextSequenceNumber based on the sequence# 'sn' of
  258. the provided model, and forget any already received out-of-date
  259. changelogs
  260. NOTE: this function should only get called once (when the asworker
  261. initially sets up this mtworker) '''
  262. def loadModelPN(self,m,mms,sn) :
  263. assert self._M == None, 'ptcal.loadModel() should only be called once'
  264. self._compiler = ModelAndRuleCompiler(self.username)
  265. self._M = self._compiler.compileModelPN(m,mmsData=mms)
  266. disjoint = self._M.decompose(mode=ig.WEAK)
  267. #create dictionary of modules
  268. for mod in disjoint:
  269. self.modules [ uuid.uuid4()] = mod
  270. self._M.mmTypeData = self._compiler.getMMTypeData()
  271. self._aswNextSequenceNumber = utils.incrementSequenceNumber(sn)
  272. self._lock.acquire()
  273. self._changelogs = \
  274. filter(lambda c : utils.sn2int(sn) < utils.sn2int(c['sequence#']),
  275. self._changelogs)
  276. self._lock.release()
  277. ''' setup internal state to reflect given runtime configuration '''
  278. def _loadRuntimeConfiguration(self,rc) :
  279. if 'looseSubtypingMM' in rc :
  280. self._compiler.RC__looseSubtypingMM = rc['looseSubtypingMM']
  281. '''
  282. read in some json that describes a model transformation from file 'fname',
  283. store in self._transfData, and push a new ModelTransformationContext onto
  284. self._mtContexts '''
  285. def _loadTransform(self,fname) :
  286. if fname not in self._transfData :
  287. self._transfData[fname] = \
  288. utils.fread('/users/%s/%s'%(self.username,fname))
  289. ''' hergin :: motif-integration start '''
  290. if TC.MOTIFMM in self._transfData[fname]['metamodels']:
  291. self._mtContexts.append(MotifContext(fname,self))
  292. elif TC.TCOREMM in self._transfData[fname]['metamodels']:
  293. self._mtContexts.append(TCoreContext(fname,self))
  294. elif TC.TRANSFMM in self._transfData[fname]['metamodels']:
  295. self._mtContexts.append(ModelTransformationContext(self._transfData[fname],fname))
  296. ''' hergin :: motif-integration end '''
  297. '''
  298. load a set of user-specified transformations and forget anything
  299. previously loaded
  300. 1. forget previously loaded transformations and compiled rules
  301. 2. reset 'session' area
  302. 3. load transformations
  303. 4. remember loaded transformations '''
  304. def loadTransforms(self,fnames) :
  305. self._transfData = {}
  306. self._mtContexts = []
  307. self._compiler.forgetCompiledRules()
  308. self._M.session = {}
  309. for fname in fnames :
  310. self._loadTransform(fname)
  311. self._userTransfs = fnames
  312. '''
  313. returns the filename and compiled form of the next rule to run
  314. 1. fetch the current transformation context
  315. 2. retrieve and load runtime configuration options, if any
  316. 3. if transformatio debugging is enabled and we're entering a
  317. ModelTransformationContext for the first time, we
  318. a) remember self._execmode and set self._execmode to PAUSE
  319. b) notify the user of the impending "programmed debugging pause"
  320. c) request a new atompm instance loaded with the transformation model
  321. corresponding to the current transformation context
  322. d) unset the _debugProgrammedBreak flag and make a blocking call that
  323. waits for another thread to reset it (via
  324. self._startDebugProgrammedBreak())... this may occur in a number of
  325. cases if when this occurs
  326. i. user presses stop --> self._execmode == 'STOPPING' :
  327. _nextRule() returns an error which in turn triggers the
  328. stopping of the current transformation
  329. ii. user disables debugging --> self._execmode == 'PAUSE' :
  330. self._execmode is restored to its previous value and execution
  331. continues as planned (i.e., _nextRule() returns with the next
  332. rule to run)
  333. iii. user presses play/step --> self._execmode == 'PLAY/STEP' :
  334. same behaviour as ii.
  335. 4. call its nextStep() function to get the next transformation step
  336. a) if the step is not an object (i.e., is an application code)
  337. i. if there is more than 1 loaded transformation context,
  338. j. pop the newly completed transformation context
  339. jj. if the next context (which may be a "parent" transformation
  340. or the next of multiple loaded transformations) is a parent,
  341. update its last step application info
  342. jjj. make a recursive call to get the next rule to run from
  343. within the "next" context
  344. ii. otherwise, simply return the said application code
  345. b) if the step is an error, return it
  346. c) otherwise, determine the type of the step (via the node's $type
  347. attribute or by inspecting the 'metamodels' array in the provided
  348. .model file)
  349. i. Rules get stored in 'nr' to be later returned
  350. ii. Transformations and Exhausts[Randoms] cause the pushing of a
  351. new transformation context onto self._mtContexts, and of a
  352. recursive call to get the next rule to run from within that
  353. new context
  354. 5. return the rule name and compiled form
  355. NOTE:: in step 4c), while in debug mode, we highlight transformations,
  356. exhausts and rules before recursing on them or returning them,
  357. respectively '''
  358. def _nextRule(self) :
  359. mtc = self._mtContexts[-1]
  360. self._loadRuntimeConfiguration(mtc.getRuntimeConfiguration())
  361. if self._debugOn and not mtc.isTransformationUnderWay() and \
  362. (type(mtc) == MotifContext or type(mtc) == TCoreContext) : # hergin :: motif-integration modify
  363. _execmode = self._execmode
  364. self._execmode = 'PAUSE'
  365. self._aswPrintReq(TC.DEBUGGING_HALT)
  366. self._requestClientDebugWindow(mtc.fname)
  367. self._startDebugProgrammedBreak()
  368. if self._execmode == 'STOPPING' :
  369. return {'$err':'transformation stopped during debugging pause'}
  370. elif self._execmode == 'PAUSE' or \
  371. self._execmode == 'PLAY' or \
  372. self._execmode == 'STEP' :
  373. self._execmode = _execmode
  374. self.bdapiQueue = Queue()
  375. ns = mtc.nextStep()
  376. if ns.__class__ != {}.__class__ :
  377. if len(self._mtContexts) > 1 :
  378. self._mtContexts = self._mtContexts[:-1]
  379. if self._mtContexts[-1].isTransformationUnderWay() :
  380. self._mtContexts[-1].setLastStepApplicationInfo(ns)
  381. return self._nextRule()
  382. else :
  383. return ns
  384. elif '$err' in ns :
  385. return ns['$err']
  386. else :
  387. def highlightUpcomingStep() :
  388. for _mtc in reversed(self._mtContexts) :
  389. if id(_mtc) in self._mtContexts2debugClients :
  390. debugClient = self._mtContexts2debugClients[id(_mtc)]
  391. self._requestNodeHighlight(
  392. debugClient['host'],
  393. debugClient['aswid'],
  394. _mtc.getCurrentStepId())
  395. break
  396. if 'id' in ns :
  397. fulltype = mtc.t['nodes'][ns['id']]['$type']
  398. ''' hergin :: motif-integration start '''
  399. if fulltype == mtc.metamodel+"/CRule":
  400. if self._debugOn :
  401. highlightUpcomingStep()
  402. self._loadTransform(ns['rule'])
  403. return self._nextRule()
  404. elif fulltype.startswith(TC.TCOREMM) or\
  405. fulltype.startswith(TC.MOTIFMM):
  406. if self._debugOn :
  407. highlightUpcomingStep()
  408. return ns
  409. ''' hergin :: motif-integration end '''
  410. elif fulltype == TC.TRANSFMM+'/Rule' :
  411. if self._debugOn :
  412. highlightUpcomingStep()
  413. return {'fname':ns['fname'],
  414. 'cr':self._compiler.compileRule(None,ns['fname'])}
  415. #Enactment OpenModel blob, pathToFormalism is present is MM, but not used here,
  416. #for functionality of opening window with formalisms is in WriteModel.
  417. #pathToFormalism should be removed from MM for OpenModel (was not removed due to
  418. #language evolution).
  419. elif fulltype == TC.TRANSFMM+'/OpenModel' :
  420. if self._debugOn :
  421. highlightUpcomingStep()
  422. fname = mtc.t['nodes'][ns['id']]['pathToModel']['value']
  423. #formalism = mtc.t['nodes'][ns['id']]['pathToFormalism']['value']
  424. formalism = ""
  425. return {'fname':fname,'formalism':formalism,'rtype':'OpenModel'}
  426. #Enactment WriteModel blob
  427. elif fulltype == TC.TRANSFMM+'/WriteModel' :
  428. if self._debugOn :
  429. highlightUpcomingStep()
  430. fname = mtc.t['nodes'][ns['id']]['pathToModel']['value']
  431. formalism = mtc.t['nodes'][ns['id']]['pathToFormalism']['value']
  432. return {'fname':fname,'formalism':formalism,'rtype':'WriteModel'}
  433. elif fulltype == TC.TRANSFMM+'/Transformation' :
  434. if self._debugOn :
  435. highlightUpcomingStep()
  436. self._loadTransform(ns['fname'])
  437. return self._nextRule()
  438. elif fulltype == TC.TRANSFMM+'/Exhaust' :
  439. self._mtContexts.append( ExhaustContext(mtc.t,ns['id']) )
  440. return self._nextRule()
  441. elif fulltype == TC.TRANSFMM+'/ExhaustRandom' :
  442. self._mtContexts.append( ExhaustContext(mtc.t,ns['id'],self._randomGen) )
  443. return self._nextRule()
  444. else :
  445. ''' hergin :: motif-integration start '''
  446. if 'trafoResult' in ns:
  447. return ns;
  448. ''' hergin :: motif-integration end '''
  449. contents = utils.fread('/users/%s/%s'%(self.username,ns['fname']))
  450. if self._debugOn :
  451. highlightUpcomingStep()
  452. if TC.RULEMM in contents['metamodels'] :
  453. return {'fname':ns['fname'],
  454. 'cr':self._compiler.compileRule(contents,ns['fname'])}
  455. elif TC.TRANSFMM in contents['metamodels'] :
  456. self._transfData[ns['fname']] = contents
  457. self._loadTransform(ns['fname'])
  458. return self._nextRule()
  459. raise ValueError(\
  460. 'file does not contain valid rule or transformation '+\
  461. 'model :: '+ns['fname'])
  462. ''' Enactment do OpenModel magic
  463. '''
  464. def runOpenModelRule(self, fname="",formalism=""):
  465. unload = ""
  466. if not fname:
  467. return (None,TC.FAILED)
  468. else:
  469. if not formalism:
  470. self._aswPrintReq('auto loading model :: '+fname)
  471. try:
  472. with open(os.getcwd()+'/users/'+self.username+fname) as f:
  473. pass
  474. except IOError as e:
  475. self._aswPrintReq('failed opening a file :: '+fname)
  476. return (None,TC.FAILED)
  477. if not self.loadedModel:
  478. method = '_loadModelForTransform'
  479. if len(self._loadedMMs) == 0:
  480. self._loadedMMs = self._compiler._loadedMMs.copy()
  481. diff = self._compiler._loadedMMs.difference(self._loadedMMs)
  482. for u in diff:
  483. unload += u+'.defaultIcons.metamodel,'
  484. else:
  485. method = '_appendModelForTransform'
  486. resp = self._aswCommTools['httpReq'](
  487. 'PUT',
  488. '/GET/console',
  489. {'text':'CLIENT_BDAPI :: '+
  490. '{"func":"'+method+'",'+
  491. ' "args":'+
  492. '{"fname":"'+fname+'",'+
  493. '"unload":"'+unload+'",'+
  494. ' "callback-url":"/__mt/bdapiresp?wid='+
  495. self._aswCommTools['wid']+'"}}'})
  496. resp = self.bdapiQueue.get(block=True,timeout=5000)
  497. if not resp['resp'] == 'ok':
  498. return (None,TC.FAILED)
  499. else:
  500. if not self.loadedModel:
  501. self.loadedModel = True
  502. return (None,TC.SUCCEEDED)
  503. #Was used to open new window with loaded formalisms and pause the transform, now
  504. #this functionality is in WriteMOdel.
  505. else:
  506. pass
  507. # Keep for now....
  508. # self._aswPrintReq('pausing transform')
  509. # self._execmode = 'PAUSE'
  510. # self._aswPrintReq('opening new window for manual step:: '+fname)
  511. # try:
  512. # with open(os.getcwd()+'/users/'+self.username+fname) as f:
  513. # exists = 'true'
  514. # except IOError as e:
  515. # exists = 'false'
  516. # resp = self._aswCommTools['httpReq'](
  517. # 'PUT',
  518. # '/GET/console',
  519. # {'text':'CLIENT_BDAPI :: '+
  520. # '{"func":"_createEmptyModelInNewWindow",'+
  521. # ' "args":'+
  522. # '{"fname":"'+fname+'","exists":"'+exists+'",'+'"formalism":"'+formalism+'",'
  523. # ' "callback-url":"/__mt/bdapiresp?wid='+
  524. # self._aswCommTools['wid']+'"}}'})
  525. # self.loadedModel = False
  526. # return (None,TC.SUCCEEDED)
  527. ''' Enactment do WriteModel magic
  528. '''
  529. def runWriteModelRule(self,fname="",formalism=""):
  530. if not fname:
  531. #this makes next openmodel call a load model instead of append.
  532. #basically use this trick to clean up canvas and have new model:
  533. #first place WriteModel blob without fname,
  534. #followed by OpenModel.
  535. self.loadedModel = False
  536. return (None,TC.SUCCEEDED)
  537. else:
  538. #No formalism specified, save model
  539. if not formalism:
  540. self._aswPrintReq('auto saving model :: '+fname)
  541. resp = self._aswCommTools['httpReq'](
  542. 'PUT',
  543. '/GET/console',
  544. {'text':'CLIENT_BDAPI :: '+
  545. '{"func":"_writeModelAfterTransform",'+
  546. ' "args":'+
  547. '{"fname":"'+fname+'",'+
  548. ' "callback-url":"/__mt/bdapiresp?wid='+
  549. self._aswCommTools['wid']+'"}}'})
  550. #Need to wait for the model to load.
  551. resp = self.bdapiQueue.get(block=True,timeout=5000)
  552. if resp['resp'] == 'ok':
  553. self.loadedModel = False
  554. return (None,TC.SUCCEEDED)
  555. else:
  556. (None,TC.FAILED)
  557. #Formalism specified, open new window, with loaded formalism and/or model.
  558. else:
  559. self._aswPrintReq('pausing transform')
  560. self._execmode = 'PAUSE'
  561. self._aswPrintReq('opening new window for manual step:: '+fname)
  562. try:
  563. with open(os.getcwd()+'/users/'+self.username+fname) as f:
  564. #open existing model
  565. exists = 'true'
  566. except IOError as e:
  567. #or save model with the fname provided
  568. exists = 'false'
  569. resp = self._aswCommTools['httpReq'](
  570. 'PUT',
  571. '/GET/console',
  572. {'text':'CLIENT_BDAPI :: '+
  573. '{"func":"_createEmptyModelInNewWindow",'+
  574. ' "args":'+
  575. '{"fname":"'+fname+'","exists":"'+exists+'",'+'"formalism":"'+formalism+'",'
  576. ' "callback-url":"/__mt/bdapiresp?wid='+
  577. self._aswCommTools['wid']+'"}}'})
  578. self.loadedModel = False
  579. return (None,TC.SUCCEEDED)
  580. '''
  581. synchronously save 1 changelog into self._changelogs '''
  582. def onchangelog(self,c) :
  583. self._lock.acquire()
  584. self._changelogs.append(c)
  585. self._lock.release()
  586. '''
  587. causes the execution of the current transformation(s) to pause (by
  588. preventing _play()'s next call to _step(), if any) '''
  589. def pause(self) :
  590. self._execmode = 'PAUSE'
  591. if not self.incUpdates:
  592. req = self.buildEditHttpReq(self.globalDeltas)
  593. self.globalDeltas = []
  594. resp = self._aswCommTools['httpReq']('POST','/batchEdit',req)
  595. if not utils.isHttpSuccessCode(resp['statusCode']) :
  596. self.stop()
  597. self._aswPrintReq(TC.REMOTE_APPLICATION_FAILURE + resp['reason'])
  598. return
  599. self._handleChangelogs()
  600. '''
  601. play()
  602. calls _play() if it isn't already running (i.e., if we're already in PLAY
  603. mode, a timed call to _play() has already been placed) and if there isn't
  604. already another thread currently paused on _debugProgrammedBreak... if
  605. there is such a thread, it is unpaused (via _stopDebugProgrammedBreak())
  606. and the current thread terminates immediately
  607. _play()
  608. schedules an action for when feedback from the last step is received...
  609. the action is
  610. 1. return if we're no longer in PLAY mode
  611. 2. take one _step()
  612. 3. schedule a recursive call to _play() in TC.INTER_RULE_DELAY
  613. seconds '''
  614. def play(self) :
  615. self.start_time = clock()
  616. if self._execmode == 'STOPPED':
  617. self._randomGen = Random(0)
  618. if self._execmode != 'PLAY' :
  619. self._execmode = 'PLAY'
  620. if not self._stopDebugProgrammedBreak() :
  621. self._play()
  622. def _play(self) :
  623. if self.incUpdates:
  624. self._doWhenLastStepFeedbackReceived(
  625. lambda : self._execmode == 'PLAY' and \
  626. self._step() and \
  627. utils.setTimeout(TC.INTER_RULE_DELAY,self._play))
  628. else:
  629. self._doWhenLastStepFeedbackReceived(
  630. lambda : self._execmode == 'PLAY' and \
  631. self._step() and \
  632. self._play())
  633. '''
  634. associate the newly created debugging window described by 'clientInfo'
  635. to the ModelTransformationContext that requested its creation '''
  636. def registerDebugClient(self,clientInfo) :
  637. clientInfo = json.loads(clientInfo)
  638. for mtc in reversed(self._mtContexts) :
  639. if hasattr(mtc,'fname') and mtc.fname == clientInfo['fname'] :
  640. self._mtContexts2debugClients[id(mtc)] = clientInfo
  641. '''
  642. request a new atompm client via the client backdoor API... the new client
  643. will be loaded with the specified model and sufficient information to
  644. identify and communicate with the client will be POSTed to the callback
  645. url '''
  646. def _requestClientDebugWindow(self,fname) :
  647. return self._aswCommTools['httpReq'](
  648. 'PUT',
  649. '/GET/console',
  650. {'text':'CLIENT_BDAPI :: '+
  651. '{"func":"_loadModelInNewWindow",'+
  652. ' "args":'+
  653. '{"fname":"'+fname+'",'+
  654. ' "callback-url":"/__mt/debugClient?wid='+
  655. self._aswCommTools['wid']+'"}}'})
  656. '''
  657. request that the specified node from the specified atompm instance be
  658. highlighted '''
  659. def _requestNodeHighlight(self,host,aswid,asid,timeout=5000) :
  660. return utils.httpReq(
  661. 'PUT',
  662. host,
  663. '/GET/console?wid='+aswid,
  664. {'text':'CLIENT_BDAPI :: '+
  665. '{"func":"_highlight",'+
  666. ' "args":'+
  667. '{"asid":"'+asid+'",'+
  668. ' "timeout":'+str(timeout)+'}}'})
  669. ''' hergin :: motif-integration :: START :: put this to outside of step function '''
  670. ''' also added self '''
  671. '''
  672. go through a rule's deltas and (1) produce a batchEdit request, and
  673. (2) undo them
  674. NOTE: we undo the deltas for 2 reasons
  675. 1. so that changes become driven by asworker changelogs (like in
  676. csworkers)
  677. 2. so that we don't need to figure out which entries in received
  678. changelogs correspond to user-operations and which ones
  679. correspond to the effects of the constructed batchEdit
  680. NOTE: since we sometimes need to use the result from one request as the
  681. parameter of another (i.e., create a node, update *it*), we use
  682. the 'mknodes' map to remember which requests created which new
  683. nodes... this also allows to know which nodes already exist and
  684. which ones were created by the last rule
  685. NOTE: because creation of connector nodes and their linking to their
  686. ends is described in non-contiguous deltas, we use the 'mknodes'
  687. map to remember incomplete connector creation requests until the
  688. appropriate MKEDGE deltas are encountered '''
  689. def buildEditHttpReq(self,deltas) :
  690. reqs = []
  691. mknodes = {}
  692. neighborhood = None
  693. '''
  694. construct an atompmId given a node... the result will be
  695. a) a 'macro' to be replaced by the result of an earlier request
  696. within the batchEdit, if the node was created by the last rule
  697. b) the atompmId stored within the node, if the node already has
  698. a counter-part in atompm '''
  699. def atompmInstanceId(node) :
  700. if node[HC.GUID] in mknodes :
  701. return '$'+str(mknodes[node[HC.GUID]])+'$'
  702. else :
  703. return node['$atompmId']
  704. for d in deltas :
  705. if d['op'] == 'RMNODE' :
  706. reqs.append({\
  707. 'method':'DELETE',
  708. 'uri':d['attrs'][HC.FULLTYPE]+'/'+\
  709. d['attrs']['$atompmId']+'.instance'})
  710. elif d['op'] == 'MKNODE' :
  711. mknodes[d['guid']] = len(reqs)
  712. node = self._M.vs[self._M.get_node(d['guid'])]
  713. if neighborhood == None :
  714. neighborhood = map(
  715. lambda n: n[HC.FULLTYPE]+'/'+n['$atompmId']+'.instance',
  716. d['neighborhood'])
  717. if node[HC.CONNECTOR_TYPE] :
  718. reqs.append({\
  719. 'method':'POST',
  720. 'uri':node[HC.FULLTYPE]+'.type',
  721. 'reqData':
  722. {'src':None,
  723. 'dest':None,
  724. 'hitchhiker':
  725. {'segments':None,
  726. 'asSrc':None,
  727. 'asDest':None,
  728. 'neighborhood':neighborhood}}})
  729. else :
  730. reqs.append({\
  731. 'method':'POST',
  732. 'uri':node[HC.FULLTYPE]+'.type',
  733. 'reqData':{'hitchhiker':{'neighborhood':neighborhood}}})
  734. elif d['op'] == 'RMEDGE' :
  735. pass
  736. elif d['op'] == 'MKEDGE' :
  737. def isConnectorMKNODE(req):
  738. return 'dest' in req['reqData']
  739. if d['guid1'] in mknodes :
  740. req = reqs[ mknodes[d['guid1']] ]
  741. if isConnectorMKNODE(req) :
  742. node2 = self._M.vs[self._M.get_node(d['guid2'])]
  743. id = atompmInstanceId(node2)
  744. req['reqData']['dest'] = \
  745. req['reqData']['hitchhiker']['asDest'] = \
  746. node2[HC.FULLTYPE]+'/'+id+'.instance'
  747. if d['guid2'] in mknodes :
  748. req = reqs[ mknodes[d['guid2']] ]
  749. if isConnectorMKNODE(req) :
  750. node1 = self._M.vs[self._M.get_node(d['guid1'])]
  751. id = atompmInstanceId(node1)
  752. req['reqData']['src'] = \
  753. req['reqData']['hitchhiker']['asSrc'] = \
  754. node1[HC.FULLTYPE]+'/'+id+'.instance'
  755. elif d['op'] == 'CHATTR' :
  756. node = self._M.vs[self._M.get_node(d['guid'])]
  757. id = atompmInstanceId(node)
  758. reqs.append({\
  759. 'method':'PUT',
  760. 'uri':node[HC.FULLTYPE]+'/'+id+'.instance',
  761. 'reqData':{'changes':{d['attr']:d['new_val']}}})
  762. elif d['op'] == 'LOADMM' :
  763. reqs.append({\
  764. 'method':'PUT',
  765. 'uri':'/current.metamodels',
  766. 'reqData':
  767. {'mm':'/%s%s.metamodel'%(self.username,d['name'])}})
  768. if self.incUpdates:
  769. for d in reversed(deltas) :
  770. if d['op'] == 'RMNODE' :
  771. newNodeIndex = self._M.add_node(newNodeGuid=d['attrs'][HC.GUID])
  772. for attr,val in d['attrs'].iteritems() :
  773. self._M.vs[newNodeIndex][attr] = val
  774. elif d['op'] == 'MKNODE' :
  775. node = self._M.vs[self._M.get_node(d['guid'])]
  776. self._M.delete_nodes([node.index])
  777. elif d['op'] == 'RMEDGE' :
  778. node1 = self._M.vs[self._M.get_node(d['guid1'])]
  779. node2 = self._M.vs[self._M.get_node(d['guid2'])]
  780. self._M.add_edges([(node1.index, node2.index)])
  781. elif d['op'] == 'MKEDGE' :
  782. pass
  783. elif d['op'] == 'CHATTR' :
  784. node = self._M.vs[self._M.get_node(d['guid'])]
  785. node[d['attr']] = d['old_val']
  786. elif d['op'] == 'LOADMM' :
  787. pass
  788. ''' hergin :: motif-integration modify: succeeded rule name + time '''
  789. #reqs.append({\
  790. # 'method':'PUT',
  791. # 'uri':'/GET/console',
  792. # 'reqData':{'text':TC.RULE_SUCCESS_MSG+" ("+self._mtContexts[-1]._lastStep['alias']+":"+self._mtContexts[-1]._lastStep['name']+") in "+str(self._mtContexts[-1]._lastStep['time'])}})
  793. # 'reqData':{'text':TC.RULE_SUCCESS_MSG}})
  794. return reqs
  795. ''' hergin :: motif-integration :: END :: put this to outside of step function '''
  796. '''
  797. author: hergin
  798. sendAndApplyDelta()
  799. If debug mode:
  800. Sends and applies the inputted deltas to the model and UI instance.
  801. else:
  802. Collect deltas in a globalDeltas variable to handle later
  803. '''
  804. def sendAndApplyDelta(self,deltas):
  805. if self.incUpdates:
  806. req = self.buildEditHttpReq(deltas)
  807. resp = self._aswCommTools['httpReq']('POST','/batchEdit',req)
  808. if not utils.isHttpSuccessCode(resp['statusCode']) :
  809. self.stop()
  810. self._aswPrintReq(TC.REMOTE_APPLICATION_FAILURE + resp['reason'])
  811. return
  812. self._handleChangelogs()
  813. else:
  814. self.globalDeltas.extend(deltas)
  815. self.packet.deltas = []
  816. '''
  817. step()
  818. wrapper around _step() that ensures that step requests from user are
  819. ignored when in PLAY mode, and that valid requests only go through (i.e.,
  820. actually call _step()) when feedback for the last step is received...
  821. moreover, as is the case for play(), if there is already another thread
  822. currently paused on _debugProgrammedBreak, it is unpaused (via
  823. _stopDebugProgrammedBreak()) and the current thread terminates immediately
  824. _step()
  825. fetch and run next rule
  826. 1. fetch next rule
  827. a) if next rule is not a {} (i.e., all available transformations have
  828. terminated and _nextRule() returned the resulting application code),
  829. report application code and stop()
  830. b) if an error is returned, report it and stop()
  831. c) otherwise,
  832. i. run rule (returns (deltas|error,applicationInfo))
  833. ii. set ran rule's application info
  834. iii. if rule was n/a, report this
  835. iii. if rule failed, report this and error
  836. iii. otherwise,
  837. j. construct a batchEdit operation based on the rule's effects,
  838. and unfo the said effects
  839. jj. send off the batchEdit
  840. jjj. stop() if the batchEdit fails
  841. NOTE: this function assumes that feedback for the last step has already
  842. been received '''
  843. def step(self) :
  844. if not hasattr(self, 'start_time'):
  845. self.start_time = clock()
  846. if self._execmode == 'PLAY' :
  847. pass
  848. else :
  849. if self._execmode == 'STOPPED':
  850. self._randomGen = Random(0)
  851. self._execmode = 'STEP'
  852. if not self._stopDebugProgrammedBreak() :
  853. self._doWhenLastStepFeedbackReceived(self._step)
  854. def _step(self) :
  855. '''
  856. run the specified rule and return a tuple describing its execution '''
  857. def runRule(r) :
  858. ''' hergin :: motif-integration start '''
  859. #self._aswPrintReq('launching rule :: '+r['fname'])
  860. #ar = NDARule(r['cr']['lhs'],r['cr']['rhs'],rng=self._randomGen)
  861. mtc = self._mtContexts[-1]
  862. if mtc.metamodel == TC.MOTIFMM or mtc.metamodel == TC.TCOREMM:
  863. ar = r['rule']
  864. else:
  865. ar = NDARule(r['cr']['lhs'],r['cr']['rhs'],rng=self._randomGen,sendAndApplyDeltaFunc=self.sendAndApplyDelta)
  866. if mtc.nextInput == "packetIn":
  867. startTime=clock()
  868. self.packet = ar.packet_in(self.packet)
  869. mtc.setLastStepExecTime(clock()-startTime)
  870. elif mtc.nextInput == "nextIn":
  871. startTime=clock()
  872. self.packet = ar.next_in(self.packet)
  873. mtc.setLastStepExecTime(clock()-startTime)
  874. elif mtc.nextInput == "cancelIn":
  875. startTime=clock()
  876. self.packet = ar.cancelIn(self.packet)
  877. mtc.setLastStepExecTime(clock()-startTime)
  878. elif mtc.nextInput == "successIn":
  879. startTime=clock()
  880. self.packet = ar.success_in(self.packet)
  881. mtc.setLastStepExecTime(clock()-startTime)
  882. ''' hergin :: motif-integration end '''
  883. if ar.is_success :
  884. return (self.packet.deltas,TC.SUCCEEDED)
  885. elif not ar.is_success :
  886. ''' hergin :: motif-integration start (Some terminology fixed) '''
  887. if ar.exception :
  888. return (str(ar.exception),TC.EXCEPTION)
  889. else :
  890. return (None,TC.FAILED)
  891. ''' hergin :: motif-integration end '''
  892. try :
  893. nr = self._nextRule()
  894. except Exception :
  895. nr = {'$err':traceback.format_exc()}
  896. ''' hergin :: motif-integration start TRAFO RESULT: in case of a CRule_end, pop it from context and continue the rest '''
  897. while 'trafoResult' in nr:
  898. if len(self._mtContexts)==1:
  899. if not self.incUpdates and self.sendDeltas:
  900. ''' hergin TO BE MODIFIED - release mode will change '''
  901. req = self.buildEditHttpReq(self.globalDeltas)
  902. self.globalDeltas = []
  903. resp = self._aswCommTools['httpReq']('POST','/batchEdit',req)
  904. if not utils.isHttpSuccessCode(resp['statusCode']) :
  905. self.stop()
  906. self._aswPrintReq(TC.REMOTE_APPLICATION_FAILURE + resp['reason'])
  907. return
  908. self._handleChangelogs()
  909. self._aswPrintReq(TC.TRANSFORMATION_DONE+nr['trafoResult']+" in "+str(self._mtContexts[-1].totalExecutionTime/1000.0)+" seconds, in total "+str((clock()-self.start_time)/1000.0))
  910. self.stop()
  911. return
  912. else:
  913. prevTrafo=self._mtContexts.pop()
  914. self._mtContexts[-1].setLastStepExecTime(prevTrafo.totalExecutionTime)
  915. self._mtContexts[-1].setLastStepApplicationInfo(nr['trafoResult'])
  916. try :
  917. nr = self._nextRule()
  918. except Exception :
  919. nr = {'$err':traceback.format_exc()}
  920. if nr.__class__ != {}.__class__ :
  921. self._aswPrintReq(TC.TRANSFORMATION_DONE + nr)
  922. self.stop()
  923. return
  924. elif '$err' in nr :
  925. self._aswPrintReq(TC.NO_NEXT_RULE+nr['$err'])
  926. self._stop()
  927. return
  928. else :
  929. if 'rtype' in nr:
  930. type = nr['rtype']
  931. if type == 'OpenModel':
  932. (res,ai) = self.runOpenModelRule(nr['fname'],nr['formalism'])
  933. elif type == 'WriteModel':
  934. (res,ai) = self.runWriteModelRule(nr['fname'],nr['formalism'])
  935. self._mtContexts[-1].setLastStepApplicationInfo(ai)
  936. self._mtContexts[-1].setLastStepFeedbackReceived()
  937. return True
  938. else:
  939. (res,ai) = runRule(nr)
  940. self._mtContexts[-1].setLastStepApplicationInfo(ai)
  941. if ai == TC.FAILED and self.incUpdates:
  942. ''' hergin :: motif-integration modify (which rule is not succeeded) '''
  943. self._aswPrintReq(TC.RULE_FAILURE_MSG+" ("+self._mtContexts[-1]._lastStep['alias']+":"+self._mtContexts[-1]._lastStep['name']+")")
  944. elif ai == TC.EXCEPTION and self.incUpdates:
  945. self._aswPrintReq(TC.RULE_EXCEPTION_MSG + res)
  946. else :
  947. ''' hergin :: motif-integration :: start '''
  948. mtc = self._mtContexts[-1]
  949. if self.incUpdates:
  950. if mtc.metamodel == TC.MOTIFMM or mtc.metamodel == TC.TCOREMM:
  951. self._aswPrintReq(TC.RULE_SUCCESS_MSG+" ("+self._mtContexts[-1]._lastStep['alias']+":"+self._mtContexts[-1]._lastStep['name']+")")
  952. else:
  953. self._aswPrintReq(TC.RULE_SUCCESS_MSG)
  954. self._mtContexts[-1].setLastStepFeedbackReceived()
  955. ''' hergin :: motif-integration :: end '''
  956. return True
  957. '''
  958. stop()
  959. sets self._execmode to STOPPING and schedules a call to _stop() for when
  960. feedback from the last step is received... being in STOPPING mode implies
  961. that the _play() loop, if any, will be broken, and that incoming user
  962. requests will be rejected... in the case where there is already another
  963. thread currently paused on _debugProgrammedBreak, it is unpaused (via
  964. _stopDebugProgrammedBreak()) which leads to the transformation being
  965. stopped from within that thread; the current thread terminates immediately
  966. _stop()
  967. 1. restores self._mtContexts to right after the user loaded his
  968. transformation(s) (i.e., to before we actually [partially] ran it)
  969. 2. resets self._mtContexts2debugClients
  970. 3. sends a console message to notify the user that the transformation has
  971. stopped
  972. 4. sets self._execmode to STOPPED (i.e., we're done STOPPING and can
  973. handle new requests) '''
  974. def isStopped(self) : return self._execmode == 'STOPPED'
  975. def isStopping(self) : return self._execmode == 'STOPPING'
  976. def stop(self) :
  977. if not self.incUpdates:
  978. req = self.buildEditHttpReq(self.globalDeltas)
  979. self.globalDeltas = []
  980. resp = self._aswCommTools['httpReq']('POST','/batchEdit',req)
  981. if not utils.isHttpSuccessCode(resp['statusCode']) :
  982. self.stop()
  983. self._aswPrintReq(TC.REMOTE_APPLICATION_FAILURE + resp['reason'])
  984. return
  985. self._handleChangelogs()
  986. self._execmode = 'STOPPING'
  987. #Used for enactment, prevents open being append.
  988. self.loadedModel = False
  989. if not self._stopDebugProgrammedBreak() :
  990. self._doWhenLastStepFeedbackReceived(self._stop)
  991. def _stop(self) :
  992. self._mtContexts = []
  993. for fname in self._userTransfs :
  994. self._loadTransform(fname)
  995. self._mtContexts2debugClients = {}
  996. self._aswPrintReq(TC.TRANSFORMATION_STOPPED)
  997. self._execmode = 'STOPPED'
  998. '''
  999. enter a "programmed debugging pause" (i.e. unset the _debugProgrammedBreak
  1000. flag and block until another thread resets it '''
  1001. def _startDebugProgrammedBreak(self) :
  1002. self._debugProgrammedBreak.clear()
  1003. self._debugProgrammedBreak.wait()
  1004. '''
  1005. if the _debugProgrammedBreak flag is not set (this can only happen when
  1006. another thread unset it to enter a programmed debugging pause), set it and
  1007. return true, otherwise do nothing and return false
  1008. NOTE:: this function returns true iff there is a thread waiting on
  1009. _debugProgrammedBreak '''
  1010. def _stopDebugProgrammedBreak(self) :
  1011. if not self._debugProgrammedBreak.isSet() :
  1012. self._debugProgrammedBreak.set()
  1013. return True
  1014. return False
  1015. '''
  1016. toggle the _debugOn flag and report debugger status to atompm... when
  1017. disabling debugging, any current programmed debugging pauses are stopped
  1018. (and execution resumes normally)
  1019. while in DEBUG MODE,
  1020. . entering a ModelTransformationContext for the first time triggers
  1021. a) setting self._execmode to PAUSE
  1022. b) notifying the user of the "programmed debugging pause"
  1023. c) spawning of a new atompm instance loaded with the relevant
  1024. transformation model
  1025. d) entering programmed debugging pause that can be broken by
  1026. _stopDebugProgrammedBreak() which is triggered by
  1027. i. disabling transformation debugging; OR
  1028. ii. pressing "play", "step" or "stop"
  1029. . before running a rule, it or its enclosing ExhaustContext's associated
  1030. atompm node is highlighted '''
  1031. def toggleDebugMode(self) :
  1032. self._debugOn = not self._debugOn
  1033. if self._debugOn :
  1034. self._aswPrintReq(TC.DEBUGGING_ON)
  1035. else :
  1036. self._stopDebugProgrammedBreak()
  1037. self._aswPrintReq(TC.DEBUGGING_OFF)
  1038. '''
  1039. #Perform flat reachability analysis
  1040. #This is what creates the simple reachability graph.
  1041. #If the Petri nets are disjoint there will be several PNs "modules" that will be calculated in parallel.
  1042. def PNFull(self,fname='',dot=False):
  1043. #if not self.modules:
  1044. self._handleChangelogs()
  1045. self.modules = {}
  1046. self.modStart = time()
  1047. disjoint = self._M.decompose(mode=ig.WEAK)
  1048. #Let's compile PN graph out of generic himesis graph and create dictionary with unique IDs
  1049. barier = barrier(len(disjoint)+1) #barier, modules + synch thread
  1050. for mod in disjoint:
  1051. queue = Queue()
  1052. uid = uuid.uuid4()
  1053. key = str(uid)[:str(uid).index('-')]
  1054. module = PnModule(self.toPN(mod),queue, barier,True)
  1055. self.modules [ key] = module
  1056. module.start()
  1057. barier.wait()
  1058. print 'Time elapsed flat analysis %f'%(time() - self.modStart)
  1059. self.modules [ key].summary()
  1060. self.modStart = time()
  1061. #do the test of reachability here.
  1062. #self.reachabilityTestFlat()
  1063. print 'Find place elapsed flat analysis %f'%(time() - self.modStart)
  1064. for key,mod in self.modules.items():
  1065. if dot:
  1066. #can take too long to plot for large state space and/or hand dot binary
  1067. #here we output the reacability graph in svg
  1068. mod.graph(key=key,fname=fname)
  1069. else:
  1070. #here we output reachability graph in xml
  1071. mod.reachtoxml(fname,key)
  1072. #peroform modular analysis, use at your own risk. Toolbar does not enable this, experimental
  1073. def analyzePN(self):
  1074. #First lets break model into submodels
  1075. self.modStart = time()
  1076. #if not self.modules:
  1077. self.modules = {}
  1078. disjoint = self._M.decompose(mode=ig.WEAK)
  1079. barier = barrier(len(disjoint)+1) #barier, modules + synch thread
  1080. for mod in disjoint:
  1081. queue = Queue()
  1082. module = PnModule(self.toPN(mod),queue, barier)
  1083. self.modules [ module.getKey()] = module
  1084. module.start()
  1085. barier.wait() #wait till all threads stop doing first phase.
  1086. M0 = []
  1087. TFenabled = []
  1088. TF = {}
  1089. work = []
  1090. tofire = []
  1091. for key,mod in self.modules.items():
  1092. TF[key] = mod.TFS
  1093. M0.append('%s-%d'%(key,0))
  1094. ind=0
  1095. self.sg = synchgraph(len(self.modules),M0)
  1096. work.append(M0)
  1097. res={}
  1098. while work:
  1099. M = work.pop()
  1100. for key,mod in self.modules.items():
  1101. TFenabled.append( mod.getEnabledTFs())
  1102. tofire = reduce(set.intersection,map(set,TFenabled))
  1103. for key,mod in self.modules.items():
  1104. mod.que.put(tofire)
  1105. barier.wait() #lets wait for threads, they may building local graphs still
  1106. end = False
  1107. for key,mod in self.modules.items():
  1108. if not mod.result:
  1109. end = True
  1110. res[key] = mod.result #got results now produce new states for syngraph and archs.
  1111. if not end:
  1112. work.append(M)
  1113. else:
  1114. #self.sg.graph()
  1115. for key,mod in self.modules.items():
  1116. mod.SC()
  1117. mod.graph()
  1118. mod.que.put(['@exit'])
  1119. self.sg.markSCC(self.modules)
  1120. self.sg.graph()
  1121. print '---------------------------'
  1122. print 'Time elapsed modular analysis %f'%(time() - self.modStart)
  1123. for key,mod in self.modules.items():
  1124. mod.summary()
  1125. print '---------------------------'
  1126. print 'Synch graph:'
  1127. self.sg.summary()
  1128. print '---------------------------'
  1129. self.modStart = time()
  1130. self.reachabilityTestModular()
  1131. print 'Find place elapsed modular analysis %f'%(time() - self.modStart)
  1132. return
  1133. #main result
  1134. fr ={}
  1135. to = {}
  1136. for key,value in res.items():
  1137. for k,v in value.items():
  1138. if not k in fr:
  1139. fr[k] = []
  1140. fr[k].append([])
  1141. fr[k].append([])
  1142. fr[k][0].append(v[0])
  1143. fr[k][1].append(v[1])
  1144. from_prod=[]
  1145. to_prod = []
  1146. T=None
  1147. for key,value in fr.items():
  1148. T = key
  1149. #res = list(list(itertools.product(*value[0]))[0])
  1150. from_prod.append(list( list(itertools.product(*value[0]))))
  1151. to_prod.append(list (list(itertools.product (*value[1]))))
  1152. self.sg.addMarkingBatch(T,from_prod,to_prod)
  1153. #ENABLE
  1154. self.sg.graph(ind)
  1155. ind+=1;
  1156. #
  1157. # self.sg.addMarking(from_prod[i],to_prod[i],T)
  1158. res.clear()
  1159. TFenabled = []
  1160. #ENABLE
  1161. #self.sg.graph()
  1162. TM = {} #dict by tf transition inside slists of lists of start and end states
  1163. #compile into our PN representation
  1164. def toPN(self,mod):
  1165. oldtonew = {}
  1166. g = ig.Graph(0,directed=True)
  1167. for node in mod.vs:
  1168. if not node['$type'].find('Place') == -1 or not node['$type'].find('Transition') == -1:
  1169. g.add_vertices(1)
  1170. index = g.vcount()-1
  1171. oldtonew[node.index]=index
  1172. g.vs[index]['name'] = node['name']
  1173. if not node['$type'].find('Place') == -1:
  1174. g.vs[index]['type'] = 'P'
  1175. g.vs[index]['nbTokens'] = node['nbTokens']
  1176. elif not node['$type'].find('Transition') == -1:
  1177. g.vs[index]['type'] = 'T'
  1178. g.vs[index]['fusion'] = node['fusion']
  1179. elif not node['$type'].find('P2T') == -1:
  1180. node['type'] = 'P2T'
  1181. elif not node['$type'].find('T2P') == -1:
  1182. node['type'] = 'T2P'
  1183. #Let's connect
  1184. P2T = mod.vs.select(type_eq = 'P2T')
  1185. T2P = mod.vs.select(type_eq = 'T2P')
  1186. for p2t in P2T:
  1187. to = mod.successors(p2t.index)
  1188. fr = mod.predecessors(p2t.index)
  1189. try:
  1190. p2tid = g.get_eid(oldtonew[fr[0]],oldtonew[to[0]])
  1191. except:
  1192. g.add_edges([(oldtonew[fr[0]],oldtonew[to[0]])])
  1193. p2tid = g.get_eid(oldtonew[fr[0]],oldtonew[to[0]])
  1194. g.es[p2tid]['weight'] = p2t['weight']
  1195. else:
  1196. old = int(g.es[p2tid]['weight'])
  1197. g.es[p2tid]['weight'] = old + int(p2t['weight'])
  1198. for t2p in T2P:
  1199. to = mod.successors(t2p.index)
  1200. fr = mod.predecessors(t2p.index)
  1201. try:
  1202. t2pid = g.get_eid(oldtonew[fr[0]],oldtonew[to[0]])
  1203. except:
  1204. g.add_edges([(oldtonew[fr[0]],oldtonew[to[0]])])
  1205. t2pid = g.get_eid(oldtonew[fr[0]],oldtonew[to[0]])
  1206. g.es[t2pid]['weight'] = t2p['weight']
  1207. else:
  1208. old = int(g.es[t2pid]['weight'])
  1209. g.es[t2pid]['weight'] = old + int(t2p['weight'])
  1210. #dot graph of our petri net, not quite himesis.
  1211. #self.graphPN('pn', g)
  1212. return g
  1213. def isReachableFlat(self,state,key=None):
  1214. if not key:
  1215. if self.modules.values()[0].reachable(state):
  1216. return True
  1217. else:
  1218. return False
  1219. else:
  1220. if self.modules[key].reachable(state):
  1221. return True
  1222. else:
  1223. return False
  1224. def reachabilityTestModular(self):
  1225. aa = 'a'
  1226. bb = 'b'
  1227. moda = {}
  1228. modb = {}
  1229. moda['a2'] = 1
  1230. #moda['a4'] = 4
  1231. #moda['a3'] = 1
  1232. modb['b1'] = 1
  1233. modb['b3'] = 1
  1234. statea = []
  1235. stateb = []
  1236. statea.append(moda)
  1237. stateb.append(modb)
  1238. if not self.isReachableFlat(statea,aa):
  1239. print 'Modular state %s%s not reachable'%(statea,stateb)
  1240. return False
  1241. if not self.isReachableFlat(stateb,bb):
  1242. print 'Modular state %s%s not reachable'%(statea,stateb)
  1243. return False
  1244. scca = self.modules[aa].reachableMod(statea)
  1245. print 'A SCC of ancestors %s'%scca
  1246. sccb = self.modules[bb].reachableMod(stateb)
  1247. print 'B SCC of ancestors %s'%sccb
  1248. result = list( list(itertools.product(scca,sccb)))
  1249. for node in result:
  1250. v = []
  1251. a = 'a-%d'%node[0]
  1252. b = 'b-%d'%node[1]
  1253. v.append(a)
  1254. v.append(b)
  1255. id = self.sg.statePresentReach(v)
  1256. if not id == -1:
  1257. print 'Modular state %s%s reachable'%(statea,stateb)
  1258. return True
  1259. print 'Modular state %s%s not reachable'%(statea,stateb)
  1260. return False
  1261. def reachabilityTestFlat(self):
  1262. moda = {}
  1263. modb = {}
  1264. moda['a1'] = 1
  1265. moda['a4'] = 2
  1266. #moda['a3'] = 1
  1267. #modb['b3'] = 2
  1268. modb['b5'] = 3
  1269. state = []
  1270. state.append(moda)
  1271. state.append(modb)
  1272. if self.isReachableFlat(state):
  1273. print 'Flat state %s reachable'%state
  1274. else:
  1275. print 'Flat state %s not reachable'%state
  1276. def graph(self,key,g):
  1277. vattr=''
  1278. eattr = ''
  1279. nodes = {}
  1280. graph = pydot.Dot(key, graph_type='digraph')
  1281. dateTag = datetime.datetime.now().strftime("%Y-%b-%d_%H-%M-%S")
  1282. for v in g.vs:
  1283. vattr +='('
  1284. i = len(v['M'])
  1285. for key,value in v['M'].items():
  1286. vattr += '%s-%s'%(key,value)
  1287. if not i-1 == 0:
  1288. vattr+=','
  1289. i -=1
  1290. vattr +=')'
  1291. nodes[v.index] = pydot.Node(vattr)
  1292. graph.add_node(nodes[v.index])
  1293. vattr = ''
  1294. for e in g.es:
  1295. graph.add_edge(pydot.Edge(nodes[e.source],nodes[e.target],label=e['T']))
  1296. graph.write_svg('graphs/STATE%s%s.svg'%(key,dateTag))
  1297. #graph.write_png('graphs/STATE%s%s.png'%(key,dateTag))
  1298. #use this one to output your PN net in a svg graph to analyze structure
  1299. #and verify that compilation from Himesis to PN went fine, since we collaps
  1300. #repeated edges.
  1301. def graphPN(self,key,g):
  1302. vattr=''
  1303. eattr = ''
  1304. nodes = {}
  1305. graph = pydot.Dot(key, graph_type='digraph')
  1306. dateTag = datetime.datetime.now().strftime("%Y-%b-%d_%H-%M-%S")
  1307. for v in g.vs:
  1308. for at in v.attributes():
  1309. if not v[at] == None:
  1310. vattr += '%s->%s\n'%(at,v[at])
  1311. nodes[v.index] = pydot.Node(vattr)
  1312. graph.add_node(nodes[v.index])
  1313. vattr = ''
  1314. for e in g.es:
  1315. graph.add_edge(pydot.Edge(nodes[e.source],nodes[e.target],label=e['weight']))
  1316. graph.write_svg('graphs/PN%s%s.svg'%(key,dateTag))
  1317. '''