Coverage for mlprodict/grammar/grammar_sklearn/grammar/gtypes.py: 86%

128 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-02-04 02:28 +0100

1""" 

2@file 

3@brief Types definition. 

4""" 

5import numpy 

6from .api_extension import AutoType 

7 

8 

9class MLType(AutoType): 

10 """ 

11 Base class for every type. 

12 """ 

13 

14 def validate(self, value): 

15 """ 

16 Checks that the value is of this type. 

17 """ 

18 # It must be overwritten. 

19 self._cache = value 

20 

21 def cast(self, value): 

22 """ 

23 Converts *value* into this type. 

24 """ 

25 raise NotImplementedError() # pragma: no cover 

26 

27 

28class MLNumType(MLType): 

29 """ 

30 Base class for numerical types. 

31 """ 

32 

33 def _format_value_json(self, value, hook=None): 

34 return str(value) 

35 

36 def _format_value_c(self, value, hook=None): 

37 return str(value) 

38 

39 def _copy_c(self, src, dst, hook=None): 

40 if hook == "typeref": 

41 return f"*{dst} = {src};" 

42 return f"{dst} = {src};" 

43 

44 

45class MLNumTypeSingle(MLNumType): 

46 """ 

47 int32 or float32 

48 """ 

49 

50 def __init__(self, numpy_type, name, ctype, key): 

51 self.numpy_type = numpy_type 

52 self.name = name 

53 self.ctype = ctype 

54 self.key = key 

55 

56 @property 

57 def CTypeSingle(self): 

58 """ 

59 Returns *ctype*. 

60 """ 

61 return self.ctype 

62 

63 def validate(self, value): 

64 """ 

65 Checks that the value is of this type. 

66 """ 

67 MLNumType.validate(self, value) 

68 if not isinstance(value, self.numpy_type): 

69 raise TypeError( # pragma: no cover 

70 f"'{type(value)}' is not a {self.numpy_type}.") 

71 return value 

72 

73 def cast(self, value): 

74 """ 

75 Exports *value* into this type. 

76 """ 

77 if isinstance(value, numpy.float32): 

78 raise TypeError( # pragma: no cover 

79 f"No need to cast, already a {self.numpy_type}") 

80 if isinstance(value, numpy.ndarray): 

81 if len(value) != 1: 

82 raise ValueError( # pragma: no cover 

83 f"Dimension of array must be one single {self.numpy_type}") 

84 return value[0] 

85 raise NotImplementedError( # pragma: no cover 

86 "Unable to cast '{0}' into a {0}".format(type(self.numpy_type))) 

87 

88 def softcast(self, value): 

89 """ 

90 Exports *value* into this type, does it anyway without verification. 

91 """ 

92 if isinstance(value, numpy.ndarray): 

93 v = value.ravel() 

94 if len(v) != 1: 

95 raise ValueError( # pragma: no cover 

96 f"Cannot cast shape {value.shape} into {self.numpy_type}") 

97 return self.numpy_type(v[0]) 

98 return self.numpy_type(value) 

99 

100 def _export_common_c(self, ctype, hook=None, result_name=None): 

101 if hook == 'type': 

102 return {'code': ctype} if result_name is None else {'code': ctype + ' ' + result_name} 

103 if result_name is None: 

104 return {'code': ctype} 

105 return {'code': ctype + ' ' + result_name, 'result_name': result_name} 

106 

107 def _byref_c(self): 

108 return "&" 

109 

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

111 return 'float32' 

112 

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

114 if hook == 'typeref': 

115 return {'code': self.ctype + '*'} if result_name is None else {'code': self.ctype + '* ' + result_name} 

116 return self._export_common_c(self.ctype, hook, result_name) 

117 

118 def _format_value_json(self, value, hook=None): 

119 if hook is None or self.key not in hook: 

120 return value 

121 return hook[self.key](value) 

122 

123 def _format_value_c(self, value, hook=None): 

124 if hook is None or self.key not in hook: 

125 return f"({self.ctype}){value}" 

126 return hook[self.key](value) 

127 

128 

129class MLNumTypeFloat32(MLNumTypeSingle): 

130 """ 

131 A numpy.float32. 

132 """ 

133 

134 def __init__(self): 

135 MLNumTypeSingle.__init__( 

136 self, numpy.float32, 'float32', 'float', 'float32') 

137 

138 

139class MLNumTypeFloat64(MLNumTypeSingle): 

140 """ 

141 A numpy.float64. 

142 """ 

143 

144 def __init__(self): 

145 MLNumTypeSingle.__init__( 

146 self, numpy.float64, 'float64', 'double', 'float64') 

147 

148 

149class MLNumTypeInt32(MLNumTypeSingle): 

150 """ 

151 A numpy.int32. 

152 """ 

153 

154 def __init__(self): 

155 MLNumTypeSingle.__init__(self, numpy.int32, 'int32', 'int', 'int32') 

156 

157 

158class MLNumTypeInt64(MLNumTypeSingle): 

159 """ 

160 A numpy.int64. 

161 """ 

162 

163 def __init__(self): 

164 MLNumTypeSingle.__init__( 

165 self, numpy.int32, 'int64', 'int64_t', 'int64') 

166 

167 

168class MLNumTypeBool(MLNumTypeSingle): 

169 """ 

170 A numpy.bool. 

171 """ 

172 

173 def __init__(self): 

174 MLNumTypeSingle.__init__(self, numpy.bool_, 'BL', 'bool', 'bool') 

175 

176 

177class MLTensor(MLType): 

178 """ 

179 Defines a tensor with a dimension and a single type for what it contains. 

180 """ 

181 

182 def __init__(self, element_type, dim): 

183 if not isinstance(element_type, MLType): 

184 raise TypeError( # pragma: no cover 

185 f'element_type must be of MLType not {type(element_type)}') 

186 if not isinstance(dim, tuple): 

187 raise TypeError( # pragma: no cover 

188 'dim must be a tuple.') 

189 if len(dim) == 0: 

190 raise ValueError( # pragma: no cover 

191 "dimension must not be null.") 

192 for d in dim: 

193 if d == 0: 

194 raise ValueError( # pragma: no cover 

195 "No dimension can be null.") 

196 self.dim = dim 

197 self.element_type = element_type 

198 

199 @property 

200 def CTypeSingle(self): 

201 """ 

202 Returns *ctype*. 

203 """ 

204 return self.element_type.ctype 

205 

206 def validate(self, value): 

207 """ 

208 Checks that the value is of this type. 

209 """ 

210 MLType.validate(self, value) 

211 if not isinstance(value, numpy.ndarray): 

212 raise TypeError( # pragma: no cover 

213 f"value is not a numpy.array but '{type(value)}'") 

214 if self.dim != value.shape: 

215 raise ValueError( # pragma: no cover 

216 f"Dimensions do not match {self.dim}={value.shape}") 

217 rvalue = value.ravel() 

218 for i, num in enumerate(rvalue): 

219 try: 

220 self.element_type.validate(num) 

221 except TypeError as e: # pragma: no cover 

222 raise TypeError( 

223 f'Unable to convert an array due to value index {i}: {num}') from e 

224 return value 

225 

226 def _byref_c(self): 

227 return "" 

228 

229 def _format_value_json(self, value, hook=None): 

230 if hook is None or 'array' not in hook: 

231 return value 

232 return hook['array'](value) 

233 

234 def _format_value_c(self, value, hook=None): 

235 return f"{{{', '.join(self.element_type._format_value_c(x) for x in value)}}}" 

236 

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

238 return f'{self.element_type._export_json(hook=hook)}:{self.dim}' 

239 

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

241 if len(self.dim) != 1: 

242 raise NotImplementedError( # pragma: no cover 

243 'Only 1D vector implemented.') 

244 if hook is None: 

245 raise ValueError( # pragma: no cover 

246 "hook must contains either 'signature' or 'declare'.") 

247 if hook == 'signature': 

248 if result_name is None: 

249 raise ValueError( # pragma: no cover 

250 "result_name must be specified.") 

251 return {'code': "{0}[{1}] {2}".format(self.element_type._export_c(hook=hook)['code'], 

252 self.dim[0], result_name), 

253 'result_name': result_name} 

254 elif hook == 'declare': 

255 if result_name is None: 

256 raise ValueError( # pragma: no cover 

257 "result_name must be specified.") 

258 dc = self.element_type._export_c( 

259 hook=hook, result_name=result_name) 

260 return {'code': f"{dc['code']}[{self.dim[0]}]"} 

261 elif hook == 'type': 

262 return {'code': f"{self.element_type._export_c(hook=hook)['code']}*"} 

263 elif hook == 'typeref': 

264 if result_name is None: 

265 return {'code': f"{self.element_type._export_c(hook='type')['code']}*"} 

266 code = self.element_type._export_c(hook='type')['code'] 

267 return {'code': f"{code}* {result_name}", 'result_name': result_name} 

268 else: 

269 raise ValueError( # pragma: no cover 

270 f"hook must contains either 'signature' or 'declare' not '{hook}'.") 

271 

272 def _copy_c(self, src, dest, hook=None): 

273 if len(self.dim) != 1: 

274 raise NotImplementedError( # pragma: no cover 

275 'Only 1D vector implemented.') 

276 code = self.element_type._export_c(hook='type')['code'] 

277 return f"memcpy({dest}, {src}, {self.dim[0]}*sizeof({code}));"