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

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 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") 

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] = 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) 

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(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} 

113 

114 

115class MLActionCst(MLAction): 

116 """ 

117 Constant 

118 """ 

119 

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 

132 

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)}'") 

152 

153 def execute(self, **kwargs): 

154 MLAction.execute(self, **kwargs) 

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

156 

157 def graph_execution(self): 

158 if self.comment: 

159 return f"cst: {self.comment} = {self.cst}" 

160 return f"cst: {self.cst}" 

161 

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 

169 

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} 

179 

180 

181class MLActionVar(MLActionCst): 

182 """ 

183 Variable. The constant is only needed to guess the 

184 variable type. 

185 """ 

186 

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 

196 

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]) 

203 

204 def enumerate_variables(self): 

205 """ 

206 Enumerates itself. 

207 """ 

208 yield self 

209 

210 def graph_execution(self): 

211 return f"var: {self.name_var} = {self.name} ({self.output._cache})" 

212 

213 @AutoAction.cache 

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

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

216 

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} 

225 

226 

227class MLActionFunctionCall(MLAction): 

228 """ 

229 Any function call. 

230 """ 

231 

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' 

245 

246 def _optional_parameters(self): 

247 """ 

248 Returns additional parameters to add the function call. 

249 """ 

250 return None 

251 

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 

273 

274 

275class MLActionBinary(MLAction): 

276 """ 

277 Any binary operation. 

278 """ 

279 

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]) 

292 

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} 

306 

307 

308class MLActionUnary(MLAction): 

309 """ 

310 Any binary operation. 

311 """ 

312 

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]) 

322 

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} 

334 

335 

336class MLActionConcat(MLActionFunctionCall): 

337 """ 

338 Concatenate number of arrays into an array. 

339 """ 

340 

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) 

356 

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)) 

364 

365 

366class MLActionCast(MLActionUnary): 

367 """ 

368 Cast into another type. 

369 """ 

370 

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 

378 

379 def execute(self, **kwargs): 

380 MLActionUnary.execute(self, **kwargs) 

381 res = self.ChildrenResults 

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

383 

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.") 

388 

389 

390class MLActionIfElse(MLAction): 

391 """ 

392 Addition 

393 """ 

394 

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 

418 

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) 

432 

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} 

445 

446 

447class MLActionReturn(MLAction): 

448 """ 

449 Returns a results. 

450 """ 

451 

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]) 

458 

459 def execute(self, **kwargs): 

460 MLAction.execute(self, **kwargs) 

461 res = self.ChildrenResults 

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

463 

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 = '' 

477 

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} 

482 

483 

484class MLActionFunction(MLActionUnary): 

485 """ 

486 A function. 

487 """ 

488 

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) 

498 

499 def execute(self, **kwargs): 

500 MLActionUnary.execute(self, **kwargs) 

501 res = self.ChildrenResults 

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

503 

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.") 

515 

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}