提交 117f23f5 authored 作者: Michael Osthege's avatar Michael Osthege 提交者: Thomas Wiecki

Merge PureOp into Op, move C-related interface types

The CLinkerOp and CLinkerObject like types were moved into link.c.interface to get them out of gof. PureOp and Op were merged (as Op) which means that some C-related methods are now declared on all Ops.
上级 c236db89
...@@ -10,7 +10,7 @@ import theano.tensor as tt ...@@ -10,7 +10,7 @@ import theano.tensor as tt
from tests import unittest_tools as utt from tests import unittest_tools as utt
from theano import Mode, function, tensor from theano import Mode, function, tensor
from theano.gof import Apply, generic from theano.gof import Apply, generic
from theano.gof.op import PureOp from theano.gof.op import Op
from theano.ifelse import IfElse, ifelse from theano.ifelse import IfElse, ifelse
...@@ -509,7 +509,7 @@ class TestIfelse(utt.OptimizationTestMixin): ...@@ -509,7 +509,7 @@ class TestIfelse(utt.OptimizationTestMixin):
[(param, param - 0.5 * tensor.grad(cost=loss, wrt=param)) for param in params] [(param, param - 0.5 * tensor.grad(cost=loss, wrt=param)) for param in params]
class IfElseIfElseIf(PureOp): class IfElseIfElseIf(Op):
def __init__(self, inplace=False): def __init__(self, inplace=False):
# check destroyhandler and others to ensure that a view_map with # check destroyhandler and others to ensure that a view_map with
self.inplace = inplace self.inplace = inplace
...@@ -591,7 +591,7 @@ class NotImplementedOpException(Exception): ...@@ -591,7 +591,7 @@ class NotImplementedOpException(Exception):
pass pass
class NotImplementedOp(PureOp): class NotImplementedOp(Op):
def make_node(self, x): def make_node(self, x):
return Apply(self, [x], [x.type()]) return Apply(self, [x], [x.type()])
......
""" """
Provides `DebugMode`, an evaluation mode for debugging theano internals. Provides `DebugMode`, an evaluation mode for debugging theano internals.
TODO: add support for IfElse Op, LazyLinker, PureOp, etc. TODO: add support for IfElse Op, LazyLinker, etc.
""" """
...@@ -1823,12 +1823,6 @@ class _Linker(LocalLinker): ...@@ -1823,12 +1823,6 @@ class _Linker(LocalLinker):
try: try:
if not self.maker.mode.check_c_code or debug: if not self.maker.mode.check_c_code or debug:
raise utils.MethodNotDefined() raise utils.MethodNotDefined()
# Ops that do not inherit from gof.op.Op don't have certain
# methods defined that the CLinker expects (Scan is an
# example, ifelse is another of such classes that inherit
# directly from PureOp)
if not isinstance(node.op, gof.op.Op):
raise utils.MethodNotDefined()
node.op.prepare_node(node, storage_map, compute_map, "c") node.op.prepare_node(node, storage_map, compute_map, "c")
thunk = node.op.make_c_thunk( thunk = node.op.make_c_thunk(
...@@ -1843,7 +1837,7 @@ class _Linker(LocalLinker): ...@@ -1843,7 +1837,7 @@ class _Linker(LocalLinker):
# consider that we don't have a python implementation # consider that we don't have a python implementation
if ( if (
(self.maker.mode.check_py_code or thunks_c[-1] is None) (self.maker.mode.check_py_code or thunks_c[-1] is None)
and node.op.perform.__code__ != gof.op.PureOp.perform.__code__ and node.op.perform.__code__ != gof.op.Op.perform.__code__
) or debug: ) or debug:
node.op.prepare_node(node, storage_map, compute_map, "py") node.op.prepare_node(node, storage_map, compute_map, "py")
thunk = node.op.make_py_thunk( thunk = node.op.make_py_thunk(
......
...@@ -4,14 +4,7 @@ import theano ...@@ -4,14 +4,7 @@ import theano
from theano.gof.destroyhandler import DestroyHandler from theano.gof.destroyhandler import DestroyHandler
from theano.gof.fg import FunctionGraph, InconsistencyError, MissingInputError from theano.gof.fg import FunctionGraph, InconsistencyError, MissingInputError
from theano.gof.graph import Apply, Constant, Variable, view_roots from theano.gof.graph import Apply, Constant, Variable, view_roots
from theano.gof.op import ( from theano.gof.op import COp, Op, OpenMPOp, get_test_value, ops_with_inner_function
COp,
Op,
OpenMPOp,
PureOp,
get_test_value,
ops_with_inner_function,
)
from theano.gof.opt import ( from theano.gof.opt import (
CheckStackTraceOptimization, CheckStackTraceOptimization,
EquilibriumOptimizer, EquilibriumOptimizer,
......
...@@ -65,8 +65,8 @@ class Apply(Node): ...@@ -65,8 +65,8 @@ class Apply(Node):
Basically, an `Apply` instance is an object that represents the Basically, an `Apply` instance is an object that represents the
Python statement `outputs = op(*inputs)`. Python statement `outputs = op(*inputs)`.
This class is typically instantiated by a `PureOp.make_node` method, which This class is typically instantiated by a `Op.make_node` method, which
is called by `PureOp.__call__`. is called by `Op.__call__`.
The function `theano.compile.function.function` uses `Apply.inputs` The function `theano.compile.function.function` uses `Apply.inputs`
together with `Variable.owner` to search the expression graph and determine together with `Variable.owner` to search the expression graph and determine
...@@ -77,7 +77,7 @@ class Apply(Node): ...@@ -77,7 +77,7 @@ class Apply(Node):
Parameters Parameters
---------- ----------
op : A PureOp instance op : A Op instance
inputs : list of Variable instances inputs : list of Variable instances
outputs : list of Variable instances outputs : list of Variable instances
......
""" """
Defines base classes `Op`, `PureOp`, and `CLinkerOp`. Defines base classes `Op` and `CLinkerOp`.
The `Op` class is the base interface for all operations The `Op` class is the base interface for all operations
compatible with `gof`'s :doc:`graph` routines. compatible with `gof`'s :doc:`graph` routines.
...@@ -11,6 +11,7 @@ import logging ...@@ -11,6 +11,7 @@ import logging
import os import os
import re import re
import sys import sys
import typing
import warnings import warnings
import numpy as np import numpy as np
...@@ -19,6 +20,7 @@ import theano ...@@ -19,6 +20,7 @@ import theano
from theano import config from theano import config
from theano.gof import graph from theano.gof import graph
from theano.gof.fg import FunctionGraph from theano.gof.fg import FunctionGraph
from theano.gof.graph import Apply, Variable
from theano.gof.utils import ( from theano.gof.utils import (
MethodNotDefined, MethodNotDefined,
TestValueError, TestValueError,
...@@ -26,6 +28,7 @@ from theano.gof.utils import ( ...@@ -26,6 +28,7 @@ from theano.gof.utils import (
get_variable_trace_string, get_variable_trace_string,
object2, object2,
) )
from theano.link.c.interface import CLinkerOp
__authors__ = "theano-dev" __authors__ = "theano-dev"
...@@ -118,448 +121,20 @@ def compute_test_value(node): ...@@ -118,448 +121,20 @@ def compute_test_value(node):
output.tag.test_value = storage_map[output][0] output.tag.test_value = storage_map[output][0]
class CLinkerObject: class Op(object2, CLinkerOp):
"""
Standard elements of an Op or Type used with the CLinker.
"""
def c_headers(self):
"""
Optional: Return a list of header files required by code returned by
this class.
Examples
--------
return ['<iostream>', '<math.h>', '/full/path/to/header.h']
These strings will be prefixed with "#include " and inserted at the
beginning of the c source code.
Strings in this list that start neither with '<' nor '"' will be
enclosed in double-quotes.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_headers", type(self), self.__class__.__name__)
def c_header_dirs(self):
"""
Optional: Return a list of header search paths required by code
returned by this class.
Examples
--------
return ['/usr/local/include', '/opt/weirdpath/src/include']
Provides search paths for headers, in addition to those in any relevant
environment variables.
Hint: for unix compilers, these are the things that get '-I' prefixed
in the compiler cmdline.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_header_dirs", type(self), self.__class__.__name__)
def c_libraries(self):
"""
Optional: Return a list of libraries required by code returned by
this class.
Examples
--------
return ['gsl', 'gslcblas', 'm', 'fftw3', 'g2c'].
The compiler will search the directories specified by the environment
variable LD_LIBRARY_PATH in addition to any returned by `c_lib_dirs`.
Hint: for unix compilers, these are the things that get '-l' prefixed
in the compiler cmdline.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_libraries", type(self), self.__class__.__name__)
def c_lib_dirs(self):
"""
Optional: Return a list of library search paths required by code
returned by this class.
Examples
--------
return ['/usr/local/lib', '/opt/weirdpath/build/libs'].
Provides search paths for libraries, in addition to those in any
relevant environment variables (e.g. LD_LIBRARY_PATH).
Hint: for unix compilers, these are the things that get '-L' prefixed
in the compiler cmdline.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_lib_dirs", type(self), self.__class__.__name__)
def c_support_code(self):
"""
Optional: Return utility code (a string, or a list of strings) for use by a `Variable` or `Op` to be
included at global scope prior to the rest of the code for this class.
QUESTION: How many times will this support code be emitted for a graph
with many instances of the same type?
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_support_code", type(self), self.__class__.__name__)
def c_code_cache_version(self):
"""
Return a tuple of integers indicating the version of this Op.
An empty tuple indicates an 'unversioned' Op that will not be cached
between processes.
The cache mechanism may erase cached modules that have been superceded
by newer versions. See `ModuleCache` for details.
See Also
--------
c_code_cache_version_apply()
"""
return ()
def c_compile_args(self):
"""
Optional: Return a list of compile args recommended to compile the
code returned by other methods in this class.
Examples
--------
return ['-ffast-math']
Compiler arguments related to headers, libraries and search paths should
be provided via the functions `c_headers`, `c_libraries`,
`c_header_dirs`, and `c_lib_dirs`.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_compile_args", type(self), self.__class__.__name__)
def c_no_compile_args(self):
"""
Optional: return a list of incompatible gcc compiler arguments.
We will remove those arguments from the command line of gcc. So if
another Op adds a compile arg in the graph that is incompatible
with this Op, the incompatible arg will not be used.
Useful for instance to remove -ffast-math.
EXAMPLE
WRITEME
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined("c_no_compile_args", type(self), self.__class__.__name__)
def c_init_code(self):
"""
Optional: return a list of code snippets to be inserted in module
initialization.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined("c_init_code", type(self), self.__class__.__name__)
class CLinkerOp(CLinkerObject):
"""
Interface definition for `Op` subclasses compiled by `CLinker`.
A subclass should implement WRITEME.
WRITEME: structure of automatically generated C code.
Put this in doc/code_structure.txt
"""
def c_code(self, node, name, inputs, outputs, sub):
"""
Required: return the C implementation of an Op.
Returns C code that does the computation associated to this `Op`,
given names for the inputs and outputs.
Parameters
----------
node : Apply instance
The node for which we are compiling the current c_code.
The same Op may be used in more than one node.
name : str
A name that is automatically assigned and guaranteed to be
unique.
inputs : list of strings
There is a string for each input of the function, and the
string is the name of a C variable pointing to that input.
The type of the variable depends on the declared type of
the input. There is a corresponding python variable that
can be accessed by prepending "py_" to the name in the
list.
outputs : list of strings
Each string is the name of a C variable where the Op should
store its output. The type depends on the declared type of
the output. There is a corresponding python variable that
can be accessed by prepending "py_" to the name in the
list. In some cases the outputs will be preallocated and
the value of the variable may be pre-filled. The value for
an unallocated output is type-dependent.
sub : dict of strings
Extra symbols defined in `CLinker` sub symbols (such as 'fail').
WRITEME
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(f"{self.__class__.__name__}.c_code")
def c_code_cache_version_apply(self, node):
"""
Return a tuple of integers indicating the version of this Op.
An empty tuple indicates an 'unversioned' Op that will not be
cached between processes.
The cache mechanism may erase cached modules that have been
superceded by newer versions. See `ModuleCache` for details.
See Also
--------
c_code_cache_version()
Notes
-----
This function overrides `c_code_cache_version` unless it explicitly
calls `c_code_cache_version`. The default implementation simply
calls `c_code_cache_version` and ignores the `node` argument.
"""
return self.c_code_cache_version()
def c_code_cleanup(self, node, name, inputs, outputs, sub):
"""
Optional: return C code to run after c_code, whether it failed or not.
This is a convenient place to clean up things allocated by c_code().
Parameters
----------
node : Apply instance
WRITEME
name : str
A name that is automatically assigned and guaranteed to be
unique.
inputs : list of strings
There is a string for each input of the function, and the
string is the name of a C variable pointing to that input.
The type of the variable depends on the declared type of
the input. There is a corresponding python variable that
can be accessed by prepending "py_" to the name in the
list.
outputs : list of strings
Each string is the name of a C variable correspoinding to
one of the outputs of the Op. The type depends on the
declared type of the output. There is a corresponding
python variable that can be accessed by prepending "py_" to
the name in the list.
sub : dict of strings
extra symbols defined in `CLinker` sub symbols (such as 'fail').
WRITEME
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(f"{self.__class__.__name__}.c_code_cleanup")
def c_support_code_apply(self, node, name):
"""
Optional: return utility code for use by an `Op` that will be
inserted at global scope, that can be specialized for the
support of a particular `Apply` node.
Parameters
----------
node: an Apply instance in the graph being compiled
name: str
A string or number that serves to uniquely identify this node.
Symbol names defined by this support code should include the name,
so that they can be called from the c_code, and so that they do not
cause name collisions.
Notes
-----
This function is called in addition to c_support_code and will
supplement whatever is returned from there.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined(
"c_support_code_apply", type(self), self.__class__.__name__
)
def c_init_code_apply(self, node, name):
"""
Optional: return a code string specific to the apply
to be inserted in the module initialization code.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A string or number that serves to uniquely identify this node.
Symbol names defined by this support code should include the name,
so that they can be called from the c_code, and so that they do not
cause name collisions.
Notes
-----
This function is called in addition to c_init_code and will supplement
whatever is returned from there.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined("c_init_code_apply", type(self), self.__class__.__name__)
def c_init_code_struct(self, node, name, sub):
"""
Optional: return a code string specific to the apply
to be inserted in the struct initialization code.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A unique name to distinguish variables from those of other nodes.
sub
A dictionary of values to substitute in the code.
Most notably it contains a 'fail' entry that you should place in
your code after setting a python exception to indicate an error.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(
"c_init_code_struct", type(self), self.__class__.__name__
)
def c_support_code_struct(self, node, name):
"""
Optional: return utility code for use by an `Op` that will be
inserted at struct scope, that can be specialized for the
support of a particular `Apply` node.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A unique name to distinguish you variables from those of other
nodes.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined(
"c_support_code_struct", type(self), self.__class__.__name__
)
def c_cleanup_code_struct(self, node, name):
"""
Optional: return a code string specific to the apply to be
inserted in the struct cleanup code.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A unique name to distinguish variables from those of other nodes.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(
"c_cleanup_code_struct", type(self), self.__class__.__name__
)
class PureOp:
"""A class that models and constructs operations in a graph. """A class that models and constructs operations in a graph.
A `PureOp` instance has several responsibilities: A `Op` instance has several responsibilities:
- construct `Apply` nodes via `PureOp.make_node` method, - construct `Apply` nodes via `Op.make_node` method,
- perform the numeric calculation of the modeled operation via - perform the numeric calculation of the modeled operation via
the `PureOp.perform` method, the `Op.perform` method,
- and (optionally) build the gradient-calculating sub-graphs via the - and (optionally) build the gradient-calculating sub-graphs via the
`PureOp.grad` method. `Op.grad` method.
To see how `PureOp`, `Type`, `Variable`, and `Apply` fit together see the To see how `Op`, `Type`, `Variable`, and `Apply` fit together see the
page on :doc:`graph`. page on :doc:`graph`.
For more details regarding how these methods should behave: see the `Op For more details regarding how these methods should behave: see the `Op
...@@ -569,7 +144,7 @@ class PureOp: ...@@ -569,7 +144,7 @@ class PureOp:
default_output = None default_output = None
""" """
An `int` that specifies which output `PureOp.__call__` should return. If An `int` that specifies which output `Op.__call__` should return. If
`None`, then all outputs are returned. `None`, then all outputs are returned.
A subclass should not change this class variable, but instead override it A subclass should not change this class variable, but instead override it
...@@ -577,7 +152,7 @@ class PureOp: ...@@ -577,7 +152,7 @@ class PureOp:
""" """
def make_node(self, *inputs): def make_node(self, *inputs) -> Apply:
"""Construct an `Apply` node that represent the application of this operation to the given inputs. """Construct an `Apply` node that represent the application of this operation to the given inputs.
This must be implemented by sub-classes. This must be implemented by sub-classes.
...@@ -588,12 +163,34 @@ class PureOp: ...@@ -588,12 +163,34 @@ class PureOp:
The constructed `Apply` node. The constructed `Apply` node.
""" """
raise MethodNotDefined("make_node", type(self), self.__class__.__name__) if not hasattr(self, "itypes"):
raise NotImplementedError(
"You can either define itypes and otypes,\
or implement make_node"
)
if not hasattr(self, "otypes"):
raise NotImplementedError(
"You can either define itypes and otypes,\
or implement make_node"
)
if len(inputs) != len(self.itypes):
raise ValueError(
f"We expected {len(self.itypes)} inputs but got {len(inputs)}."
)
if not all(inp.type == it for inp, it in zip(inputs, self.itypes)):
raise TypeError(
f"We expected inputs of types '{str(self.itypes)}' but got types '{str([inp.type for inp in inputs])}'"
)
return theano.Apply(self, inputs, [o() for o in self.otypes])
def __call__(self, *inputs, **kwargs): def __call__(
self, *inputs, **kwargs
) -> typing.Union[Variable, typing.List[Variable],]:
"""Construct an `Apply` node using `self.make_node` and return its outputs. """Construct an `Apply` node using `self.make_node` and return its outputs.
This method is just a wrapper around `PureOp.make_node`. This method is just a wrapper around `Op.make_node`.
It is called by code such as: It is called by code such as:
...@@ -608,14 +205,14 @@ class PureOp: ...@@ -608,14 +205,14 @@ class PureOp:
`Variable`, `y`. The `Apply` node constructed by `self.make_node` `Variable`, `y`. The `Apply` node constructed by `self.make_node`
behind the scenes is available via `y.owner`. behind the scenes is available via `y.owner`.
`PureOp` authors are able to determine which output is returned by this method `Op` authors are able to determine which output is returned by this method
via the `PureOp.default_output` property., but subclasses are free to override this via the `Op.default_output` property., but subclasses are free to override this
function and ignore `default_output`. function and ignore `default_output`.
Parameters Parameters
---------- ----------
inputs : tuple of Variable inputs : tuple of Variable
The `PureOp`'s inputs. The `Op`'s inputs.
kwargs kwargs
Additional keyword arguments to be forwarded to Additional keyword arguments to be forwarded to
`make_node()` *except* for optional argument `return_list` (which `make_node()` *except* for optional argument `return_list` (which
...@@ -629,8 +226,8 @@ class PureOp: ...@@ -629,8 +226,8 @@ class PureOp:
outputs : list of Variable or Variable outputs : list of Variable or Variable
Either a list of output `Variable`s, or a single `Variable`. Either a list of output `Variable`s, or a single `Variable`.
This is determined by the number of outputs produced by the This is determined by the number of outputs produced by the
`PureOp`, the value of the keyword `return_list`, and the value of `Op`, the value of the keyword `return_list`, and the value of
the `PureOp.default_output` property. the `Op.default_output` property.
""" """
return_list = kwargs.pop("return_list", False) return_list = kwargs.pop("return_list", False)
...@@ -687,7 +284,7 @@ class PureOp: ...@@ -687,7 +284,7 @@ class PureOp:
r"""Construct a graph for the L-operator. r"""Construct a graph for the L-operator.
This method is primarily used by `tensor.Lop` and dispatches to This method is primarily used by `tensor.Lop` and dispatches to
`PureOp.grad` by default. `Op.grad` by default.
The *L-operator* computes a *row* vector times the Jacobian. The The *L-operator* computes a *row* vector times the Jacobian. The
mathematical relationship is mathematical relationship is
...@@ -759,9 +356,9 @@ class PureOp: ...@@ -759,9 +356,9 @@ class PureOp:
with the right number of dimensions and the correct dtype. with the right number of dimensions and the correct dtype.
Its shape and stride pattern can be arbitrary. It is not Its shape and stride pattern can be arbitrary. It is not
guaranteed that such pre-set values were produced by a previous call to guaranteed that such pre-set values were produced by a previous call to
this `PureOp.perform`; they could've been allocated by another this `Op.perform`; they could've been allocated by another
`PureOp`'s `perform` method. `Op`'s `perform` method.
A `PureOp` is free to reuse `output_storage` as it sees fit, or to A `Op` is free to reuse `output_storage` as it sees fit, or to
discard it and allocate new memory. discard it and allocate new memory.
Raises Raises
...@@ -778,10 +375,10 @@ class PureOp: ...@@ -778,10 +375,10 @@ class PureOp:
" You can use optimizer=fast_compile instead.", " You can use optimizer=fast_compile instead.",
) )
def do_constant_folding(self, fgraph, node): def do_constant_folding(self, fgraph: FunctionGraph, node: Apply):
"""Determine whether or not constant folding should be performed for the given node. """Determine whether or not constant folding should be performed for the given node.
This allows each `PureOp` to determine if it wants to be constant This allows each `Op` to determine if it wants to be constant
folded when all its inputs are constant. This allows it to choose where folded when all its inputs are constant. This allows it to choose where
it puts its memory/speed trade-off. Also, it could make things faster it puts its memory/speed trade-off. Also, it could make things faster
as constants can't be used for in-place operations (see as constants can't be used for in-place operations (see
...@@ -799,13 +396,6 @@ class PureOp: ...@@ -799,13 +396,6 @@ class PureOp:
""" """
return True return True
class Op(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 # 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. # if params_type is set to a ParamsType. If not, we raise a MethodNotDefined exception.
def get_params(self, node): def get_params(self, node):
...@@ -991,32 +581,6 @@ class Op(object2, PureOp, CLinkerOp): ...@@ -991,32 +581,6 @@ class Op(object2, PureOp, CLinkerOp):
) )
return self.make_py_thunk(node, storage_map, compute_map, no_recycling) return self.make_py_thunk(node, storage_map, compute_map, no_recycling)
def make_node(self, *inputs):
"""
Create a "apply" nodes for the inputs in that order.
"""
if not hasattr(self, "itypes"):
raise NotImplementedError(
"You can either define itypes and otypes,\
or implement make_node"
)
if not hasattr(self, "otypes"):
raise NotImplementedError(
"You can either define itypes and otypes,\
or implement make_node"
)
if len(inputs) != len(self.itypes):
raise ValueError(
f"We expected {len(self.itypes)} inputs but got {len(inputs)}."
)
if not all(inp.type == it for inp, it in zip(inputs, self.itypes)):
raise TypeError(
f"We expected inputs of types '{str(self.itypes)}' but got types '{str([inp.type for inp in inputs])}'"
)
return theano.Apply(self, inputs, [o() for o in self.otypes])
def get_test_value(v): def get_test_value(v):
"""Get the test value for `v`. """Get the test value for `v`.
......
...@@ -1231,7 +1231,7 @@ def local_optimizer(tracks, inplace=False, requirements=()): ...@@ -1231,7 +1231,7 @@ def local_optimizer(tracks, inplace=False, requirements=()):
f.__name__, f.__name__,
) )
for t in tracks: for t in tracks:
if not (isinstance(t, op.Op) or issubclass(t, op.PureOp)): if not (isinstance(t, op.Op) or issubclass(t, op.Op)):
raise ValueError( raise ValueError(
"Tracks are op classes or instances", f.__module__, f.__name__ "Tracks are op classes or instances", f.__module__, f.__name__
) )
......
...@@ -17,259 +17,14 @@ from theano.gof import graph, utils ...@@ -17,259 +17,14 @@ from theano.gof import graph, utils
######## ########
# Type # # Type #
######## ########
from theano.gof.op import CLinkerObject, Op from theano.gof.op import Op
from theano.gof.utils import MethodNotDefined, object2 from theano.gof.utils import MethodNotDefined, object2
from theano.link.c.interface import CLinkerType
__docformat__ = "restructuredtext en" __docformat__ = "restructuredtext en"
class CLinkerType(CLinkerObject):
"""
Interface specification for Types that can be arguments to a `CLinkerOp`.
A CLinkerType instance is mainly responsible for providing the C code that
interfaces python objects with a C `CLinkerOp` implementation.
See WRITEME for a general overview of code generation by `CLinker`.
"""
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.
A hint to tell the compiler that this type is a builtin C type or a
small struct and that its memory footprint is negligible. Simple
objects may be passed on the stack.
"""
return False
def c_literal(self, data):
"""
Optional: WRITEME
Parameters
----------
data : WRITEME
WRITEME
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_literal", type(self), self.__class__.__name__)
def c_declare(self, name, sub, check_input=True):
"""
Required: Return c code to declare variables that will be
instantiated by `c_extract`.
Parameters
----------
name: str
The name of the ``PyObject *`` pointer that will
the value for this Type
sub: dict string -> string
a dictionary of special codes. Most importantly
sub['fail']. See CLinker for more info on `sub` and ``fail``.
Notes
-----
It is important to include the `name` inside of variables which
are declared here, so that name collisions do not occur in the
source file that is generated.
The variable called ``name`` is not necessarily defined yet
where this code is inserted. This code might be inserted to
create class variables for example, whereas the variable ``name``
might only exist inside certain functions in that class.
TODO: Why should variable declaration fail? Is it even allowed to?
Raises
------
MethodNotDefined
Subclass does not implement this method.
Examples
--------
.. code-block: python
return "PyObject ** addr_of_%(name)s;"
"""
raise MethodNotDefined()
def c_init(self, name, sub):
"""
Required: Return c code to initialize the variables that were declared
by self.c_declare().
Notes
-----
The variable called ``name`` is not necessarily defined yet
where this code is inserted. This code might be inserted in a
class constructor for example, whereas the variable ``name``
might only exist inside certain functions in that class.
TODO: Why should variable initialization fail? Is it even allowed to?
Examples
--------
.. code-block: python
return "addr_of_%(name)s = NULL;"
"""
raise MethodNotDefined("c_init", type(self), self.__class__.__name__)
def c_extract(self, name, sub, check_input=True):
"""
Required: Return c code to extract a PyObject * instance.
The code returned from this function must be templated using
``%(name)s``, representing the name that the caller wants to
call this `Variable`. The Python object self.data is in a
variable called "py_%(name)s" and this code must set the
variables declared by c_declare to something representative
of py_%(name)s. If the data is improper, set an appropriate
exception and insert "%(fail)s".
TODO: Point out that template filling (via sub) is now performed
by this function. --jpt
Parameters
----------
name : str
The name of the ``PyObject *`` pointer that will
store the value for this Type.
sub : dict string -> string
A dictionary of special codes. Most importantly
sub['fail']. See CLinker for more info on `sub` and ``fail``.
Raises
------
MethodNotDefined
Subclass does not implement this method.
Examples
--------
.. code-block: python
return "if (py_%(name)s == Py_None)" + \\\
addr_of_%(name)s = &py_%(name)s;" + \\\
"else" + \\\
{ PyErr_SetString(PyExc_ValueError, \\\
'was expecting None'); %(fail)s;}"
"""
raise MethodNotDefined("c_extract", type(self), self.__class__.__name__)
def c_extract_out(self, name, sub, check_input=True):
"""
Optional: C code to extract a PyObject * instance.
Unlike c_extract, c_extract_out has to accept Py_None,
meaning that the variable should be left uninitialized.
"""
return """
if (py_%(name)s == Py_None)
{
%(c_init_code)s
}
else
{
%(c_extract_code)s
}
""" % dict(
name=name,
c_init_code=self.c_init(name, sub),
c_extract_code=self.c_extract(name, sub, check_input),
)
def c_cleanup(self, name, sub):
"""
Return C code to clean up after `c_extract`.
This returns C code that should deallocate whatever `c_extract`
allocated or decrease the reference counts. Do not decrease
py_%(name)s's reference count.
WRITEME
Parameters
----------
name : WRITEME
WRITEME
sub : WRITEME
WRITEME
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined()
def c_sync(self, name, sub):
"""
Required: Return C code to pack C types back into a PyObject.
The code returned from this function must be templated using
"%(name)s", representing the name that the caller wants to
call this Variable. The returned code may set "py_%(name)s"
to a PyObject* and that PyObject* will be accessible from
Python via variable.data. Do not forget to adjust reference
counts if "py_%(name)s" is changed from its original value.
Parameters
----------
name : WRITEME
WRITEME
sub : WRITEME
WRITEME
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_sync", type(self), self.__class__.__name__)
def c_code_cache_version(self):
"""
Return a tuple of integers indicating the version of this Type.
An empty tuple indicates an 'unversioned' Type that will not
be cached between processes.
The cache mechanism may erase cached modules that have been
superceded by newer versions. See `ModuleCache` for details.
"""
return ()
class PureType: class PureType:
""" """
Interface specification for variable type instances. Interface specification for variable type instances.
......
from theano.gof.utils import MethodNotDefined
class CLinkerObject:
"""
Standard elements of an Op or Type used with the CLinker.
"""
def c_headers(self):
"""
Optional: Return a list of header files required by code returned by
this class.
Examples
--------
return ['<iostream>', '<math.h>', '/full/path/to/header.h']
These strings will be prefixed with "#include " and inserted at the
beginning of the c source code.
Strings in this list that start neither with '<' nor '"' will be
enclosed in double-quotes.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_headers", type(self), self.__class__.__name__)
def c_header_dirs(self):
"""
Optional: Return a list of header search paths required by code
returned by this class.
Examples
--------
return ['/usr/local/include', '/opt/weirdpath/src/include']
Provides search paths for headers, in addition to those in any relevant
environment variables.
Hint: for unix compilers, these are the things that get '-I' prefixed
in the compiler cmdline.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_header_dirs", type(self), self.__class__.__name__)
def c_libraries(self):
"""
Optional: Return a list of libraries required by code returned by
this class.
Examples
--------
return ['gsl', 'gslcblas', 'm', 'fftw3', 'g2c'].
The compiler will search the directories specified by the environment
variable LD_LIBRARY_PATH in addition to any returned by `c_lib_dirs`.
Hint: for unix compilers, these are the things that get '-l' prefixed
in the compiler cmdline.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_libraries", type(self), self.__class__.__name__)
def c_lib_dirs(self):
"""
Optional: Return a list of library search paths required by code
returned by this class.
Examples
--------
return ['/usr/local/lib', '/opt/weirdpath/build/libs'].
Provides search paths for libraries, in addition to those in any
relevant environment variables (e.g. LD_LIBRARY_PATH).
Hint: for unix compilers, these are the things that get '-L' prefixed
in the compiler cmdline.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_lib_dirs", type(self), self.__class__.__name__)
def c_support_code(self):
"""
Optional: Return utility code (a string, or a list of strings) for use by a `Variable` or `Op` to be
included at global scope prior to the rest of the code for this class.
QUESTION: How many times will this support code be emitted for a graph
with many instances of the same type?
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_support_code", type(self), self.__class__.__name__)
def c_code_cache_version(self):
"""
Return a tuple of integers indicating the version of this Op.
An empty tuple indicates an 'unversioned' Op that will not be cached
between processes.
The cache mechanism may erase cached modules that have been superceded
by newer versions. See `ModuleCache` for details.
See Also
--------
c_code_cache_version_apply()
"""
return ()
def c_compile_args(self):
"""
Optional: Return a list of compile args recommended to compile the
code returned by other methods in this class.
Examples
--------
return ['-ffast-math']
Compiler arguments related to headers, libraries and search paths should
be provided via the functions `c_headers`, `c_libraries`,
`c_header_dirs`, and `c_lib_dirs`.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_compile_args", type(self), self.__class__.__name__)
def c_no_compile_args(self):
"""
Optional: return a list of incompatible gcc compiler arguments.
We will remove those arguments from the command line of gcc. So if
another Op adds a compile arg in the graph that is incompatible
with this Op, the incompatible arg will not be used.
Useful for instance to remove -ffast-math.
EXAMPLE
WRITEME
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined("c_no_compile_args", type(self), self.__class__.__name__)
def c_init_code(self):
"""
Optional: return a list of code snippets to be inserted in module
initialization.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined("c_init_code", type(self), self.__class__.__name__)
class CLinkerOp(CLinkerObject):
"""
Interface definition for `Op` subclasses compiled by `CLinker`.
A subclass should implement WRITEME.
WRITEME: structure of automatically generated C code.
Put this in doc/code_structure.txt
"""
def c_code(self, node, name, inputs, outputs, sub):
"""
Required: return the C implementation of an Op.
Returns C code that does the computation associated to this `Op`,
given names for the inputs and outputs.
Parameters
----------
node : Apply instance
The node for which we are compiling the current c_code.
The same Op may be used in more than one node.
name : str
A name that is automatically assigned and guaranteed to be
unique.
inputs : list of strings
There is a string for each input of the function, and the
string is the name of a C variable pointing to that input.
The type of the variable depends on the declared type of
the input. There is a corresponding python variable that
can be accessed by prepending "py_" to the name in the
list.
outputs : list of strings
Each string is the name of a C variable where the Op should
store its output. The type depends on the declared type of
the output. There is a corresponding python variable that
can be accessed by prepending "py_" to the name in the
list. In some cases the outputs will be preallocated and
the value of the variable may be pre-filled. The value for
an unallocated output is type-dependent.
sub : dict of strings
Extra symbols defined in `CLinker` sub symbols (such as 'fail').
WRITEME
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(f"{self.__class__.__name__}.c_code")
def c_code_cache_version_apply(self, node):
"""
Return a tuple of integers indicating the version of this Op.
An empty tuple indicates an 'unversioned' Op that will not be
cached between processes.
The cache mechanism may erase cached modules that have been
superceded by newer versions. See `ModuleCache` for details.
See Also
--------
c_code_cache_version()
Notes
-----
This function overrides `c_code_cache_version` unless it explicitly
calls `c_code_cache_version`. The default implementation simply
calls `c_code_cache_version` and ignores the `node` argument.
"""
return self.c_code_cache_version()
def c_code_cleanup(self, node, name, inputs, outputs, sub):
"""
Optional: return C code to run after c_code, whether it failed or not.
This is a convenient place to clean up things allocated by c_code().
Parameters
----------
node : Apply instance
WRITEME
name : str
A name that is automatically assigned and guaranteed to be
unique.
inputs : list of strings
There is a string for each input of the function, and the
string is the name of a C variable pointing to that input.
The type of the variable depends on the declared type of
the input. There is a corresponding python variable that
can be accessed by prepending "py_" to the name in the
list.
outputs : list of strings
Each string is the name of a C variable correspoinding to
one of the outputs of the Op. The type depends on the
declared type of the output. There is a corresponding
python variable that can be accessed by prepending "py_" to
the name in the list.
sub : dict of strings
extra symbols defined in `CLinker` sub symbols (such as 'fail').
WRITEME
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(f"{self.__class__.__name__}.c_code_cleanup")
def c_support_code_apply(self, node, name):
"""
Optional: return utility code for use by an `Op` that will be
inserted at global scope, that can be specialized for the
support of a particular `Apply` node.
Parameters
----------
node: an Apply instance in the graph being compiled
name: str
A string or number that serves to uniquely identify this node.
Symbol names defined by this support code should include the name,
so that they can be called from the c_code, and so that they do not
cause name collisions.
Notes
-----
This function is called in addition to c_support_code and will
supplement whatever is returned from there.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined(
"c_support_code_apply", type(self), self.__class__.__name__
)
def c_init_code_apply(self, node, name):
"""
Optional: return a code string specific to the apply
to be inserted in the module initialization code.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A string or number that serves to uniquely identify this node.
Symbol names defined by this support code should include the name,
so that they can be called from the c_code, and so that they do not
cause name collisions.
Notes
-----
This function is called in addition to c_init_code and will supplement
whatever is returned from there.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined("c_init_code_apply", type(self), self.__class__.__name__)
def c_init_code_struct(self, node, name, sub):
"""
Optional: return a code string specific to the apply
to be inserted in the struct initialization code.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A unique name to distinguish variables from those of other nodes.
sub
A dictionary of values to substitute in the code.
Most notably it contains a 'fail' entry that you should place in
your code after setting a python exception to indicate an error.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(
"c_init_code_struct", type(self), self.__class__.__name__
)
def c_support_code_struct(self, node, name):
"""
Optional: return utility code for use by an `Op` that will be
inserted at struct scope, that can be specialized for the
support of a particular `Apply` node.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A unique name to distinguish you variables from those of other
nodes.
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined(
"c_support_code_struct", type(self), self.__class__.__name__
)
def c_cleanup_code_struct(self, node, name):
"""
Optional: return a code string specific to the apply to be
inserted in the struct cleanup code.
Parameters
----------
node : an Apply instance in the graph being compiled
name : str
A unique name to distinguish variables from those of other nodes.
Raises
------
MethodNotDefined
The subclass does not override this method.
"""
raise MethodNotDefined(
"c_cleanup_code_struct", type(self), self.__class__.__name__
)
class CLinkerType(CLinkerObject):
"""
Interface specification for Types that can be arguments to a `CLinkerOp`.
A CLinkerType instance is mainly responsible for providing the C code that
interfaces python objects with a C `CLinkerOp` implementation.
See WRITEME for a general overview of code generation by `CLinker`.
"""
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.
A hint to tell the compiler that this type is a builtin C type or a
small struct and that its memory footprint is negligible. Simple
objects may be passed on the stack.
"""
return False
def c_literal(self, data):
"""
Optional: WRITEME
Parameters
----------
data : WRITEME
WRITEME
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_literal", type(self), self.__class__.__name__)
def c_declare(self, name, sub, check_input=True):
"""
Required: Return c code to declare variables that will be
instantiated by `c_extract`.
Parameters
----------
name: str
The name of the ``PyObject *`` pointer that will
the value for this Type
sub: dict string -> string
a dictionary of special codes. Most importantly
sub['fail']. See CLinker for more info on `sub` and ``fail``.
Notes
-----
It is important to include the `name` inside of variables which
are declared here, so that name collisions do not occur in the
source file that is generated.
The variable called ``name`` is not necessarily defined yet
where this code is inserted. This code might be inserted to
create class variables for example, whereas the variable ``name``
might only exist inside certain functions in that class.
TODO: Why should variable declaration fail? Is it even allowed to?
Raises
------
MethodNotDefined
Subclass does not implement this method.
Examples
--------
.. code-block: python
return "PyObject ** addr_of_%(name)s;"
"""
raise MethodNotDefined()
def c_init(self, name, sub):
"""
Required: Return c code to initialize the variables that were declared
by self.c_declare().
Notes
-----
The variable called ``name`` is not necessarily defined yet
where this code is inserted. This code might be inserted in a
class constructor for example, whereas the variable ``name``
might only exist inside certain functions in that class.
TODO: Why should variable initialization fail? Is it even allowed to?
Examples
--------
.. code-block: python
return "addr_of_%(name)s = NULL;"
"""
raise MethodNotDefined("c_init", type(self), self.__class__.__name__)
def c_extract(self, name, sub, check_input=True):
"""
Required: Return c code to extract a PyObject * instance.
The code returned from this function must be templated using
``%(name)s``, representing the name that the caller wants to
call this `Variable`. The Python object self.data is in a
variable called "py_%(name)s" and this code must set the
variables declared by c_declare to something representative
of py_%(name)s. If the data is improper, set an appropriate
exception and insert "%(fail)s".
TODO: Point out that template filling (via sub) is now performed
by this function. --jpt
Parameters
----------
name : str
The name of the ``PyObject *`` pointer that will
store the value for this Type.
sub : dict string -> string
A dictionary of special codes. Most importantly
sub['fail']. See CLinker for more info on `sub` and ``fail``.
Raises
------
MethodNotDefined
Subclass does not implement this method.
Examples
--------
.. code-block: python
return "if (py_%(name)s == Py_None)" + \\\
addr_of_%(name)s = &py_%(name)s;" + \\\
"else" + \\\
{ PyErr_SetString(PyExc_ValueError, \\\
'was expecting None'); %(fail)s;}"
"""
raise MethodNotDefined("c_extract", type(self), self.__class__.__name__)
def c_extract_out(self, name, sub, check_input=True):
"""
Optional: C code to extract a PyObject * instance.
Unlike c_extract, c_extract_out has to accept Py_None,
meaning that the variable should be left uninitialized.
"""
return """
if (py_%(name)s == Py_None)
{
%(c_init_code)s
}
else
{
%(c_extract_code)s
}
""" % dict(
name=name,
c_init_code=self.c_init(name, sub),
c_extract_code=self.c_extract(name, sub, check_input),
)
def c_cleanup(self, name, sub):
"""
Return C code to clean up after `c_extract`.
This returns C code that should deallocate whatever `c_extract`
allocated or decrease the reference counts. Do not decrease
py_%(name)s's reference count.
WRITEME
Parameters
----------
name : WRITEME
WRITEME
sub : WRITEME
WRITEME
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined()
def c_sync(self, name, sub):
"""
Required: Return C code to pack C types back into a PyObject.
The code returned from this function must be templated using
"%(name)s", representing the name that the caller wants to
call this Variable. The returned code may set "py_%(name)s"
to a PyObject* and that PyObject* will be accessible from
Python via variable.data. Do not forget to adjust reference
counts if "py_%(name)s" is changed from its original value.
Parameters
----------
name : WRITEME
WRITEME
sub : WRITEME
WRITEME
Raises
------
MethodNotDefined
Subclass does not implement this method.
"""
raise MethodNotDefined("c_sync", type(self), self.__class__.__name__)
def c_code_cache_version(self):
"""
Return a tuple of integers indicating the version of this Type.
An empty tuple indicates an 'unversioned' Type that will not
be cached between processes.
The cache mechanism may erase cached modules that have been
superceded by newer versions. See `ModuleCache` for details.
"""
return ()
...@@ -59,7 +59,7 @@ from theano.compile.function import function ...@@ -59,7 +59,7 @@ from theano.compile.function import function
from theano.compile.io import In, Out from theano.compile.io import In, Out
from theano.compile.mode import AddFeatureOptimizer from theano.compile.mode import AddFeatureOptimizer
from theano.compile.profiling import ScanProfileStats from theano.compile.profiling import ScanProfileStats
from theano.gof import Apply, PureOp from theano.gof import Apply, Op
from theano.gof.graph import equal_computations, io_connection_pattern from theano.gof.graph import equal_computations, io_connection_pattern
from theano.gof.toolbox import NoOutputFromInplace from theano.gof.toolbox import NoOutputFromInplace
from theano.gradient import DisconnectedType, NullType, grad_undefined from theano.gradient import DisconnectedType, NullType, grad_undefined
...@@ -80,7 +80,7 @@ __contact__ = "Razvan Pascanu <r.pascanu@gmail>" ...@@ -80,7 +80,7 @@ __contact__ = "Razvan Pascanu <r.pascanu@gmail>"
_logger = logging.getLogger("theano.scan.op") _logger = logging.getLogger("theano.scan.op")
class Scan(PureOp): class Scan(Op):
""" """
Parameters Parameters
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论