ptcal.py 63 KB

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