Salome HOME
Yacs decorator
[modules/yacs.git] / src / py2yacs / yacsdecorator.py
1 #!/usr/bin/env python3
2 # Copyright (C) 2006-2020  CEA/DEN, EDF R&D
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20 import sys
21
22 # this is a pointer to the module object instance itself.
23 this_module = sys.modules[__name__]
24
25 class OutputPort:
26   def __init__(self, yacs_node, yacs_port):
27     self.yacs_node = yacs_node
28     self.yacs_port = yacs_port
29
30 class LeafNodeType:
31   def __init__(self, path, fn_name, inputs, outputs):
32     self.path = path
33     self.fn_name = fn_name
34     self.inputs = inputs
35     self.outputs = outputs
36     self.number = 0
37
38   def newName(self):
39     name = self.fn_name + "_" + str(self.number)
40     self.number += 1
41     return name
42
43   def createNewNode(self, inputs):
44     """
45     inputs : dict {input_name:value}
46     """
47     generator = getGenerator()
48     output_ports = generator.createScriptNode(self, inputs)
49     return output_ports
50
51 def leaf(f):
52   """
53   Decorator for python scripts.
54   """
55   if this_module._exec_mode == this_module._default_mode:
56     return f
57   co = f.__code__
58   import py2yacs
59   props = py2yacs.function_properties(co.co_filename, co.co_name)
60   nodeType = LeafNodeType(co.co_filename, co.co_name,
61                           props.inputs, props.outputs)
62   def my_func(*args, **kwargs):
63     if len(args) + len(kwargs) != len(nodeType.inputs):
64       mes = "Wrong number of arguments when calling function '{}'.\n".format(
65                                                                nodeType.fn_name)
66       mes += " {} arguments expected and {} arguments found.\n".format(
67                                   len(nodeType.inputs), len(args) + len(kwargs))
68       raise Exception(mes)
69     idx = 0
70     args_dic = {}
71     for a in args:
72       args_dic[nodeType.inputs[idx]] = a
73       idx += 1
74     for k,v in kwargs.items():
75       args_dic[k] = v
76     if len(args_dic) != len(nodeType.inputs):
77       mes="Wrong arguments when calling function {}.\n".format(nodeType.fn_name)
78       raise Exception(mes)
79     return nodeType.createNewNode(args_dic)
80   return my_func
81
82 def bloc(f):
83   """
84   Decorator for blocs.
85   """
86   #co = f.__code__
87   #print("bloc :", co.co_name)
88   #print("  file:", co.co_filename)
89   #print("  line:", co.co_firstlineno)
90   #print("  args:", co.co_varnames)
91   return f
92
93 def foreach(f):
94   """
95   Decorator to generate foreach blocs
96   """
97   if this_module._exec_mode == this_module._default_mode:
98     return default_foreach(f)
99   elif this_module._exec_mode == this_module._yacs_mode:
100     return yacs_foreach(f)
101
102 def default_foreach(f):
103   def my_func(lst):
104     result = []
105     for e in lst:
106       result.append(f(e))
107     t_result = result
108     if len(result) > 0 :
109       if type(result[0]) is tuple:
110         # transform the list of tuples in a tuple of lists
111         l_result = []
112         for e in result[0]:
113           l_result.append([])
114         for t in result:
115           idx = 0
116           for e in t:
117             l_result[idx].append(e)
118             idx += 1
119         t_result = tuple(l_result)
120     return t_result
121   return my_func
122
123 def yacs_foreach(f):
124   #co = f.__code__
125   #import yacsvisit
126   #props = yacsvisit.main(co.co_filename, co.co_name)
127   def my_func(input_list):
128     fn_name = f.__code__.co_name
129     generator = getGenerator()
130     sample_port = generator.beginForeach(fn_name, input_list)
131     output_list = f(sample_port)
132     output_list = generator.endForeach(output_list)
133     return output_list
134   return my_func
135
136 class SchemaGenerator():
137   """
138   Link to Salome for YACS schema generation.
139   """
140   def __init__(self):
141     import SALOMERuntime
142     SALOMERuntime.RuntimeSALOME.setRuntime()
143     self.runtime = SALOMERuntime.getSALOMERuntime()
144     self.proc = self.runtime.createProc("GeneratedSchema")
145     self.proc.setProperty("executor","workloadmanager")
146     self.containers = {}
147     self.pyobjtype = self.runtime.getTypeCode("pyobj")
148     self.seqpyobjtype = self.runtime.getTypeCode("seqpyobj")
149     self.bloc_stack = [self.proc]
150     self.name_index = 0 # used to ensure unique names
151
152   def newName(self, name):
153     new_name = name + "_" + str(self.name_index)
154     self.name_index += 1
155     return new_name
156
157   def getContextName(self):
158     context_name = ""
159     if len(self.bloc_stack) > 1:
160       # We are in a block
161       block_path = ".".join([ b.getName() for b in self.bloc_stack[1:] ])
162       context_name = block_path + "."
163     return context_name
164
165   def getContainer(self, container_type):
166     """
167     A new container may be created if it does not already exist for this type.
168     """
169     if container_type not in self.containers:
170       cont=self.proc.createContainer(container_type,"Salome")
171       #cont.setProperty("nb_proc_per_node","0")
172       cont.setProperty("type","multi")
173       cont.usePythonCache(False)
174       cont.attachOnCloning()
175       self.containers[container_type] = cont
176     return self.containers[container_type]
177
178   def createScript(self, file_path, function_name, inputs, outputs):
179     import inspect
180     stack = inspect.stack()
181     stack_info = "Call stack\n"
182     # skip the first 4 levels in the stack
183     for level in stack[4:-1] :
184       info = inspect.getframeinfo(level[0])
185       stack_info += "file: {}, line: {}, function: {}, context: {}\n".format(
186         info.filename, info.lineno, info.function, info.code_context)
187      
188     if len(outputs) == 0:
189       result = ""
190     elif len(outputs) == 1:
191       result = "{} = ".format(outputs[0])
192     else:
193       result = ",".join(outputs)
194       result += " = "
195
196     if len(inputs) == 0:
197       params = ""
198     elif len(inputs) == 1:
199       params = "{} ".format(inputs[0])
200     else:
201       params = ",".join(inputs)
202     
203     script = """'''
204 {call_stack}
205 '''
206 import yacstools
207 study_function = yacstools.getFunction("{file_path}", "{function_name}")
208 {result}study_function({parameters})
209 """.format(call_stack=stack_info,
210            file_path=file_path,
211            function_name=function_name,
212            result=result,
213            parameters=params)
214     return script
215
216   def createScriptNode(self, leaf, input_values):
217     node_name = leaf.newName()
218     file_path = leaf.path
219     function_name = leaf.fn_name
220     inputs = leaf.inputs # names
221     outputs = leaf.outputs # names
222     script = self.createScript(file_path, function_name, inputs, outputs)
223     container = self.getContainer("generic_cont")
224     new_node = self.runtime.createScriptNode("Salome", node_name)
225     new_node.setContainer(container)
226     new_node.setExecutionMode("remote")
227     new_node.setScript(script)
228     self.bloc_stack[-1].edAddChild(new_node)
229     # create ports
230     for p in inputs:
231       new_node.edAddInputPort(p, self.pyobjtype)
232     output_obj_list = []
233     for p in outputs:
234       port = new_node.edAddOutputPort(p, self.pyobjtype)
235       output_obj_list.append(OutputPort(new_node, port))
236     # create links
237     for k,v in input_values.items():
238       input_port = new_node.getInputPort(k)
239       if isinstance(v, OutputPort):
240         self.proc.edAddLink(v.yacs_port, input_port)
241         self.addCFLink(v.yacs_node, new_node)
242         #self.proc.edAddCFLink(v.yacs_node, new_node)
243       else:
244         input_port.edInitPy(v)
245     # return output ports
246     result = None
247     if len(output_obj_list) == 1 :
248       result = output_obj_list[0]
249     elif len(output_obj_list) > 1 :
250       result = tuple(output_obj_list)
251     return result
252
253   def beginForeach(self, fn_name, input_values):
254     foreach_name = self.newName(fn_name)
255     new_foreach = self.runtime.createForEachLoopDyn(foreach_name,
256                                                     self.pyobjtype)
257     #new_foreach = self.runtime.createForEachLoop(foreach_name, self.pyobjtype)
258     #new_foreach.edGetNbOfBranchesPort().edInitInt(1)
259     self.bloc_stack[-1].edAddChild(new_foreach)
260     bloc_name = "bloc_"+foreach_name
261     new_block = self.runtime.createBloc(bloc_name)
262     new_foreach.edAddChild(new_block)
263     sample_port = new_foreach.edGetSamplePort()
264     input_list_port = new_foreach.edGetSeqOfSamplesPort()
265     if isinstance(input_values, OutputPort):
266       # we need a conversion node pyobj -> seqpyobj
267       conversion_node = self.runtime.createScriptNode("Salome",
268                                                       "input_"+foreach_name)
269       port_name = "val"
270       input_port = conversion_node.edAddInputPort(port_name, self.pyobjtype)
271       output_port = conversion_node.edAddOutputPort(port_name,
272                                                     self.seqpyobjtype)
273       conversion_node.setExecutionMode("local") # no need for container
274       # no script, the same variable for input and output
275       conversion_node.setScript("")
276       self.bloc_stack[-1].edAddChild(conversion_node)
277       self.proc.edAddLink(input_values.yacs_port, input_port)
278       self.addCFLink(input_values.yacs_node, conversion_node)
279       self.proc.edAddLink(output_port, input_list_port)
280       # No need to look for ancestors. Both nodes are on the same level.
281       self.proc.edAddCFLink(conversion_node, new_foreach)
282     else:
283       input_list_port.edInitPy(list(input_values))
284     self.bloc_stack.append(new_foreach)
285     self.bloc_stack.append(new_block)
286     return OutputPort(new_foreach, sample_port)
287
288   def endForeach(self, outputs):
289     self.bloc_stack.pop() # remove the block
290     for_each_node = self.bloc_stack.pop() # remove the foreach
291     converted_ret = None
292     if outputs is not None:
293       # We need a conversion node seqpyobj -> pyobj
294       if type(outputs) is tuple:
295         list_out = list(outputs)
296       else:
297         list_out = [outputs]
298       conversion_node_name = "output_" + for_each_node.getName()
299       conversion_node = self.runtime.createScriptNode("Salome",
300                                                       conversion_node_name)
301       conversion_node.setExecutionMode("local") # no need for container
302       conversion_node.setScript("")
303       self.bloc_stack[-1].edAddChild(conversion_node)
304       list_ret = []
305       idx_name = 0 # for unique port names
306       for port in list_out :
307         if isinstance(port, OutputPort):
308           port_name = port.yacs_port.getName() + "_" + str(idx_name)
309           idx_name += 1
310           input_port = conversion_node.edAddInputPort(port_name,
311                                                       self.seqpyobjtype)
312           output_port = conversion_node.edAddOutputPort(port_name,
313                                                         self.pyobjtype)
314           self.proc.edAddLink(port.yacs_port, input_port)
315           list_ret.append(OutputPort(conversion_node, output_port))
316         else:
317           list_ret.append(port)
318       self.proc.edAddCFLink(for_each_node, conversion_node)
319       if len(list_ret) > 1 :
320         converted_ret = tuple(list_ret)
321       else:
322         converted_ret = list_ret[0]
323     return converted_ret
324
325   def dump(self, file_path):
326     self.proc.saveSchema(file_path)
327
328   def addCFLink(self, node_from, node_to):
329     commonAncestor = self.proc.getLowestCommonAncestor(node_from, node_to)
330     if node_from.getName() != commonAncestor.getName() :
331       link_from = node_from
332       while link_from.getFather().getName() != commonAncestor.getName() :
333         link_from = link_from.getFather()
334       link_to = node_to
335       while link_to.getFather().getName() != commonAncestor.getName() :
336         link_to = link_to.getFather()
337       self.proc.edAddCFLink(link_from, link_to)
338     else:
339       # from node is ancestor of to node. No CF link needed.
340       pass
341
342 _generator = None
343
344 _default_mode = "Default"
345 _yacs_mode = "YACS"
346 _exec_mode = _default_mode
347
348 def getGenerator():
349   """
350   Get the singleton object.
351   """
352   if this_module._generator is None:
353     if this_module._exec_mode == this_module._yacs_mode:
354       this_module._generator = SchemaGenerator()
355   return this_module._generator
356
357 def activateYacsMode():
358   this_module._exec_mode = this_module._yacs_mode
359
360 def activateDefaultMode():
361   this_module._exec_mode = this_module._default_mode
362
363 def finalize(path):
364   if this_module._exec_mode == this_module._yacs_mode :
365     getGenerator().dump(path)
366
367