Source code for graphdot.codegen.cpptool

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import itertools as it
import numpy as np
from graphdot.codegen import Template
from .typetool import can_cast, _dtype_util


[docs]def cpptype(decls=[], **kwdecls): """ cpptype is a class decorator that simplifies the translation of python objects to corresponding C++ structures. """ ctype = np.dtype(decls + list(kwdecls.items()), align=True) def decor(cls): class CppType(type(cls)): @property def dtype(self): '''Enables numpy.dtype(cls) to return the layout of the C++ counterpart.''' return ctype def __repr__(self): return f'@cpptype({repr(ctype)})\n{repr(cls)}' class Class(cls, metaclass=CppType): @property def dtype(self): '''Useful in microkernel composition.''' return Class.dtype @property def state(self): state = [] for key, (field, _) in Class.dtype.fields.items(): if _dtype_util.is_object(field): state.append(getattr(self, key).state) elif _dtype_util.is_array(field): states = getattr(self, key) if not isinstance(states, np.ndarray): raise TypeError( f'attribute {key} declared as an array' f'but the actual type is not a numpy' f'ndarray.' ) if states.shape != field.shape: raise ValueError( f'attribute {key}: ' f'actual shape of {states.shape} does not ' f'match declared shape of {field.shape}.' ) if _dtype_util.is_object(field.base): states = [ states[coord].state for coord in it.product(*map(range, field.shape)) ] state.append( np.array(states, dtype=field.base) .reshape(field.shape).tolist()) else: state.append(field.type(getattr(self, key))) return tuple(state) def __setattr__(self, name, value): if name in Class.dtype.names: lt = Class.dtype.fields[name][0] if _dtype_util.is_array(lt): if not isinstance(value, np.ndarray): value = np.array(value) if value.shape != lt.shape: raise ValueError( f"Cannot set array attribute '{name}' with " f"value of mismatching shape:\n" f"{value}" ) for t in map(np.dtype, value.ravel()): if not can_cast(t, lt.base): raise TypeError( f"Cannot set attribute '{name}' " f"(C++ type {decltype(lt)}) " f"with values of {value.dtype}" ) else: rt = np.dtype(type(value)) if not can_cast(rt, lt): raise TypeError( f"Cannot set attribute '{name}' " f"(C++ type {decltype(lt)}) " f"with value {value} of {type(value)}" ) super().__setattr__(name, value) # TODO: need a nicer __repr__ Class.__name__ = cls.__name__ return Class return decor
def _assert_is_identifier(name): if name and not name.isidentifier(): raise ValueError( f'Name {name} is not a valid Python/C++ identifier.' )
[docs]def decltype(t, name=''): '''Generate C++ source code for structures corresponding to a `cpptype` class.''' t = np.dtype(t, align=True) # convert np.float32 etc. to dtype if name.startswith('$'): ''' template class types ''' n, t, *Ts = name[1:].split('::') _assert_is_identifier(n) return Template(r'${template}<${arguments,}>${name}').render( template=t, arguments=[decltype(T) for T in Ts], name=n ) else: _assert_is_identifier(name) if _dtype_util.is_object(t): ''' structs ''' if len(t.names): return Template(r'struct{${members;};}${name}').render( name=name, members=[decltype(t.fields[v][0], v) for v in t.names]) else: return f'constexpr static _empty {name} {{}}' elif _dtype_util.is_array(t): ''' C-style arrays ''' return Template(r'${t} ${name}[${shape][}]').render( t=decltype(t.base), name=name, shape=t.shape ) else: ''' scalar types and C-strings ''' if t.kind == 'S': return f'char {name}[{t.itemsize}]' else: return f'{str(t.name)} {name}'.strip()