PageRenderTime 39ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/src/python/PSetTweaks/PSetTweak.py

https://github.com/dmwm/WMCore
Python | 448 lines | 280 code | 53 blank | 115 comment | 26 complexity | a053e5e5b81587630cd4335eb1d358c7 MD5 | raw file
  1. #!/usr/bin/env python
  2. """
  3. _PSetTweaks_
  4. Record a set of tweakable parameters from a CMSSW Configuration in a CMSSW
  5. independent python structure
  6. """
  7. from future import standard_library
  8. standard_library.install_aliases()
  9. from builtins import object, map, range
  10. from past.builtins import basestring
  11. from future.utils import viewitems, viewvalues
  12. import imp
  13. import inspect
  14. import json
  15. import pickle
  16. import sys
  17. from functools import reduce
  18. class PSetHolder(object):
  19. """
  20. _PSetHolder_
  21. Dummy PSet object used to construct the Tweak object to mimic
  22. the config structure
  23. """
  24. def __init__(self, psetName):
  25. self.psetName_ = psetName
  26. self.parameters_ = []
  27. # //
  28. # // Assistant lambda functions
  29. #//
  30. childPSets = lambda x: [ value for value in viewvalues(x.__dict__)
  31. if value.__class__.__name__ == "PSetHolder" ]
  32. childParameters = lambda p, x: [ "%s.%s" % (p,i) for i in x.parameters_ ]
  33. recursiveGetattr = lambda obj, attr: reduce(getattr, attr.split("."), obj)
  34. def parameterIterator(obj):
  35. """
  36. _parameterIterator_
  37. Util to iterate through the parameters in a PSetHolder
  38. """
  39. x = None
  40. for x in childParameters(obj):
  41. yield getattr(obj, x)
  42. def psetIterator(obj):
  43. """
  44. _psetIterator_
  45. Util to iterate through the child psets in a PSetHolder
  46. """
  47. for x in childPSets(obj):
  48. yield x
  49. class PSetLister(object):
  50. """
  51. _PSetLister_
  52. Operator to decompose the PSet structure into a
  53. more sequence like get up
  54. """
  55. def __init__(self):
  56. self.psets = []
  57. self.parameters = {}
  58. self.queue = []
  59. def __call__(self, pset):
  60. """
  61. _operator(PSetHolder)_
  62. recursively traverse all parameters in this and all child
  63. PSets
  64. """
  65. self.queue.append(pset.psetName_)
  66. psetPath = ".".join(self.queue)
  67. self.psets.append(psetPath)
  68. params = childParameters(psetPath, pset)
  69. self.parameters[psetPath] = params
  70. list(map(self, childPSets(pset)))
  71. self.queue.pop(-1)
  72. class JSONiser(object):
  73. """
  74. _JSONiser_
  75. Util class to build a json dictionary structure from the PSet tree
  76. and also recover the pset tree from a json structure
  77. """
  78. def __init__(self):
  79. self.json = {}
  80. self.queue = []
  81. self.parameters = {}
  82. def __call__(self, pset, parent = None):
  83. """
  84. _operator(pset)_
  85. operate on pset and substructure to build a json dictionary
  86. """
  87. if parent == None: parent = self.json
  88. thisPSet = parent.get(pset.psetName_, None)
  89. if thisPSet == None:
  90. parent[pset.psetName_] = {}
  91. thisPSet = parent[pset.psetName_]
  92. for param in pset.parameters_:
  93. thisPSet[param] = getattr(pset, param)
  94. thisPSet['parameters_'] = pset.parameters_
  95. for child in childPSets(pset):
  96. self(child, thisPSet)
  97. def dejson(self, dictionary):
  98. """
  99. _dejson_
  100. Convert the json structure back to PSetHolders
  101. """
  102. params = dictionary.get('parameters_', [])
  103. queue = ".".join(self.queue)
  104. for param in params:
  105. self.parameters["%s.%s" % (queue, param)] = dictionary[param]
  106. for key, value in viewitems(dictionary):
  107. if isinstance(value, dict):
  108. self.queue.append(key)
  109. self.dejson(dictionary[key])
  110. self.queue.pop(-1)
  111. class PSetTweak(object):
  112. """
  113. _PSetTweak_
  114. Template object listing the parameters to be edited.
  115. Also provides serialisation functionality and defines the
  116. process + tweak operator to apply the tweaks to a process.
  117. """
  118. def __init__(self):
  119. self.process = PSetHolder("process")
  120. def addParameter(self, attrName, value):
  121. """
  122. _addAttribute_
  123. Add an attribute as process.pset1.pset2.param = value
  124. Value should be the appropriate python type
  125. """
  126. currentPSet = None
  127. paramList = attrName.split(".")
  128. for _ in range(0, len(paramList)):
  129. param = paramList.pop(0)
  130. if param == "process":
  131. currentPSet = self.process
  132. elif len(paramList) > 0:
  133. if not hasattr(currentPSet, param):
  134. setattr(currentPSet, param, PSetHolder(param))
  135. currentPSet = getattr(currentPSet, param)
  136. else:
  137. setattr(currentPSet, param, value)
  138. currentPSet.parameters_.append(param)
  139. def getParameter(self, paramName):
  140. """
  141. _getParameter_
  142. Get value of the parameter with the name given of the
  143. form process.module...
  144. """
  145. if not paramName.startswith("process"):
  146. msg = "Invalid Parameter Name: %s\n" % paramName
  147. msg += "Parameter must start with process"
  148. raise RuntimeError(msg)
  149. return recursiveGetattr(self, paramName)
  150. def __iter__(self):
  151. """
  152. _iterate_
  153. Loop over all parameters in the tweak, returning the
  154. parameter name as a . delimited path and the value
  155. """
  156. lister = PSetLister()
  157. lister(self.process)
  158. for pset in lister.psets:
  159. for param in lister.parameters[pset]:
  160. yield param , self.getParameter(param)
  161. def psets(self):
  162. """
  163. _psets_
  164. Generator function to yield the PSets in the tweak
  165. """
  166. lister = PSetLister()
  167. lister(self.process)
  168. for pset in lister.psets:
  169. yield pset
  170. def __str__(self):
  171. """string repr for debugging etc"""
  172. result = ""
  173. for x,y in self:
  174. result += "%s = %s\n" % (x, y)
  175. return result
  176. def setattrCalls(self, psetPath):
  177. """
  178. _setattrCalls_
  179. Generate setattr call for each parameter in the pset structure
  180. Used for generating python format
  181. """
  182. result = {}
  183. current = None
  184. last = None
  185. psets = psetPath.split(".")
  186. for _ in range(0, len(psets)):
  187. pset = psets.pop(0)
  188. last = current
  189. if current == None:
  190. current = pset
  191. else:
  192. current += ".%s" % pset
  193. if last != None:
  194. result[current] = "setattr(%s, \"%s\", PSetHolder(\"%s\"))" % (
  195. last, pset, pset)
  196. return result
  197. def pythonise(self):
  198. """
  199. _pythonise_
  200. return this object as python format
  201. """
  202. src = inspect.getsourcelines(PSetHolder)
  203. result = ""
  204. for line in src[0]:
  205. result += line
  206. result += "\n\n"
  207. result += "# define PSet Structure\n"
  208. result += "process = PSetHolder(\"process\")\n"
  209. setattrCalls = {}
  210. for pset in self.psets():
  211. setattrCalls.update(self.setattrCalls(pset))
  212. order = sorted(setattrCalls.keys())
  213. for call in order:
  214. if call == "process": continue
  215. result += "%s\n" % setattrCalls[call]
  216. result += "# set parameters\n"
  217. for param, value in self:
  218. psetName = param.rsplit(".", 1)[0]
  219. paramName = param.rsplit(".", 1)[1]
  220. if isinstance(value, basestring):
  221. value = "\"%s\"" % value
  222. result += "setattr(%s, \"%s\", %s)\n" % (
  223. psetName, paramName, value)
  224. result += "%s.parameters_.append(\"%s\")\n" % (psetName, paramName)
  225. return result
  226. def jsonise(self):
  227. """
  228. _jsonise_
  229. return json format of this tweak
  230. """
  231. jsoniser = JSONiser()
  232. jsoniser(self.process)
  233. result = json.dumps(jsoniser.json)
  234. return result
  235. def jsondictionary(self):
  236. """
  237. _jsondictionary_
  238. return the json layout dictionary, rather than stringing it
  239. """
  240. jsoniser = JSONiser()
  241. jsoniser(self.process)
  242. return jsoniser.json
  243. def simplejsonise(self):
  244. """
  245. _simplejsonise_
  246. return simple json format of this tweak
  247. E.g.:
  248. {"process.maxEvents.input": 1200, "process.source.firstRun": 1, "process.source.firstLuminosityBlock": 59965}
  249. """
  250. jsoniser = JSONiser()
  251. jsoniser.dejson(self.jsondictionary())
  252. result = json.dumps(jsoniser.parameters)
  253. return result
  254. def persist(self, filename, formatting="python"):
  255. """
  256. _persist_
  257. Save this object as either python, json or pickle
  258. """
  259. if formatting not in ("python", "json", "pickle", "simplejson"):
  260. msg = "Unsupported Format: %s" % formatting
  261. raise RuntimeError(msg)
  262. if formatting == "python":
  263. with open(filename, 'w') as handle:
  264. handle.write(self.pythonise())
  265. if formatting == "json":
  266. with open(filename, "w") as handle:
  267. handle.write(self.jsonise())
  268. if formatting == "pickle":
  269. with open(filename, "wb") as handle:
  270. pickle.dump(self, handle)
  271. if formatting == "simplejson":
  272. with open(filename, "w") as handle:
  273. handle.write(self.simplejsonise())
  274. return
  275. def unpersist(self, filename, formatting=None):
  276. """
  277. _unpersist_
  278. Load data from file provided, if format is not specified, guess
  279. it based on file extension
  280. """
  281. if formatting == None:
  282. fileSuffix = filename.rsplit(".", 1)[1]
  283. if fileSuffix == "py":
  284. formatting = "python"
  285. if fileSuffix == "pkl":
  286. formatting = "pickle"
  287. if fileSuffix == "json":
  288. formatting = "json"
  289. if formatting not in ("python", "json", "pickle"):
  290. msg = "Unsupported Format: %s" % formatting
  291. raise RuntimeError(msg)
  292. if formatting == "pickle":
  293. with open(filename, 'rb') as handle:
  294. unpickle = pickle.load(handle)
  295. self.process.__dict__.update(unpickle.__dict__)
  296. if formatting == "python":
  297. modRef = imp.load_source('tempTweak', filename)
  298. lister = PSetLister()
  299. lister(modRef.process)
  300. for pset in lister.psets:
  301. for param in lister.parameters[pset]:
  302. self.addParameter(param , recursiveGetattr(modRef, param))
  303. del modRef, sys.modules['tempTweak']
  304. if formatting == "json":
  305. with open(filename, 'r') as handle:
  306. jsonContent = handle.read()
  307. jsoniser = JSONiser()
  308. jsoniser.dejson(json.loads(jsonContent))
  309. for param, value in viewitems(jsoniser.parameters):
  310. self.addParameter(param , value)
  311. def reset(self):
  312. """
  313. _reset_
  314. Reset pset holder process
  315. """
  316. del self.process
  317. self.process = PSetHolder("process")
  318. def makeTweakFromJSON(jsonDictionary):
  319. """
  320. _makeTweakFromJSON_
  321. Make a tweak instance and populate it from a dictionary JSON
  322. structure
  323. """
  324. jsoniser = JSONiser()
  325. jsoniser.dejson(jsonDictionary)
  326. tweak = PSetTweak()
  327. for param, value in viewitems(jsoniser.parameters):
  328. tweak.addParameter(param , value)
  329. return tweak