提交 8e292493 authored 作者: Frédéric Bastien's avatar Frédéric Bastien 提交者: GitHub

Merge pull request #5612 from notoraptor/make-structs

Implement a struct generator for wrapping op params in Python and C codes.
......@@ -130,6 +130,16 @@ the most important ones:
change in the code. If you don't want to cache the compiled code
return an empty tuple or don't implement it.
.. method:: c_element_type()
Optional: should return the name of the primitive C type of
items into variables handled by this Theano type. For example,
for a matrix of 32-bit signed NumPy integers, it should return
``"npy_int32"``. If C type may change from an instance to another
(e.g. ``Scalar('int32')`` vs ``Scalar('int64')``), consider
implementing this method. If C type is fixed accross instances,
this method may be useless (as you already know the C type
when you work with the C code).
Each of these functions take two arguments, ``name`` and ``sub`` which
must be used to parameterize the C code they return. ``name`` is a
......
......@@ -73,8 +73,10 @@ attribute :attr:`params_type` to an instance of your params Type.
.. note::
If you want to have multiple parameters you have to bundle those
inside a single object and use that as the params type.
If you want to have multiple parameters, Theano provides the convenient class
:class:`theano.gof.params_type.ParamsType` that allows to bundle many parameters into
one object that will be available in both Python (as a Python object) and C code (as a struct).
See :ref:`ParamsType tutorial and API documentation <libdoc_gof_params_type>` for more infos.
For example if we decide to use an int as the params the following
would be appropriate:
......
......@@ -17,4 +17,5 @@
fgraph
toolbox
type
params_type
utils
.. _libdoc_gof_params_type:
============================================================
:mod:`theano.gof.params_type` -- Wrapper class for op params
============================================================
---------
Reference
---------
.. automodule:: theano.gof.params_type
:platform: Unix, Windows
:synopsis: Wrapper class for op params
:members:
.. moduleauthor:: LISA
\ No newline at end of file
......@@ -80,6 +80,8 @@ from theano.gof.type import \
from theano.gof.utils import \
hashtype, object2, MethodNotDefined
from theano.gof.params_type import ParamsType, Params
import theano
if theano.config.cmodule.preload_cache:
......
......@@ -125,9 +125,10 @@ class Apply(Node):
Returns the params for the node, or NoParams if no params is set.
"""
if hasattr(self.op, 'get_params'):
try:
return self.op.get_params(self)
return NoParams
except theano.gof.utils.MethodNotDefined:
return NoParams
def __getstate__(self):
d = self.__dict__
......
......@@ -795,6 +795,22 @@ class Op(utils.object2, PureOp, CLinkerOp):
Convenience class to bundle `PureOp` and `CLinkerOp`.
"""
# We add a default get_params() implementation which will try to detect params from the op
# if params_type is set to a ParamsType. If not, we raise a MethodNotDefined exception.
def get_params(self, node):
if hasattr(self, 'params_type') and isinstance(self.params_type, theano.gof.ParamsType):
wrapper = self.params_type
if not all(hasattr(self, field) for field in wrapper.fields):
raise AttributeError('%s: missing attributes for ParamsType parameter.' % type(self).__name__)
wrap_dict = dict()
for i in range(wrapper.length):
field = wrapper.fields[i]
_type = wrapper.types[i]
wrap_dict[field] = _type.filter(getattr(self, field), strict=False, allow_downcast=True)
return theano.gof.Params(wrapper, **wrap_dict)
raise theano.gof.utils.MethodNotDefined('get_params')
def prepare_node(self, node, storage_map, compute_map, impl):
"""
Make any special modifications that the Op needs before doing
......@@ -1377,7 +1393,25 @@ class COp(Op):
The names must be strings that are not a C keyword and the
values must be strings of literal C representations.
If op uses a :class:`theano.gof.params_type.ParamsType` as ``params_type``,
it returns:
- a default macro ``PARAMS_TYPE`` which defines the class name of the
corresponding C struct.
- a macro ``DTYPE_PARAM_key`` for every ``key`` in the ParamsType for which associated
type implements the method :func:`theano.gof.type.CLinkerType.c_element_type`.
``DTYPE_PARAM_key`` defines the primitive C type name of an item in a variable
associated to ``key``.
"""
if hasattr(self, 'params_type') and isinstance(self.params_type, theano.gof.ParamsType):
wrapper = self.params_type
params = [('PARAMS_TYPE', wrapper.name)]
for i in range(wrapper.length):
try:
params.append(('DTYPE_PARAM_' + wrapper.fields[i], wrapper.types[i].c_element_type()))
except utils.MethodNotDefined:
pass
return params
return []
def c_code_cache_version(self):
......
"""
Module for wrapping many Op parameters into one object available in both Python and C code.
The module provides the main public class :class:`ParamsType` that allows to bundle many Theano types
into one parameter type, and an internal convenient class :class:`Params` which will be automatically
used to create a Params object that is compatible with the ParamsType defined.
The Params object will be available in both Python code (as a standard Python object) and C code
(as a specific struct with parameters as struct fields). To be fully-available in C code, Theano
types wrapped into a ParamsType must provide a C interface (e.g. TensorType, Scalar, GpuArrayType,
or your own type. See :ref:`extending_op_params` for more details).
Example of usage
----------------
Importation:
.. code-block:: python
# Import ParamsType class.
from theano.gof import ParamsType
# If you want to use a tensor and a scalar as parameters,
# you should import required Theano types.
from theano.tensor import TensorType
from theano.scalar import Scalar
In your Op sub-class:
.. code-block:: python
params_type = ParamsType(attr1=TensorType('int32', (False, False)), attr2=Scalar('float64'))
If your op contains attributes ``attr1`` **and** ``attr2``, the default ``op.get_params()``
implementation will automatically try to look for it and generate an appropriate Params object.
Attributes must be compatible with the corresponding types defined into the ParamsType
(we will try to convert and downcast if needed). In this example, ``your_op.attr1``
should be a matrix of integers, and ``your_op.attr2``
should be a real number (integer or floating value).
.. code-block:: python
def __init__(value_attr1, value_attr2):
self.attr1 = value_attr1
self.attr2 = value_attr2
In ``perform()`` implementation (with params named ``param``):
.. code-block:: python
matrix_param = param.attr1
number_param = param.attr2
In ``c_code()`` implementation (with ``param = sub['params']``):
.. code-block:: c
PyArrayObject* matrix = param->attr1;
npy_float64 number = param->attr2;
/* You won't need to free them or whatever else. */
See :class:`QuadraticOpFunc` and :class:`QuadraticCOpFunc` in ``theano/gof/tests/test_params_type.py``
for complete working examples.
"""
from __future__ import absolute_import, print_function, division
import re
import hashlib
from theano.gof.utils import MethodNotDefined, c_cpp_keywords
from theano.gof import Type
class Params(dict):
"""
Internal convenient class to wrap many Python objects into one
(this class is not safe as the hash method does not check if values are effectively hashable).
**Example:**
.. code-block:: python
from theano.gof import ParamsType, Params
from theano.scalar import Scalar
# You must create a ParamsType first:
params_type = ParamsType(attr1=Scalar('int32'),
key2=Scalar('float32'),
field3=Scalar('int64'))
# Then you can create a Params object with
# the params type defined above and values for attributes.
params = Params(params_type, attr1=1, key2=2.0, field3=3)
print(params.attr1, params.key2, params.field3)
d = dict(attr1=1, key2=2.5, field3=-1)
params2 = Params(params_type, **d)
print(params2.attr1, params2.key2, params2.field3)
"""
def __init__(self, params_type, **kwargs):
if not isinstance(params_type, ParamsType):
raise TypeError('Params: 1st constructor argument should be a ParamsType.')
for field in params_type.fields:
if field not in kwargs:
raise TypeError('Params: ParamsType attribute "%s" not in Params args.' % field)
super(Params, self).__init__(**kwargs)
self.__dict__.update(__params_type__=params_type, __signatures__=None)
def __repr__(self):
return 'Params(%s)' % ', '.join([('%s:%s:%s' % (k, type(self[k]).__name__, self[k])) for k in sorted(self.keys())])
def __getattr__(self, key):
if key not in self:
raise AttributeError('Params: attribute "%s" does not exist.' % key)
return self[key]
def __setattr__(self, key, value):
raise NotImplementedError('Params is immutable')
def __setitem__(self, key, value):
raise NotImplementedError('Params is immutable')
def __delitem__(self, key):
raise NotImplementedError('Params is immutable')
def __hash__(self):
# As values are immutable, we can save data signatures the first time
# to not regenerate them in future hash() calls.
if self.__signatures__ is None:
# NB: For writing, we must bypass setattr() which is always called by default by Python.
self.__dict__['__signatures__'] = tuple(
# NB: Params object should have been already filtered.
self.__params_type__.types[i].make_constant(self[self.__params_type__.fields[i]]).signature()
for i in range(self.__params_type__.length)
)
return hash((type(self), self.__params_type__) + self.__signatures__)
def __eq__(self, other):
return (type(self) == type(other) and self.__params_type__ == other.__params_type__ and all(
# NB: Params object should have been already filtered.
self.__params_type__.types[i].values_eq(self[self.__params_type__.fields[i]], other[self.__params_type__.fields[i]])
for i in range(self.__params_type__.length)
))
def __ne__(self, other):
return not self.__eq__(other)
class ParamsType(Type):
"""
This class can create a struct of Theano types (like TensorType, GpuArrayType, etc.)
to be used as a convenience op parameter wrapping many data.
ParamsType constructor takes key-value args.
Key will be the name of the attribute in the struct.
Value is the Theano type of this attribute, ie. an instance of (a subclass of) :class:`Type`
(eg. ``TensorType('int64', (False,))``).
In a Python code any attribute named ``key`` will be available via::
structObject.key
In a C code, any attribute named ``key`` will be available via:
.. code-block:: c
structObject->key;
.. note::
This Type is not complete and should never be used for regular graph operations.
"""
def __init__(self, **kwargs):
if len(kwargs) == 0:
raise ValueError('Cannot create ParamsType from empty data.')
for attribute_name in kwargs:
if re.match('^[A-Za-z_][A-Za-z0-9_]*$', attribute_name) is None:
raise AttributeError('ParamsType: attribute "%s" should be a valid identifier.' % attribute_name)
if attribute_name in c_cpp_keywords:
raise SyntaxError('ParamsType: "%s" is a potential C/C++ keyword and should not be used as attribute name.'
% attribute_name)
type_instance = kwargs[attribute_name]
type_name = type_instance.__class__.__name__
if not isinstance(type_instance, Type):
raise TypeError('ParamsType: attribute "%s" should inherit from Theano Type, got "%s".'
% (attribute_name, type_name))
self.length = len(kwargs)
self.fields = tuple(sorted(kwargs.keys()))
self.types = tuple(kwargs[field] for field in self.fields)
self.name = self.generate_struct_name()
def __repr__(self):
return 'ParamsType<%s>' % ', '.join([('%s:%s' % (self.fields[i], self.types[i])) for i in range(self.length)])
def __eq__(self, other):
return (type(self) == type(other) and self.fields == other.fields and self.types == other.types)
def __hash__(self):
return hash((type(self),) + self.fields + self.types)
def generate_struct_name(self):
# This method tries to generate an unique name for the current instance.
# This name is intended to be used as struct name in C code and as constant
# definition to check if a similar ParamsType has already been created
# (see c_support_code() below).
fields_string = ','.join(self.fields).encode('utf-8')
types_string = ','.join(str(t) for t in self.types).encode('utf-8')
fields_hex = hashlib.md5(fields_string).hexdigest()
types_hex = hashlib.md5(types_string).hexdigest()
return '_Params_%s_%s' % (fields_hex, types_hex)
# Returns a Params object with expected attributes or (in strict mode) checks that data has expected attributes.
def filter(self, data, strict=False, allow_downcast=None):
if strict and not isinstance(data, Params):
raise TypeError('%s: strict mode: data should be an instance of Params.' % self)
# Filter data attributes to check if they respect the ParamsType's contract.
filtered = {self.fields[i]: self.types[i].filter(getattr(data, self.fields[i]), strict, allow_downcast)
for i in range(self.length)}
return data if (strict or isinstance(data, Params)) else Params(self, **filtered)
def values_eq(self, a, b):
return all(self.types[i].values_eq(getattr(a, self.fields[i]), getattr(b, self.fields[i]))
for i in range(self.length))
def values_eq_approx(self, a, b):
return all(self.types[i].values_eq_approx(getattr(a, self.fields[i]), getattr(b, self.fields[i]))
for i in range(self.length))
def c_compile_args(self, c_compiler):
c_compile_args_list = []
for _type in self.types:
try:
try:
c_compile_args_list.extend(_type.c_compile_args(c_compiler))
except TypeError:
c_compile_args_list.extend(_type.c_compile_args())
except MethodNotDefined:
pass
return c_compile_args_list
def c_no_compile_args(self, c_compiler):
c_no_compile_args_list = []
for _type in self.types:
try:
try:
c_no_compile_args_list.extend(_type.c_no_compile_args(c_compiler))
except TypeError:
c_no_compile_args_list.extend(_type.c_no_compile_args())
except MethodNotDefined:
pass
return c_no_compile_args_list
def c_headers(self, c_compiler):
c_headers_list = []
for _type in self.types:
try:
try:
c_headers_list.extend(_type.c_headers(c_compiler))
except TypeError:
c_headers_list.extend(_type.c_headers())
except MethodNotDefined:
pass
return c_headers_list
def c_libraries(self, c_compiler):
c_libraries_list = []
for _type in self.types:
try:
try:
c_libraries_list.extend(_type.c_libraries(c_compiler))
except TypeError:
c_libraries_list.extend(_type.c_libraries())
except MethodNotDefined:
pass
return c_libraries_list
def c_header_dirs(self):
c_header_dirs_list = []
for _type in self.types:
try:
c_header_dirs_list.extend(_type.c_header_dirs())
except MethodNotDefined:
pass
return c_header_dirs_list
def c_lib_dirs(self):
c_lib_dirs_list = []
for _type in self.types:
try:
c_lib_dirs_list.extend(_type.c_lib_dirs())
except MethodNotDefined:
pass
return c_lib_dirs_list
def c_init_code(self):
c_init_code_list = []
for _type in self.types:
try:
c_init_code_list.extend(_type.c_init_code())
except MethodNotDefined:
pass
return c_init_code_list
def c_support_code(self):
sub = {'fail': '{this->setErrorOccurred(); return;}'}
struct_name = self.name
struct_name_defined = struct_name.upper()
c_declare_list = []
c_init_list = []
c_cleanup_list = []
c_extract_list = []
for attribute_name, type_instance in zip(self.fields, self.types):
c_declare_list.append(type_instance.c_declare(attribute_name, sub))
c_init_list.append(type_instance.c_init(attribute_name, sub))
c_cleanup_list.append(type_instance.c_cleanup(attribute_name, sub))
c_extract_list.append("""
void extract_%(attribute_name)s(PyObject* py_%(attribute_name)s) {
%(extract_code)s
}
""" % {
'attribute_name': attribute_name,
'extract_code': type_instance.c_extract(attribute_name, sub)
})
struct_declare = '\n'.join(c_declare_list)
struct_init = '\n'.join(c_init_list)
struct_cleanup = '\n'.join(c_cleanup_list)
struct_extract = '\n\n'.join(c_extract_list)
struct_extract_method = """
void extract(PyObject* object, int field_pos) {
switch(field_pos) {
// Extraction cases.
%s
// Default case.
default:
PyErr_Format(PyExc_TypeError, "ParamsType: no extraction defined for a field %%d.", field_pos);
this->setErrorOccurred();
break;
}
}
""" % ('\n'.join(
[('case %d: extract_%s(object); break;' % (i, self.fields[i])) for i in range(self.length)])
)
return """
#ifndef %(struct_name_defined)s
#define %(struct_name_defined)s
struct %(struct_name)s {
/* Attributes, */
int %(struct_name)s_error;
%(struct_declare)s
/* Constructor. */
%(struct_name)s() {
%(struct_name)s_error = 0;
%(struct_init)s
}
/* Destructor. */
~%(struct_name)s() {
// cleanup() is defined below.
cleanup();
}
/* Cleanup method. */
void cleanup() {
%(struct_cleanup)s
}
/* Extraction methods. */
%(struct_extract)s
/* Extract method. */
%(struct_extract_method)s
/* Other methods. */
void setErrorOccurred() {
++%(struct_name)s_error;
}
int errorOccurred() {
return %(struct_name)s_error;
}
};
#endif
""" % dict(struct_name_defined=struct_name_defined, struct_name=struct_name, struct_declare=struct_declare,
struct_init=struct_init, struct_cleanup=struct_cleanup, struct_extract=struct_extract,
struct_extract_method=struct_extract_method)
def c_code_cache_version(self):
return ((1, 7), tuple(t.c_code_cache_version() for t in self.types))
# As this struct has constructor and destructor, it could be instanciated on stack,
# but current implementations of C ops will then pass the instance by value at functions,
# so it's better to work directly with pointers.
def c_declare(self, name, sub, check_input=True):
return """
%(struct_name)s* %(name)s;
""" % dict(struct_name=self.name, name=name)
def c_init(self, name, sub):
# NB: It seems c_init() is not called for an op param.
# So the real initialization is done at top of c_extract.
return """
%(name)s = NULL;
""" % dict(name=name)
def c_cleanup(self, name, sub):
return """
delete %(name)s;
%(name)s = NULL;
""" % dict(name=name)
def c_extract(self, name, sub, check_input=True):
return """
/* Seems c_init() is not called for a op param. So I call `new` here. */
%(name)s = new %(struct_name)s;
const char* fields[] = {%(fields_list)s};
if (py_%(name)s == Py_None) {
PyErr_SetString(PyExc_ValueError, "ParamsType: expected an object, not None.");
%(fail)s
}
for (int i = 0; i < %(length)s; ++i) {
PyObject* o = PyDict_GetItemString(py_%(name)s, fields[i]);
if (o == NULL) {
PyErr_Format(PyExc_TypeError, "ParamsType: missing expected attribute \\"%%s\\" in object.", fields[i]);
%(fail)s
}
%(name)s->extract(o, i);
if (%(name)s->errorOccurred()) {
/* The extract code from attribute type should have already raised a Python exception,
* so we just print the attribute name in stderr. */
fprintf(stderr, "\\nParamsType: error when extracting value for attribute \\"%%s\\".\\n", fields[i]);
%(fail)s
}
}
""" % dict(name=name, struct_name=self.name, length=self.length, fail=sub['fail'],
fields_list='"%s"' % '", "'.join(self.fields))
from __future__ import absolute_import, print_function, division
import theano
import numpy
from unittest import TestCase
from theano.gof import Op, COp, Apply
from theano import Generic
from theano.scalar import Scalar
from theano.tensor import TensorType
from theano.gof import ParamsType, Params
from theano import tensor
from theano.tests import unittest_tools as utt
tensor_type_0d = TensorType('float64', tuple())
scalar_type = Scalar('float64')
generic_type = Generic()
# A test op to compute `y = a*x^2 + bx + c` for any tensor x, with a, b, c as op params.
class QuadraticOpFunc(Op):
__props__ = ('a', 'b', 'c')
params_type = ParamsType(a=tensor_type_0d,
b=scalar_type,
c=generic_type)
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def make_node(self, x):
x = tensor.as_tensor_variable(x)
return Apply(self, [x], [x.type()])
def perform(self, node, inputs, output_storage, coefficients):
x = inputs[0]
y = output_storage[0]
y[0] = coefficients.a * (x**2) + coefficients.b * x + coefficients.c
def c_code_cache_version(self):
return (1, 5)
def c_support_code_apply(self, node, name):
float_type = node.inputs[0].type.dtype_specs()[1]
return """
/* Computes: x = a*x*x + b*x + c for x in tensor. */
int quadratic_%(name)s(PyArrayObject* tensor, %(float_type)s a, %(float_type)s b, %(float_type)s c) {
NpyIter* iterator = NpyIter_New(tensor,
NPY_ITER_READWRITE | NPY_ITER_EXTERNAL_LOOP | NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING, NULL);
if(iterator == NULL) {
PyErr_SetString(PyExc_RuntimeError, "Unable to iterate over a tensor for an elemwise operation.");
return -1;
}
NpyIter_IterNextFunc* get_next = NpyIter_GetIterNext(iterator, NULL);
char** data_ptr = NpyIter_GetDataPtrArray(iterator);
npy_intp* stride_ptr = NpyIter_GetInnerStrideArray(iterator);
npy_intp* innersize_ptr = NpyIter_GetInnerLoopSizePtr(iterator);
do {
char* data = *data_ptr;
npy_intp stride = *stride_ptr;
npy_intp count = *innersize_ptr;
while(count) {
%(float_type)s x = *((%(float_type)s*)data);
*((%(float_type)s*)data) = a*x*x + b*x + c;
data += stride;
--count;
}
} while(get_next(iterator));
NpyIter_Deallocate(iterator);
return 0;
}
""" % {'name': name, 'float_type': float_type}
def c_code(self, node, name, inputs, outputs, sub):
return """
%(float_type)s a = (%(float_type)s) (*(npy_float64*) PyArray_GETPTR1(%(coeff)s->a, 0)); // 0-D TensorType.
%(float_type)s b = %(coeff)s->b; // Scalar.
%(float_type)s c = (%(float_type)s) PyFloat_AsDouble(%(coeff)s->c); // Generic.
Py_XDECREF(%(Y)s);
%(Y)s = (PyArrayObject*)PyArray_EMPTY(PyArray_NDIM(%(X)s), PyArray_DIMS(%(X)s), PyArray_TYPE(%(X)s), PyArray_IS_F_CONTIGUOUS(%(X)s));
if (PyArray_CopyInto(%(Y)s, %(X)s) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Unable to copy input into output.");
%(fail)s
};
if (quadratic_%(name)s(%(Y)s, a, b, c) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Unable to compute quadratic function.");
%(fail)s
}
""" % dict(name=name, coeff=sub['params'], fail=sub['fail'],
X=inputs[0], Y=outputs[0], float_type=node.inputs[0].type.c_element_type())
# Same op as above, but implemented as a COp (with C code in an external file).
class QuadraticCOpFunc(COp):
__props__ = ('a', 'b', 'c')
params_type = ParamsType(a=tensor_type_0d,
b=scalar_type,
c=generic_type)
def __init__(self, a, b, c):
super(QuadraticCOpFunc, self).__init__('test_quadratic_function.c',
'APPLY_SPECIFIC(compute_quadratic)')
self.a = a
self.b = b
self.c = c
def make_node(self, x):
x = tensor.as_tensor_variable(x)
return Apply(self, [x], [x.type()])
def perform(self, node, inputs, output_storage, coefficients):
x = inputs[0]
y = output_storage[0]
y[0] = coefficients.a * (x**2) + coefficients.b * x + coefficients.c
class TestParamsType(TestCase):
def test_hash_and_eq_params(self):
wp1 = ParamsType(a=Generic(), array=TensorType('int64', (False,)), floatting=Scalar('float64'),
npy_scalar=TensorType('float64', tuple()))
wp2 = ParamsType(a=Generic(), array=TensorType('int64', (False,)), floatting=Scalar('float64'),
npy_scalar=TensorType('float64', tuple()))
w1 = Params(wp1, a=1, array=numpy.asarray([1, 2, 4, 5, 7]), floatting=-4.5, npy_scalar=numpy.asarray(12))
w2 = Params(wp2, a=1, array=numpy.asarray([1, 2, 4, 5, 7]), floatting=-4.5, npy_scalar=numpy.asarray(12))
assert w1 == w2
assert not (w1 != w2)
assert hash(w1) == hash(w2)
# Changing attributes names only (a -> other_name).
wp2_other = ParamsType(other_name=Generic(), array=TensorType('int64', (False,)), floatting=Scalar('float64'),
npy_scalar=TensorType('float64', tuple()))
w2 = Params(wp2_other, other_name=1, array=numpy.asarray([1, 2, 4, 5, 7]), floatting=-4.5, npy_scalar=numpy.asarray(12))
assert w1 != w2
# Changing attributes values only (now a=2).
w2 = Params(wp2, a=2, array=numpy.asarray([1, 2, 4, 5, 7]), floatting=-4.5, npy_scalar=numpy.asarray(12))
assert w1 != w2
# Changing NumPy array values (5 -> -5).
w2 = Params(wp2, a=1, array=numpy.asarray([1, 2, 4, -5, 7]), floatting=-4.5, npy_scalar=numpy.asarray(12))
assert w1 != w2
def test_hash_and_eq_params_type(self):
w1 = ParamsType(a1=TensorType('int64', (False, False)),
a2=TensorType('int64', (False, True, False, False, True)),
a3=Generic())
w2 = ParamsType(a1=TensorType('int64', (False, False)),
a2=TensorType('int64', (False, True, False, False, True)),
a3=Generic())
assert w1 == w2
assert not (w1 != w2)
assert hash(w1) == hash(w2)
assert w1.name == w2.name
# Changing attributes names only.
w2 = ParamsType(a1=TensorType('int64', (False, False)),
other_name=TensorType('int64', (False, True, False, False, True)), # a2 -> other_name
a3=Generic())
assert w1 != w2
# Changing attributes types only.
w2 = ParamsType(a1=TensorType('int64', (False, False)),
a2=Generic(), # changing class
a3=Generic())
assert w1 != w2
# Changing attributes types characteristics only.
w2 = ParamsType(a1=TensorType('int64', (False, True)), # changing broadcasting
a2=TensorType('int64', (False, True, False, False, True)),
a3=Generic())
assert w1 != w2
def test_params_type_filtering(self):
shape_tensor5 = (1, 2, 2, 3, 2)
size_tensor5 = shape_tensor5[0] * shape_tensor5[1] * shape_tensor5[2] * shape_tensor5[3] * shape_tensor5[4]
random_tensor = numpy.random.normal(size=size_tensor5).reshape(shape_tensor5)
w = ParamsType(a1=TensorType('int32', (False, False)),
a2=TensorType('float64', (False, False, False, False, False)),
a3=Generic())
# With a value that does not match the params type.
o = Params(w,
a1=numpy.asarray([[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]).astype('int64'),
a2=random_tensor.astype('float32'),
a3=2000)
# should fail (o.a1 is not int32, o.a2 is not float64)
self.assertRaises(TypeError, w.filter, o, True)
# should fail (o.a1 is not int32, o.a2 is not float64, and downcast is disallowed)
self.assertRaises(TypeError, w.filter, o, False, False)
# Should pass.
w.filter(o, strict=False, allow_downcast=True)
# With a value that matches the params type.
o1 = Params(w,
a1=numpy.asarray([[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]).astype('int32'),
a2=random_tensor.astype('float64'),
a3=2000)
# All should pass.
w.filter(o1, strict=True)
w.filter(o1, strict=False, allow_downcast=False)
w.filter(o1, strict=False, allow_downcast=True)
# Check values_eq and values_eq_approx.
o2 = Params(w,
a1=numpy.asarray([[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]).astype('int32'),
a2=random_tensor.astype('float64'),
a3=2000)
assert w.values_eq(o1, o2)
assert w.values_eq_approx(o1, o2)
# Check value_eq_approx.
# NB: I don't know exactly which kind of differences is rejected by values_eq but accepted by values_eq_approx.
# So, I just play a little with float values.
o3 = Params(w,
a1=numpy.asarray([[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]).astype('int32'),
a2=(random_tensor.astype('float32') * 10 / 2.2 * 2.19999999999 / 10).astype('float64'),
a3=2000.0 - 0.00000000000000001)
assert w.values_eq_approx(o1, o3)
def test_op_params(self):
a, b, c = 2, 3, -7
x = tensor.matrix(dtype='float64')
y1 = QuadraticOpFunc(a, b, c)(x)
y2 = QuadraticCOpFunc(a, b, c)(x)
f1 = theano.function([x], y1)
f2 = theano.function([x], y2)
shape = (100, 100)
vx = numpy.random.normal(size=shape[0] * shape[1]).astype('float64').reshape(*shape)
vy1 = f1(vx)
vy2 = f2(vx)
ref = a * (vx**2) + b * vx + c
utt.assert_allclose(vy1, vy2)
utt.assert_allclose(ref, vy1)
#section support_code_apply
int APPLY_SPECIFIC(quadratic_function)(PyArrayObject* tensor, DTYPE_INPUT_0 a, DTYPE_INPUT_0 b, DTYPE_INPUT_0 c) {
NpyIter* iterator = NpyIter_New(tensor,
NPY_ITER_READWRITE | NPY_ITER_EXTERNAL_LOOP | NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING, NULL);
if(iterator == NULL) {
PyErr_SetString(PyExc_RuntimeError, "Unable to iterate over a tensor for an elemwise operation.");
return -1;
}
NpyIter_IterNextFunc* get_next = NpyIter_GetIterNext(iterator, NULL);
char** data_ptr = NpyIter_GetDataPtrArray(iterator);
npy_intp* stride_ptr = NpyIter_GetInnerStrideArray(iterator);
npy_intp* innersize_ptr = NpyIter_GetInnerLoopSizePtr(iterator);
do {
char* data = *data_ptr;
npy_intp stride = *stride_ptr;
npy_intp count = *innersize_ptr;
while(count) {
DTYPE_INPUT_0 x = *((DTYPE_INPUT_0*)data);
*((DTYPE_INPUT_0*)data) = a*x*x + b*x + c;
data += stride;
--count;
}
} while(get_next(iterator));
NpyIter_Deallocate(iterator);
return 0;
}
int APPLY_SPECIFIC(compute_quadratic)(PyArrayObject* X, PyArrayObject** Y, PARAMS_TYPE* coeff) {
DTYPE_INPUT_0 a = (DTYPE_INPUT_0) (*(DTYPE_PARAM_a*) PyArray_GETPTR1(coeff->a, 0)); // 0-D TensorType.
DTYPE_INPUT_0 b = coeff->b; // Scalar.
DTYPE_INPUT_0 c = (DTYPE_INPUT_0) PyFloat_AsDouble(coeff->c); // Generic.
Py_XDECREF(*Y);
*Y = (PyArrayObject*)PyArray_EMPTY(PyArray_NDIM(X), PyArray_DIMS(X), TYPENUM_INPUT_0, PyArray_IS_F_CONTIGUOUS(X));
if (PyArray_CopyInto(*Y, X) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Unable to copy input into output.");
return 1;
};
if (APPLY_SPECIFIC(quadratic_function)(*Y, a, b, c) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Unable to compute quadratic function.");
return 1;
}
return 0;
}
......@@ -35,6 +35,19 @@ class CLinkerType(CLinkerObject):
"""
def c_element_type(self):
"""
Optional: Return the name of the primitive C type of items into variables
handled by this type.
e.g:
- For ``TensorType(dtype='int64', ...)``: should return ``"npy_int64"``.
- For ``GpuArrayType(dtype='int32', ...)``: should return ``"ga_int"``.
"""
raise MethodNotDefined("c_element_type", type(self), self.__class__.__name__)
def c_is_simple(self):
"""
Optional: Return True for small or builtin C types.
......
......@@ -573,3 +573,20 @@ def hash_from_file(file_path):
with open(file_path, 'rb') as f:
file_content = f.read()
return hash_from_code(file_content)
# Set of C and C++ keywords as defined (at March 2nd, 2017) in the pages below:
# - http://fr.cppreference.com/w/c/keyword
# - http://fr.cppreference.com/w/cpp/keyword
# Added `NULL` and `_Pragma` keywords.
c_cpp_keywords = {'_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex', '_Generic', '_Imaginary', '_Noreturn',
'_Pragma', '_Static_assert', '_Thread_local', 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto',
'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', 'compl',
'const', 'const_cast', 'constexpr', 'continue', 'decltype', 'default', 'delete', 'do', 'double',
'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend',
'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'noexcept', 'not', 'not_eq',
'NULL', 'nullptr', 'operator', 'or', 'or_eq', 'private', 'protected', 'public', 'register',
'reinterpret_cast', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'static_assert',
'static_cast', 'struct', 'switch', 'template', 'this', 'thread_local', 'throw', 'true', 'try',
'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile',
'wchar_t', 'while', 'xor', 'xor_eq'}
......@@ -459,6 +459,9 @@ class GpuArrayType(Type):
else:
return np.dtype(self.dtype).itemsize
def c_element_type(self):
return pygpu.gpuarray.dtype_to_ctype(self.dtype)
def c_declare(self, name, sub, check_input=True):
return """
PyGpuArrayObject *%(name)s;
......
......@@ -349,6 +349,9 @@ class Scalar(Type):
return True
return abs(diff) <= (abs(a) * tolerance) + (abs(b) * tolerance)
def c_element_type(self):
return self.dtype_specs()[1]
def c_headers(self, c_compiler):
l = ['<math.h>']
# These includes are needed by Scalar and TensorType,
......
......@@ -374,6 +374,9 @@ class TensorType(Type):
def __repr__(self):
return str(self)
def c_element_type(self):
return self.dtype_specs()[1]
def c_declare(self, name, sub, check_input=True):
"""
Override `CLinkerType.c_declare`.
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论