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
« 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
9class MLType(AutoType):
10 """
11 Base class for every type.
12 """
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
21 def cast(self, value):
22 """
23 Converts *value* into this type.
24 """
25 raise NotImplementedError() # pragma: no cover
28class MLNumType(MLType):
29 """
30 Base class for numerical types.
31 """
33 def _format_value_json(self, value, hook=None):
34 return str(value)
36 def _format_value_c(self, value, hook=None):
37 return str(value)
39 def _copy_c(self, src, dst, hook=None):
40 if hook == "typeref":
41 return f"*{dst} = {src};"
42 return f"{dst} = {src};"
45class MLNumTypeSingle(MLNumType):
46 """
47 int32 or float32
48 """
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
56 @property
57 def CTypeSingle(self):
58 """
59 Returns *ctype*.
60 """
61 return self.ctype
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
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)))
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)
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}
107 def _byref_c(self):
108 return "&"
110 def _export_json(self, hook=None, result_name=None):
111 return 'float32'
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)
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)
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)
129class MLNumTypeFloat32(MLNumTypeSingle):
130 """
131 A numpy.float32.
132 """
134 def __init__(self):
135 MLNumTypeSingle.__init__(
136 self, numpy.float32, 'float32', 'float', 'float32')
139class MLNumTypeFloat64(MLNumTypeSingle):
140 """
141 A numpy.float64.
142 """
144 def __init__(self):
145 MLNumTypeSingle.__init__(
146 self, numpy.float64, 'float64', 'double', 'float64')
149class MLNumTypeInt32(MLNumTypeSingle):
150 """
151 A numpy.int32.
152 """
154 def __init__(self):
155 MLNumTypeSingle.__init__(self, numpy.int32, 'int32', 'int', 'int32')
158class MLNumTypeInt64(MLNumTypeSingle):
159 """
160 A numpy.int64.
161 """
163 def __init__(self):
164 MLNumTypeSingle.__init__(
165 self, numpy.int32, 'int64', 'int64_t', 'int64')
168class MLNumTypeBool(MLNumTypeSingle):
169 """
170 A numpy.bool.
171 """
173 def __init__(self):
174 MLNumTypeSingle.__init__(self, numpy.bool_, 'BL', 'bool', 'bool')
177class MLTensor(MLType):
178 """
179 Defines a tensor with a dimension and a single type for what it contains.
180 """
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
199 @property
200 def CTypeSingle(self):
201 """
202 Returns *ctype*.
203 """
204 return self.element_type.ctype
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
226 def _byref_c(self):
227 return ""
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)
234 def _format_value_c(self, value, hook=None):
235 return f"{{{', '.join(self.element_type._format_value_c(x) for x in value)}}}"
237 def _export_json(self, hook=None, result_name=None):
238 return f'{self.element_type._export_json(hook=hook)}:{self.dim}'
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}'.")
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}));"