Coverage for mlprodict/grammar/grammar_sklearn/grammar/gactions.py: 94%
271 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-04 02:28 +0100
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-04 02:28 +0100
1"""
2@file
3@brief Action definition.
4"""
5import numpy
6from .api_extension import AutoAction
7from .gtypes import (
8 MLType, MLNumTypeFloat32, MLNumTypeFloat64,
9 MLTensor, MLNumTypeInt32, MLNumTypeInt64, MLNumTypeBool)
12class MLAction(AutoAction):
13 """
14 Base class for every action.
15 """
17 def __init__(self, inputs, output, name, children=None):
18 """
19 @param inputs type of inputs
20 @param output output type
21 @param name a name which identifies the action
22 @param children actions used to compute this one
23 """
24 if not isinstance(inputs, list):
25 raise TypeError(
26 'inputs must be a list of MLType.') # pragma: no cover
27 for t in inputs:
28 if not isinstance(t, MLType):
29 raise TypeError( # pragma: no cover
30 f"Every input must be a MLType not '{type(t)}'.")
31 if not isinstance(output, MLType):
32 raise TypeError('output must be of MLType.') # pragma: no cover
33 self.inputs = inputs
34 self.output = output
35 self.name = name
36 self.children = children if children is not None else []
37 for child in self.children:
38 if not isinstance(child, MLAction): # pragma: no cover
39 raise TypeError("All children must be of type MLAction")
41 def execute(self, **kwargs):
42 """
43 Computes the action. Returns the output.
44 """
45 # It must be overwritten.
46 self.children_results_ = [child.execute(
47 **kwargs) for child in self.children]
48 for v, tv in zip(self.children_results_, self.inputs):
49 tv.validate(v)
51 @property
52 def ChildrenResults(self):
53 """
54 Return the last execution results.
55 """
56 return self.children_results_
58 def enumerate_variables(self):
59 """
60 Enumerates all variables.
61 """
62 for child in self.children:
63 for var in child.enumerate_variables():
64 yield var
66 def graph_execution(self):
67 """
68 Returns a formated string which retruns the outputs.
69 """
70 rows = []
71 rows.append("-- BEGIN {0} {3} id={1} output={2}".format(
72 self.name, id(self), self.output._cache, getattr(self, "comment", "")))
73 for i, ch in enumerate(self.children):
74 gr = ch.graph_execution()
75 temp = [" " + li for li in gr.split("\n")]
76 temp[0] = f" {i}-" + temp[0][4:]
77 rows.extend(temp)
78 rows.append(
79 f"-- END {self.name} -- output={self.output._cache}")
80 return "\n".join(rows)
82 @AutoAction.cache
83 def _export_json(self, hook=None, result_name=None):
84 val = {"output": self.output._export_json()}
85 if self.children:
86 val["action"] = dict(name=self.name,
87 variants=[c._export_json(hook=hook) for c in self.children])
88 else:
89 val["action"] = dict(name=self.name)
90 if self.inputs:
91 val["input"] = [i._export_json(hook=hook)
92 for i in self.inputs]
93 return val
95 @AutoAction.cache
96 def _export_c(self, hook=None, result_name=None):
97 if result_name is None:
98 raise ValueError(
99 "result_name must not be None") # pragma: no cover
100 rows = []
101 rows.append(f"// {id(self)}-{self.name} - children")
102 names = []
103 if self.children:
104 for i, c in enumerate(self.children):
105 rname = f"{result_name}{getattr(self, 'cname', '')}{i}"
106 dc = c._export_c(hook=hook, result_name=rname)
107 if not dc['cache']:
108 rows.append(dc['code'])
109 names.append(dc['result_name'])
110 rows.append(f"// {id(self)}-{self.name} - itself")
111 res = "\n".join(rows)
112 return {'code': res, 'result_name': result_name, 'child_names': names}
115class MLActionCst(MLAction):
116 """
117 Constant
118 """
120 def __init__(self, cst, inout_type=None, comment=None):
121 """
122 @param cst constant
123 @param inout_type type
124 @param comment comment
125 """
126 if inout_type is None:
127 inout_type = MLActionCst.guess_type(cst)
128 MLAction.__init__(self, [], inout_type, "cst")
129 inout_type.validate(cst)
130 self.cst = cst
131 self.comment = comment
133 @staticmethod
134 def guess_type(value):
135 """
136 Guesses a type given a value.
137 """
138 if isinstance(value, numpy.float32):
139 return MLNumTypeFloat32()
140 if isinstance(value, numpy.float64):
141 return MLNumTypeFloat64()
142 if isinstance(value, (int, numpy.int32)):
143 return MLNumTypeInt32()
144 if isinstance(value, (int, numpy.int64)):
145 return MLNumTypeInt64()
146 if isinstance(value, numpy.ndarray):
147 a = numpy.zeros(1, value.dtype)
148 t = MLActionCst.guess_type(a[0])
149 return MLTensor(t, value.shape)
150 raise NotImplementedError( # pragma: no cover
151 f"Not implemented for type '{type(value)}'")
153 def execute(self, **kwargs):
154 MLAction.execute(self, **kwargs)
155 return self.output.validate(self.cst)
157 def graph_execution(self):
158 if self.comment:
159 return f"cst: {self.comment} = {self.cst}"
160 return f"cst: {self.cst}"
162 @AutoAction.cache
163 def _export_json(self, hook=None, result_name=None):
164 res = {"name": "cst",
165 "value": self.output._format_value_json(self.cst, hook=hook)}
166 if hasattr(self, "comment"):
167 res["comment"] = self.comment
168 return res
170 @AutoAction.cache
171 def _export_c(self, hook=None, result_name=None):
172 if result_name is None:
173 raise ValueError("result_name cannot be None.") # pragma: no cover
174 dc = self.output._export_c(hook='declare', result_name=result_name)
175 res = f"{dc['code']} = {self.output._format_value_c(self.cst)};"
176 if self.comment:
177 res += f" // {self.comment}"
178 return {'code': res, 'result_name': result_name}
181class MLActionVar(MLActionCst):
182 """
183 Variable. The constant is only needed to guess the
184 variable type.
185 """
187 def __init__(self, value, name, inout_type=None):
188 """
189 @param value value
190 @param name variable name
191 @param inout_type type
192 """
193 MLActionCst.__init__(self, value, inout_type)
194 self.name = "var"
195 self.name_var = name
197 def execute(self, **kwargs):
198 MLAction.execute(self, **kwargs)
199 if self.name_var not in kwargs:
200 raise KeyError( # pragma: no cover
201 f"Unable to find variable name '{self.name_var}'")
202 return self.output.validate(kwargs[self.name_var])
204 def enumerate_variables(self):
205 """
206 Enumerates itself.
207 """
208 yield self
210 def graph_execution(self):
211 return f"var: {self.name_var} = {self.name} ({self.output._cache})"
213 @AutoAction.cache
214 def _export_json(self, hook=None, result_name=None):
215 return {"name": "var", "value": self.name_var}
217 @AutoAction.cache
218 def _export_c(self, hook=None, result_name=None):
219 if result_name is None:
220 raise ValueError( # pragma: no cover
221 "result_name must not be None")
222 dc = self.output._export_c(hook='typeref', result_name=result_name)
223 res = f"{dc['code']} = {self.name_var};"
224 return {'code': res, 'result_name': result_name}
227class MLActionFunctionCall(MLAction):
228 """
229 Any function call.
230 """
232 def __init__(self, name, output, *acts):
233 """
234 @param name function name
235 @param output type
236 @param *acts list of arguments
237 """
238 for act in acts:
239 if not isinstance(act, MLAction):
240 raise TypeError( # pragma: no cover
241 f"All element of acts must be MLAction not '{type(act)}'.")
242 MLAction.__init__(self, [act.output for act in acts],
243 output, name, children=acts)
244 self.cname = 'c'
246 def _optional_parameters(self):
247 """
248 Returns additional parameters to add the function call.
249 """
250 return None
252 @AutoAction.cache
253 def _export_c(self, hook=None, result_name=None):
254 if result_name is None:
255 raise ValueError(
256 "result_name must not be None") # pragma: no cover
257 dcf = MLAction._export_c(self, hook=hook, result_name=result_name)
258 rows = [dcf['code']]
259 fcall = ", ".join(dcf['child_names'])
260 add = self._optional_parameters() # pylint: disable=E1128
261 if add is not None:
262 fcall = ", ".join([fcall, add])
263 dc = self.output._export_c(hook='declare', result_name=result_name)
264 rows.append(dc['code'] + ";")
265 ep = self.output._byref_c()
266 type_list = "_".join(c.output.CTypeSingle for c in self.children)
267 rows.append(f"{self.name}_{type_list}({ep}{result_name}, {fcall});")
268 rows.append(f"// {id(self)}-{self.name} - done")
269 # Addition printf to debug the C++ code.
270 # rows.append('printf("C++ {1} %f\\n", {0});'.format(result_name, self.name))
271 res = {'code': "\n".join(rows), 'result_name': dcf['result_name']}
272 return res
275class MLActionBinary(MLAction):
276 """
277 Any binary operation.
278 """
280 def __init__(self, act1, act2, name):
281 """
282 @param act1 first element
283 @param act2 second element
284 @param name operator name
285 """
286 if not isinstance(act1, MLAction):
287 raise TypeError("act1 must be MLAction.") # pragma: no cover
288 if not isinstance(act2, MLAction):
289 raise TypeError("act2 must be MLAction.") # pragma: no cover
290 MLAction.__init__(self, [act1.output, act2.output], act2.output, name,
291 children=[act1, act2])
293 @AutoAction.cache
294 def _export_c(self, hook=None, result_name=None):
295 if result_name is None:
296 raise ValueError(
297 "result_name must not be None") # pragma: no cover
298 dc = MLAction._export_c(self, hook=hook, result_name=result_name)
299 rows = [dc['code']]
300 dc2 = self.output._export_c(hook='type')
301 op = "{2} {0} = {0}0 {1} {0}1;".format(
302 result_name, self.name, dc2['code'])
303 rows.append(op)
304 rows.append(f"// {id(self)}-{self.name} - done")
305 return {'code': "\n".join(rows), 'result_name': result_name}
308class MLActionUnary(MLAction):
309 """
310 Any binary operation.
311 """
313 def __init__(self, act1, name):
314 """
315 @param act1 element
316 @param name operator name
317 """
318 if not isinstance(act1, MLAction):
319 raise TypeError("act1 must be MLAction.") # pragma: no cover
320 MLAction.__init__(self, [act1.output], act1.output, name,
321 children=[act1])
323 @AutoAction.cache
324 def _export_c(self, hook=None, result_name=None):
325 if result_name is None:
326 raise ValueError( # pragma: no cover
327 "result_name must not be None")
328 dc = MLAction._export_c(self, hook=hook, result_name=result_name)
329 rows = [dc['code']]
330 op = "auto {0} = {1} {0}0;".format(result_name, self.name)
331 rows.append(op)
332 rows.append(f"// {id(self)}-{self.name} - done")
333 return {'code': "\n".join(rows), 'result_name': result_name}
336class MLActionConcat(MLActionFunctionCall):
337 """
338 Concatenate number of arrays into an array.
339 """
341 def __init__(self, act1, act2):
342 """
343 @param act1 first element
344 @param act2 second element
345 """
346 if not isinstance(act1, MLAction):
347 raise TypeError("act1 must be MLAction.") # pragma: no cover
348 if not isinstance(act2, MLAction):
349 raise TypeError("act2 must be MLAction.") # pragma: no cover
350 n1 = (1 if isinstance(act1.output, (MLNumTypeFloat32, MLNumTypeFloat64))
351 else act1.output.dim[0])
352 n2 = (1 if isinstance(act2.output, (MLNumTypeFloat32, MLNumTypeFloat64))
353 else act2.output.dim[0])
354 MLActionFunctionCall.__init__(self, "concat", MLTensor(
355 act1.output.__class__(), (n1 + n2,)), act1, act2)
357 def execute(self, **kwargs):
358 """
359 Concatenation
360 """
361 MLActionFunctionCall.execute(self, **kwargs)
362 res = self.ChildrenResults
363 return self.output.validate(numpy.array(res))
366class MLActionCast(MLActionUnary):
367 """
368 Cast into another type.
369 """
371 def __init__(self, act1, new_type):
372 """
373 @param act1 element
374 @param new_type new type
375 """
376 MLActionUnary.__init__(self, act1, "cast")
377 self.output = new_type
379 def execute(self, **kwargs):
380 MLActionUnary.execute(self, **kwargs)
381 res = self.ChildrenResults
382 return self.output.validate(self.output.cast(res[0]))
384 @AutoAction.cache
385 def _export_c(self, hook=None, result_name=None):
386 raise NotImplementedError( # pragma: no cover
387 "Not enough information to do it here.")
390class MLActionIfElse(MLAction):
391 """
392 Addition
393 """
395 def __init__(self, cond, act1, act2, check_type=True, comment=None):
396 """
397 @param cond condition
398 @param act1 first action
399 @param ect2 second action
400 @param check_type check ype
401 @param comment comment
402 """
403 if not isinstance(act1, MLAction):
404 raise TypeError("act1 must be MLAction.") # pragma: no cover
405 if not isinstance(act2, MLAction):
406 raise TypeError("act2 must be MLAction.") # pragma: no cover
407 if not isinstance(cond, MLAction):
408 raise TypeError("cond must be MLAction.") # pragma: no cover
409 if not isinstance(cond.output, MLNumTypeBool):
410 raise TypeError( # pragma: no cover
411 f"No boolean condition {type(cond.output)}")
412 if check_type and type(act1.output) != type(act2.output):
413 raise TypeError("Not the same input type {0} != {1}".format( # pragma: no cover
414 type(act1.output), type(act2.output)))
415 MLAction.__init__(self, [cond.output, act1.output, act2.output], act2.output, "if",
416 children=[cond, act1, act2])
417 self.comment = comment
419 def execute(self, **kwargs):
420 self.children_results_ = [
421 self.children[0].execute(**kwargs), None, None]
422 self.inputs[0].validate(self.children_results_[0])
423 if self.children_results_[0]:
424 self.children_results_[1] = self.children[1].execute(**kwargs)
425 self.inputs[1].validate(self.children_results_[1])
426 res = self.children_results_[1]
427 else:
428 self.children_results_[2] = self.children[2].execute(**kwargs)
429 self.inputs[2].validate(self.children_results_[2])
430 res = self.children_results_[2]
431 return self.output.validate(res)
433 @AutoAction.cache
434 def _export_c(self, hook=None, result_name=None):
435 if result_name is None:
436 raise ValueError(
437 "result_name must not be None") # pragma: no cover
438 dc = MLAction._export_c(self, hook=hook, result_name=result_name)
439 rows = [dc['code']]
440 dc2 = self.output._export_c(hook='type')
441 op = "{1} {0} = {0}0 ? {0}1 : {0}2;".format(result_name, dc2['code'])
442 rows.append(op)
443 rows.append(f"// {id(self)}-{self.name} - done")
444 return {'code': "\n".join(rows), 'result_name': result_name}
447class MLActionReturn(MLAction):
448 """
449 Returns a results.
450 """
452 def __init__(self, act):
453 """
454 @param act action to return
455 """
456 MLAction.__init__(self, [act.output],
457 act.output, "return", children=[act])
459 def execute(self, **kwargs):
460 MLAction.execute(self, **kwargs)
461 res = self.ChildrenResults
462 return self.output.validate(res[0])
464 @AutoAction.cache
465 def _export_c(self, hook=None, result_name=None):
466 if len(self.children) != 1:
467 raise ValueError(
468 "Only one result can be returned.") # pragma: no cover
469 if result_name is None:
470 raise ValueError(
471 "result_name must not be None") # pragma: no cover
472 dc = self.children[0]._export_c(hook=hook, result_name=result_name)
473 if not dc['cache']:
474 code = dc['code']
475 else:
476 code = ''
478 add = self.output._copy_c(
479 result_name, result_name[:-1], hook="typeref")
480 code += "\n" + add
481 return {'code': code, 'result_name': result_name}
484class MLActionFunction(MLActionUnary):
485 """
486 A function.
487 """
489 def __init__(self, act, name):
490 """
491 @param act action
492 @param name name
493 """
494 if not isinstance(act, MLActionReturn):
495 raise NotImplementedError( # pragma: no cover
496 "Last result must be MLActionReturn.")
497 MLActionUnary.__init__(self, act, name)
499 def execute(self, **kwargs):
500 MLActionUnary.execute(self, **kwargs)
501 res = self.ChildrenResults
502 return self.output.validate(res[0])
504 @AutoAction.cache
505 def _export_c(self, hook=None, result_name=None):
506 if result_name is None:
507 raise ValueError(
508 "result_name must not be None") # pragma: no cover
509 if len(self.children) != 1:
510 raise ValueError(
511 "The function must return one result.") # pragma: no cover
512 if result_name[-1] == '0':
513 raise ValueError( # pragma: no cover
514 f"result_name '{result_name}' cannot end with 0.")
516 vars = {v.name: v for v in self.enumerate_variables()}
517 vars = [_[1] for _ in list(sorted(vars.items()))]
518 parameters = ", ".join("{0} {1}".format(
519 v.output._export_c(hook='type')['code'], v.name_var) for v in vars)
520 typename = self.children[0].output._export_c(
521 hook='typeref', result_name=result_name)['code']
522 signature = f"int {self.name} ({typename}, {parameters})"
523 dc = MLAction._export_c(self, hook=hook, result_name=result_name)
524 code = dc['code']
525 rows = [signature, "{"]
526 rows.extend(" " + line for line in code.split("\n"))
527 rows.extend(
528 [' return 0;', f" // {id(self)}-{self.name} - done", '}'])
529 return {'code': "\n".join(rows), 'result_name': result_name}