Coverage for mlprodict/grammar/grammar_sklearn/grammar/gactions.py: 94%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

271 statements  

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) 

10 

11 

12class MLAction(AutoAction): 

13 """ 

14 Base class for every action. 

15 """ 

16 

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 "Every input must be a MLType not '{0}'.".format(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") 

40 

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) 

50 

51 @property 

52 def ChildrenResults(self): 

53 """ 

54 Return the last execution results. 

55 """ 

56 return self.children_results_ 

57 

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 

65 

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] = " {0}-".format(i) + temp[0][4:] 

77 rows.extend(temp) 

78 rows.append( 

79 "-- END {0} -- output={1}".format(self.name, self.output._cache)) 

80 return "\n".join(rows) 

81 

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 

94 

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("// {0}-{1} - children".format(id(self), self.name)) 

102 names = [] 

103 if self.children: 

104 for i, c in enumerate(self.children): 

105 rname = "{0}{1}{2}".format( 

106 result_name, getattr(self, "cname", ""), i) 

107 dc = c._export_c(hook=hook, result_name=rname) 

108 if not dc['cache']: 

109 rows.append(dc['code']) 

110 names.append(dc['result_name']) 

111 rows.append("// {0}-{1} - itself".format(id(self), self.name)) 

112 res = "\n".join(rows) 

113 return {'code': res, 'result_name': result_name, 'child_names': names} 

114 

115 

116class MLActionCst(MLAction): 

117 """ 

118 Constant 

119 """ 

120 

121 def __init__(self, cst, inout_type=None, comment=None): 

122 """ 

123 @param cst constant 

124 @param inout_type type 

125 @param comment comment 

126 """ 

127 if inout_type is None: 

128 inout_type = MLActionCst.guess_type(cst) 

129 MLAction.__init__(self, [], inout_type, "cst") 

130 inout_type.validate(cst) 

131 self.cst = cst 

132 self.comment = comment 

133 

134 @staticmethod 

135 def guess_type(value): 

136 """ 

137 Guesses a type given a value. 

138 """ 

139 if isinstance(value, numpy.float32): 

140 return MLNumTypeFloat32() 

141 if isinstance(value, numpy.float64): 

142 return MLNumTypeFloat64() 

143 if isinstance(value, (int, numpy.int32)): 

144 return MLNumTypeInt32() 

145 if isinstance(value, (int, numpy.int64)): 

146 return MLNumTypeInt64() 

147 if isinstance(value, numpy.ndarray): 

148 a = numpy.zeros(1, value.dtype) 

149 t = MLActionCst.guess_type(a[0]) 

150 return MLTensor(t, value.shape) 

151 raise NotImplementedError( # pragma: no cover 

152 "Not implemented for type '{0}'".format(type(value))) 

153 

154 def execute(self, **kwargs): 

155 MLAction.execute(self, **kwargs) 

156 return self.output.validate(self.cst) 

157 

158 def graph_execution(self): 

159 if self.comment: 

160 return "cst: {0} = {1}".format(self.comment, self.cst) 

161 return "cst: {0}".format(self.cst) 

162 

163 @AutoAction.cache 

164 def _export_json(self, hook=None, result_name=None): 

165 res = {"name": "cst", 

166 "value": self.output._format_value_json(self.cst, hook=hook)} 

167 if hasattr(self, "comment"): 

168 res["comment"] = self.comment 

169 return res 

170 

171 @AutoAction.cache 

172 def _export_c(self, hook=None, result_name=None): 

173 if result_name is None: 

174 raise ValueError("result_name cannot be None.") # pragma: no cover 

175 dc = self.output._export_c(hook='declare', result_name=result_name) 

176 res = "{0} = {1};".format( 

177 dc['code'], self.output._format_value_c(self.cst)) 

178 if self.comment: 

179 res += " // {0}".format(self.comment) 

180 return {'code': res, 'result_name': result_name} 

181 

182 

183class MLActionVar(MLActionCst): 

184 """ 

185 Variable. The constant is only needed to guess the 

186 variable type. 

187 """ 

188 

189 def __init__(self, value, name, inout_type=None): 

190 """ 

191 @param value value 

192 @param name variable name 

193 @param inout_type type 

194 """ 

195 MLActionCst.__init__(self, value, inout_type) 

196 self.name = "var" 

197 self.name_var = name 

198 

199 def execute(self, **kwargs): 

200 MLAction.execute(self, **kwargs) 

201 if self.name_var not in kwargs: 

202 raise KeyError( # pragma: no cover 

203 "Unable to find variable name '{0}'".format(self.name_var)) 

204 return self.output.validate(kwargs[self.name_var]) 

205 

206 def enumerate_variables(self): 

207 """ 

208 Enumerates itself. 

209 """ 

210 yield self 

211 

212 def graph_execution(self): 

213 return "var: {0} = {1} ({2})".format(self.name_var, self.name, self.output._cache) 

214 

215 @AutoAction.cache 

216 def _export_json(self, hook=None, result_name=None): 

217 return {"name": "var", "value": self.name_var} 

218 

219 @AutoAction.cache 

220 def _export_c(self, hook=None, result_name=None): 

221 if result_name is None: 

222 raise ValueError( # pragma: no cover 

223 "result_name must not be None") 

224 dc = self.output._export_c(hook='typeref', result_name=result_name) 

225 res = "{0} = {1};".format(dc['code'], self.name_var) 

226 return {'code': res, 'result_name': result_name} 

227 

228 

229class MLActionFunctionCall(MLAction): 

230 """ 

231 Any function call. 

232 """ 

233 

234 def __init__(self, name, output, *acts): 

235 """ 

236 @param name function name 

237 @param output type 

238 @param *acts list of arguments 

239 """ 

240 for act in acts: 

241 if not isinstance(act, MLAction): 

242 raise TypeError( # pragma: no cover 

243 "All element of acts must be MLAction not '{0}'.".format(type(act))) 

244 MLAction.__init__(self, [act.output for act in acts], 

245 output, name, children=acts) 

246 self.cname = 'c' 

247 

248 def _optional_parameters(self): 

249 """ 

250 Returns additional parameters to add the function call. 

251 """ 

252 return None 

253 

254 @AutoAction.cache 

255 def _export_c(self, hook=None, result_name=None): 

256 if result_name is None: 

257 raise ValueError( 

258 "result_name must not be None") # pragma: no cover 

259 dcf = MLAction._export_c(self, hook=hook, result_name=result_name) 

260 rows = [dcf['code']] 

261 fcall = ", ".join(dcf['child_names']) 

262 add = self._optional_parameters() # pylint: disable=E1128 

263 if add is not None: 

264 fcall = ", ".join([fcall, add]) 

265 dc = self.output._export_c(hook='declare', result_name=result_name) 

266 rows.append(dc['code'] + ";") 

267 ep = self.output._byref_c() 

268 type_list = "_".join(c.output.CTypeSingle for c in self.children) 

269 rows.append("{0}_{4}({3}{1}, {2});".format( 

270 self.name, result_name, fcall, ep, type_list)) 

271 rows.append("// {0}-{1} - done".format(id(self), self.name)) 

272 # Addition printf to debug the C++ code. 

273 # rows.append('printf("C++ {1} %f\\n", {0});'.format(result_name, self.name)) 

274 res = {'code': "\n".join(rows), 'result_name': dcf['result_name']} 

275 return res 

276 

277 

278class MLActionBinary(MLAction): 

279 """ 

280 Any binary operation. 

281 """ 

282 

283 def __init__(self, act1, act2, name): 

284 """ 

285 @param act1 first element 

286 @param act2 second element 

287 @param name operator name 

288 """ 

289 if not isinstance(act1, MLAction): 

290 raise TypeError("act1 must be MLAction.") # pragma: no cover 

291 if not isinstance(act2, MLAction): 

292 raise TypeError("act2 must be MLAction.") # pragma: no cover 

293 MLAction.__init__(self, [act1.output, act2.output], act2.output, name, 

294 children=[act1, act2]) 

295 

296 @AutoAction.cache 

297 def _export_c(self, hook=None, result_name=None): 

298 if result_name is None: 

299 raise ValueError( 

300 "result_name must not be None") # pragma: no cover 

301 dc = MLAction._export_c(self, hook=hook, result_name=result_name) 

302 rows = [dc['code']] 

303 dc2 = self.output._export_c(hook='type') 

304 op = "{2} {0} = {0}0 {1} {0}1;".format( 

305 result_name, self.name, dc2['code']) 

306 rows.append(op) 

307 rows.append("// {0}-{1} - done".format(id(self), self.name)) 

308 return {'code': "\n".join(rows), 'result_name': result_name} 

309 

310 

311class MLActionUnary(MLAction): 

312 """ 

313 Any binary operation. 

314 """ 

315 

316 def __init__(self, act1, name): 

317 """ 

318 @param act1 element 

319 @param name operator name 

320 """ 

321 if not isinstance(act1, MLAction): 

322 raise TypeError("act1 must be MLAction.") # pragma: no cover 

323 MLAction.__init__(self, [act1.output], act1.output, name, 

324 children=[act1]) 

325 

326 @AutoAction.cache 

327 def _export_c(self, hook=None, result_name=None): 

328 if result_name is None: 

329 raise ValueError( # pragma: no cover 

330 "result_name must not be None") 

331 dc = MLAction._export_c(self, hook=hook, result_name=result_name) 

332 rows = [dc['code']] 

333 op = "auto {0} = {1} {0}0;".format(result_name, self.name) 

334 rows.append(op) 

335 rows.append("// {0}-{1} - done".format(id(self), self.name)) 

336 return {'code': "\n".join(rows), 'result_name': result_name} 

337 

338 

339class MLActionConcat(MLActionFunctionCall): 

340 """ 

341 Concatenate number of arrays into an array. 

342 """ 

343 

344 def __init__(self, act1, act2): 

345 """ 

346 @param act1 first element 

347 @param act2 second element 

348 """ 

349 if not isinstance(act1, MLAction): 

350 raise TypeError("act1 must be MLAction.") # pragma: no cover 

351 if not isinstance(act2, MLAction): 

352 raise TypeError("act2 must be MLAction.") # pragma: no cover 

353 n1 = (1 if isinstance(act1.output, (MLNumTypeFloat32, MLNumTypeFloat64)) 

354 else act1.output.dim[0]) 

355 n2 = (1 if isinstance(act2.output, (MLNumTypeFloat32, MLNumTypeFloat64)) 

356 else act2.output.dim[0]) 

357 MLActionFunctionCall.__init__(self, "concat", MLTensor( 

358 act1.output.__class__(), (n1 + n2,)), act1, act2) 

359 

360 def execute(self, **kwargs): 

361 """ 

362 Concatenation 

363 """ 

364 MLActionFunctionCall.execute(self, **kwargs) 

365 res = self.ChildrenResults 

366 return self.output.validate(numpy.array(res)) 

367 

368 

369class MLActionCast(MLActionUnary): 

370 """ 

371 Cast into another type. 

372 """ 

373 

374 def __init__(self, act1, new_type): 

375 """ 

376 @param act1 element 

377 @param new_type new type 

378 """ 

379 MLActionUnary.__init__(self, act1, "cast") 

380 self.output = new_type 

381 

382 def execute(self, **kwargs): 

383 MLActionUnary.execute(self, **kwargs) 

384 res = self.ChildrenResults 

385 return self.output.validate(self.output.cast(res[0])) 

386 

387 @AutoAction.cache 

388 def _export_c(self, hook=None, result_name=None): 

389 raise NotImplementedError( # pragma: no cover 

390 "Not enough information to do it here.") 

391 

392 

393class MLActionIfElse(MLAction): 

394 """ 

395 Addition 

396 """ 

397 

398 def __init__(self, cond, act1, act2, check_type=True, comment=None): 

399 """ 

400 @param cond condition 

401 @param act1 first action 

402 @param ect2 second action 

403 @param check_type check ype 

404 @param comment comment 

405 """ 

406 if not isinstance(act1, MLAction): 

407 raise TypeError("act1 must be MLAction.") # pragma: no cover 

408 if not isinstance(act2, MLAction): 

409 raise TypeError("act2 must be MLAction.") # pragma: no cover 

410 if not isinstance(cond, MLAction): 

411 raise TypeError("cond must be MLAction.") # pragma: no cover 

412 if not isinstance(cond.output, MLNumTypeBool): 

413 raise TypeError( # pragma: no cover 

414 "No boolean condition {0}".format(type(cond.output))) 

415 if check_type and type(act1.output) != type(act2.output): 

416 raise TypeError("Not the same input type {0} != {1}".format( # pragma: no cover 

417 type(act1.output), type(act2.output))) 

418 MLAction.__init__(self, [cond.output, act1.output, act2.output], act2.output, "if", 

419 children=[cond, act1, act2]) 

420 self.comment = comment 

421 

422 def execute(self, **kwargs): 

423 self.children_results_ = [ 

424 self.children[0].execute(**kwargs), None, None] 

425 self.inputs[0].validate(self.children_results_[0]) 

426 if self.children_results_[0]: 

427 self.children_results_[1] = self.children[1].execute(**kwargs) 

428 self.inputs[1].validate(self.children_results_[1]) 

429 res = self.children_results_[1] 

430 else: 

431 self.children_results_[2] = self.children[2].execute(**kwargs) 

432 self.inputs[2].validate(self.children_results_[2]) 

433 res = self.children_results_[2] 

434 return self.output.validate(res) 

435 

436 @AutoAction.cache 

437 def _export_c(self, hook=None, result_name=None): 

438 if result_name is None: 

439 raise ValueError( 

440 "result_name must not be None") # pragma: no cover 

441 dc = MLAction._export_c(self, hook=hook, result_name=result_name) 

442 rows = [dc['code']] 

443 dc2 = self.output._export_c(hook='type') 

444 op = "{1} {0} = {0}0 ? {0}1 : {0}2;".format(result_name, dc2['code']) 

445 rows.append(op) 

446 rows.append("// {0}-{1} - done".format(id(self), self.name)) 

447 return {'code': "\n".join(rows), 'result_name': result_name} 

448 

449 

450class MLActionReturn(MLAction): 

451 """ 

452 Returns a results. 

453 """ 

454 

455 def __init__(self, act): 

456 """ 

457 @param act action to return 

458 """ 

459 MLAction.__init__(self, [act.output], 

460 act.output, "return", children=[act]) 

461 

462 def execute(self, **kwargs): 

463 MLAction.execute(self, **kwargs) 

464 res = self.ChildrenResults 

465 return self.output.validate(res[0]) 

466 

467 @AutoAction.cache 

468 def _export_c(self, hook=None, result_name=None): 

469 if len(self.children) != 1: 

470 raise ValueError( 

471 "Only one result can be returned.") # pragma: no cover 

472 if result_name is None: 

473 raise ValueError( 

474 "result_name must not be None") # pragma: no cover 

475 dc = self.children[0]._export_c(hook=hook, result_name=result_name) 

476 if not dc['cache']: 

477 code = dc['code'] 

478 else: 

479 code = '' 

480 

481 add = self.output._copy_c( 

482 result_name, result_name[:-1], hook="typeref") 

483 code += "\n" + add 

484 return {'code': code, 'result_name': result_name} 

485 

486 

487class MLActionFunction(MLActionUnary): 

488 """ 

489 A function. 

490 """ 

491 

492 def __init__(self, act, name): 

493 """ 

494 @param act action 

495 @param name name 

496 """ 

497 if not isinstance(act, MLActionReturn): 

498 raise NotImplementedError( # pragma: no cover 

499 "Last result must be MLActionReturn.") 

500 MLActionUnary.__init__(self, act, name) 

501 

502 def execute(self, **kwargs): 

503 MLActionUnary.execute(self, **kwargs) 

504 res = self.ChildrenResults 

505 return self.output.validate(res[0]) 

506 

507 @AutoAction.cache 

508 def _export_c(self, hook=None, result_name=None): 

509 if result_name is None: 

510 raise ValueError( 

511 "result_name must not be None") # pragma: no cover 

512 if len(self.children) != 1: 

513 raise ValueError( 

514 "The function must return one result.") # pragma: no cover 

515 if result_name[-1] == '0': 

516 raise ValueError( # pragma: no cover 

517 "result_name '{0}' cannot end with 0.".format(result_name)) 

518 

519 vars = {v.name: v for v in self.enumerate_variables()} 

520 vars = [_[1] for _ in list(sorted(vars.items()))] 

521 parameters = ", ".join("{0} {1}".format( 

522 v.output._export_c(hook='type')['code'], v.name_var) for v in vars) 

523 typename = self.children[0].output._export_c( 

524 hook='typeref', result_name=result_name)['code'] 

525 signature = "int {1} ({0}, {2})".format( 

526 typename, self.name, parameters) 

527 dc = MLAction._export_c(self, hook=hook, result_name=result_name) 

528 code = dc['code'] 

529 rows = [signature, "{"] 

530 rows.extend(" " + line for line in code.split("\n")) 

531 rows.extend( 

532 [' return 0;', " // {0}-{1} - done".format(id(self), self.name), '}']) 

533 return {'code': "\n".join(rows), 'result_name': result_name}