From 5aa1e0e1755fe5aa8c1df28117d5256aa2a67d2b Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 11 Jan 2020 19:46:18 -0600 Subject: [PATCH 01/11] Improve the documentation of the function registry This improves the documentation of dagrt.function_registry. It also exposes the documentation for the built-in functions. What's not yet incorporated: * changes to isnan (#30) * changes to the type inference (#34) * any interface changes due to #24 --- dagrt/function_registry.py | 200 ++++++++++++++++++++++++++++--------- doc/reference.rst | 4 - 2 files changed, 154 insertions(+), 50 deletions(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 5082151..f4d1cee 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -32,6 +32,47 @@ from dagrt.codegen.data import ( NoneType = type(None) +__doc__ = """ +The function registry is used by targets to register external +functions and customized function call code. + +.. autoclass:: Function +.. autoclass:: FunctionRegistry +.. autoclass:: FunctionNotFound + +.. data:: base_function_registry + +The default function registry, containing all the built-in functions (see +:ref:`builtins`). + +Registering new functions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: register_ode_rhs +.. autofunction:: register_function + +.. _builtins: + +Built-ins +^^^^^^^^^ + +The built-in functions are listed below. This also serves as their language +documentation. + +.. autoclass:: Norm1 +.. autoclass:: Norm2 +.. autoclass:: NormInf +.. autoclass:: DotProduct +.. autoclass:: Len +.. autoclass:: IsNaN +.. autoclass:: Array +.. autoclass:: MatMul +.. autoclass:: Transpose +.. autoclass:: LinearSolve +.. autoclass:: SVD +.. autoclass:: Print + +""" # {{{ function @@ -51,8 +92,21 @@ class Function(RecordWithoutPickling): assigned. .. attribute:: identifier + + The name of the function. + .. attribute:: arg_names + + The names of the arguments to the function. + .. attribute:: default_dict + + A dictionary mapping argument names to default values. + + .. automethod:: get_result_kinds + .. automethod:: register_codegen + .. automethod:: get_codegen + .. automethod:: resolve_args """ def __init__(self, language_to_codegen=None, **kwargs): @@ -84,6 +138,12 @@ class Function(RecordWithoutPickling): raise NotImplementedError() def register_codegen(self, language, codegen_function): + """Return a copy of *self* with *codegen_function* + registered as a code generator for *language*. + + The interface for *codegen_function* depends on the + code generator being used. + """ new_language_to_codegen = self.language_to_codegen.copy() if language in new_language_to_codegen: @@ -95,6 +155,8 @@ class Function(RecordWithoutPickling): return self.copy(language_to_codegen=new_language_to_codegen) def get_codegen(self, language): + """Return the code generator for *language*. + """ try: return self.language_to_codegen[language] except KeyError: @@ -103,6 +165,13 @@ class Function(RecordWithoutPickling): % (self.identifier, language)) def resolve_args(self, arg_dict): + """Resolve positional and keyword arguments to an argument list. + + See also :func:`dagrt.utils.resolve_args`. + + :arg arg_dict: a dictionary mapping numbers (for positional arguments) + or identifiers (for keyword arguments) to values + """ from dagrt.utils import resolve_args return resolve_args(self.arg_names, self.default_dict, arg_dict) @@ -124,6 +193,13 @@ class FixedResultKindsFunction(Function): # {{{ function registry class FunctionRegistry(RecordWithoutPickling): + """ + .. automethod:: register + .. automethod:: __getitem__ + .. automethod:: __contains__ + .. automethod:: register_codegen + .. automethod:: get_codegen + """ def __init__(self, id_to_function=None): if id_to_function is None: id_to_function = {} @@ -143,6 +219,10 @@ class FunctionRegistry(RecordWithoutPickling): return self.copy(id_to_function=new_id_to_function) def __getitem__(self, function_id): + """Return the :class:`Function` with identifier *function_id*. + + :raises FunctionNotFound: when *function_id* was not found + """ try: return self.id_to_function[function_id] except KeyError: @@ -200,24 +280,32 @@ class _NormBase(Function): return (Scalar(is_real_valued=True),) -class _Norm1(_NormBase): - """``norm_1(x)`` returns the 1-norm of *x*.""" +class Norm1(_NormBase): + """``norm_1(x)`` returns the 1-norm of *x*. + *x* is a user type or array. + """ identifier = "norm_1" -class _Norm2(_NormBase): - """``norm_2(x)`` returns the 2-norm of *x*.""" +class Norm2(_NormBase): + """``norm_2(x)`` returns the 2-norm of *x*. + *x* is a user type or array. + """ identifier = "norm_2" -class _NormInf(_NormBase): - """``norm_inf(x)`` returns the infinity-norm of *x*.""" +class NormInf(_NormBase): + """``norm_inf(x)`` returns the infinity-norm of *x*. + *x* is a user type or array. + """ identifier = "norm_inf" -class _DotProduct(Function): - """dot_product(x, y)`` return the dot product of *x* and *y*. The +class DotProduct(Function): + """``dot_product(x, y)`` return the dot product of *x* and *y*. The complex conjugate of *x* is taken first, if applicable. + *x* and *y* are either arrays (that must be of the same length) or the + same user type. """ result_names = ("result",) @@ -236,8 +324,10 @@ class _DotProduct(Function): return (Scalar(is_real_valued=False),) -class _Len(Function): - """``len(x)`` returns the number of degrees of freedom in *x* """ +class Len(Function): + """``len(x)`` returns the number of degrees of freedom in *x*. + *x* is a user type or array. + """ result_names = ("result",) identifier = "len" @@ -253,8 +343,10 @@ class _Len(Function): return (Scalar(is_real_valued=True),) -class _IsNaN(Function): - """``isnan(x)`` returns True if there are any NaNs in *x*""" +class IsNaN(Function): + """``isnan(x)`` returns True if and only if there are any NaNs in *x*. + *x* is a user type, scalar, or array. + """ result_names = ("result",) identifier = "isnan" @@ -270,9 +362,9 @@ class _IsNaN(Function): return (Boolean(),) -class _Array(Function): - """``array(n)`` returns an empty array with n entries in it. - n must be an integer. +class Array(Function): + """``array(n)`` returns an empty array with *n* entries in it. + *n* must be an integer. """ result_names = ("result",) @@ -289,10 +381,10 @@ class _Array(Function): return (Array(is_real_valued=True),) -class _MatMul(Function): +class MatMul(Function): """``matmul(a, b, a_cols, b_cols)`` returns a 1D array containing the - matrix resulting from multiplying the arrays a and b (both interpreted - as matrices, with a number of columns *a_cols* and *b_cols* respectively) + matrix resulting from multiplying the arrays *a* and *b* (both interpreted + as matrices, with a number of columns *a_cols* and *b_cols* respectively). """ result_names = ("result",) @@ -321,9 +413,10 @@ class _MatMul(Function): return (Array(is_real_valued),) -class _Transpose(Function): +class Transpose(Function): """``transpose(a, a_cols)`` returns a 1D array containing the - matrix resulting from transposing the array a + matrix resulting from transposing the array *a* (interpreted + as a matrix with *a_cols* columns). """ result_names = ("result",) @@ -348,10 +441,10 @@ class _Transpose(Function): return (Array(is_real_valued),) -class _LinearSolve(Function): +class LinearSolve(Function): """``linear_solve(a, b, a_cols, b_cols)`` returns a 1D array containing the - matrix resulting from multiplying the matrix inverse of a by b (both interpreted - as matrices, with a number of columns *a_cols* and *b_cols* respectively) + matrix resulting from multiplying the matrix inverse of *a* by *b*, both interpreted + as matrices, with a number of columns *a_cols* and *b_cols* respectively. """ result_names = ("result",) @@ -380,7 +473,7 @@ class _LinearSolve(Function): return (Array(is_real_valued),) -class _SVD(Function): +class SVD(Function): """``SVD(a, a_cols)`` returns a 2D array ``u``, a 1D array ``sigma``, and a 2D array ``vt``, representing the (reduced) SVD of ``a``. """ @@ -407,7 +500,7 @@ class _SVD(Function): return (Array(is_real_valued), Array(is_real_valued), Array(is_real_valued)) -class _Print(Function): +class Print(Function): """``print(arg)`` prints the given operand to standard output. Returns an integer that may be ignored. """ @@ -443,18 +536,18 @@ def _make_bfr(): bfr = FunctionRegistry() for func, py_pattern in [ - (_Norm1(), "self._builtin_norm_1({args})"), - (_Norm2(), "self._builtin_norm_2({args})"), - (_NormInf(), "self._builtin_norm_inf({args})"), - (_DotProduct(), "{numpy}.vdot({args})"), - (_Len(), "{numpy}.size({args})"), - (_IsNaN(), "{numpy}.isnan({args})"), - (_Array(), "self._builtin_array({args})"), - (_MatMul(), "self._builtin_matmul({args})"), - (_Transpose(), "self._builtin_transpose({args})"), - (_LinearSolve(), "self._builtin_linear_solve({args})"), - (_Print(), "self._builtin_print({args})"), - (_SVD(), "self._builtin_svd({args})"), + (Norm1(), "self._builtin_norm_1({args})"), + (Norm2(), "self._builtin_norm_2({args})"), + (NormInf(), "self._builtin_norm_inf({args})"), + (DotProduct(), "{numpy}.vdot({args})"), + (Len(), "{numpy}.size({args})"), + (IsNaN(), "{numpy}.isnan({args})"), + (Array(), "self._builtin_array({args})"), + (MatMul(), "self._builtin_matmul({args})"), + (Transpose(), "self._builtin_transpose({args})"), + (LinearSolve(), "self._builtin_linear_solve({args})"), + (Print(), "self._builtin_print({args})"), + (SVD(), "self._builtin_svd({args})"), ]: bfr = bfr.register(func) @@ -466,23 +559,23 @@ def _make_bfr(): import dagrt.codegen.fortran as f - bfr = bfr.register_codegen(_Norm2.identifier, "fortran", + bfr = bfr.register_codegen(Norm2.identifier, "fortran", f.codegen_builtin_norm_2) - bfr = bfr.register_codegen(_Len.identifier, "fortran", + bfr = bfr.register_codegen(Len.identifier, "fortran", f.codegen_builtin_len) - bfr = bfr.register_codegen(_IsNaN.identifier, "fortran", + bfr = bfr.register_codegen(IsNaN.identifier, "fortran", f.codegen_builtin_isnan) - bfr = bfr.register_codegen(_Array.identifier, "fortran", + bfr = bfr.register_codegen(Array.identifier, "fortran", f.builtin_array) - bfr = bfr.register_codegen(_MatMul.identifier, "fortran", + bfr = bfr.register_codegen(MatMul.identifier, "fortran", f.builtin_matmul) - bfr = bfr.register_codegen(_Transpose.identifier, "fortran", + bfr = bfr.register_codegen(Transpose.identifier, "fortran", f.builtin_transpose) - bfr = bfr.register_codegen(_LinearSolve.identifier, "fortran", + bfr = bfr.register_codegen(LinearSolve.identifier, "fortran", f.builtin_linear_solve) - bfr = bfr.register_codegen(_SVD.identifier, "fortran", + bfr = bfr.register_codegen(SVD.identifier, "fortran", f.builtin_svd) - bfr = bfr.register_codegen(_Print.identifier, "fortran", + bfr = bfr.register_codegen(Print.identifier, "fortran", f.builtin_print) return bfr @@ -547,6 +640,8 @@ def register_ode_rhs( function_registry, output_type_id, identifier=None, input_type_ids=None, input_names=None): + """Register a function as an ODE right-hand side. + """ if identifier is None: identifier = ""+output_type_id @@ -566,6 +661,19 @@ def register_function( default_dict=None, result_names=(), result_kinds=()): + """Register a function returning output(s) of fixed kind. + + :arg function_registry: the base :class:`FunctionRegistry` + :arg identifier: a string, the function identifier + :arg arg_names: a list of strings, the names of the arguments + :arg default_dict: a dictionary mapping argument names to default + values + :arg result_names: a list of strings, the names of the output(s) + :arg result_kinds: a list of :class:`dagrt.codegen.data.SymbolKinds`, + the kinds of the output(s) + + :returns: a new :class:`FunctionRegistry` + """ return function_registry.register( FixedResultKindsFunction( diff --git a/doc/reference.rst b/doc/reference.rst index 6d5e74d..61a432c 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -28,11 +28,7 @@ Fortran Function registry ~~~~~~~~~~~~~~~~~ -The function registry is used by targets to register external -functions and customized function call code. - .. automodule:: dagrt.function_registry - :members: Transformations ~~~~~~~~~~~~~~~ -- GitLab From 251c6d28ddbc9daa5ecdfdf6d9542ee9b620187e Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 11 Jan 2020 19:55:24 -0600 Subject: [PATCH 02/11] flake8 fixes --- dagrt/function_registry.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index f4d1cee..0912d1e 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -74,6 +74,7 @@ documentation. """ + # {{{ function class FunctionNotFound(KeyError): @@ -362,7 +363,7 @@ class IsNaN(Function): return (Boolean(),) -class Array(Function): +class Array_(Function): """``array(n)`` returns an empty array with *n* entries in it. *n* must be an integer. """ @@ -443,8 +444,9 @@ class Transpose(Function): class LinearSolve(Function): """``linear_solve(a, b, a_cols, b_cols)`` returns a 1D array containing the - matrix resulting from multiplying the matrix inverse of *a* by *b*, both interpreted - as matrices, with a number of columns *a_cols* and *b_cols* respectively. + matrix resulting from multiplying the matrix inverse of *a* by *b*, both + interpreted as matrices, with a number of columns *a_cols* and *b_cols* + respectively. """ result_names = ("result",) @@ -542,7 +544,7 @@ def _make_bfr(): (DotProduct(), "{numpy}.vdot({args})"), (Len(), "{numpy}.size({args})"), (IsNaN(), "{numpy}.isnan({args})"), - (Array(), "self._builtin_array({args})"), + (Array_(), "self._builtin_array({args})"), (MatMul(), "self._builtin_matmul({args})"), (Transpose(), "self._builtin_transpose({args})"), (LinearSolve(), "self._builtin_linear_solve({args})"), @@ -565,7 +567,7 @@ def _make_bfr(): f.codegen_builtin_len) bfr = bfr.register_codegen(IsNaN.identifier, "fortran", f.codegen_builtin_isnan) - bfr = bfr.register_codegen(Array.identifier, "fortran", + bfr = bfr.register_codegen(Array_.identifier, "fortran", f.builtin_array) bfr = bfr.register_codegen(MatMul.identifier, "fortran", f.builtin_matmul) -- GitLab From 6b490b0964e3f01a241b4f40d1e12ecdecd4b131 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 11 Jan 2020 19:55:56 -0600 Subject: [PATCH 03/11] Rename Array to Array_ --- dagrt/function_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 0912d1e..dcc51e0 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -65,7 +65,7 @@ documentation. .. autoclass:: DotProduct .. autoclass:: Len .. autoclass:: IsNaN -.. autoclass:: Array +.. autoclass:: Array_ .. autoclass:: MatMul .. autoclass:: Transpose .. autoclass:: LinearSolve -- GitLab From 75fb7e6e2a8120518c1d393c82d4c44e76e02026 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 11 Jan 2020 19:56:36 -0600 Subject: [PATCH 04/11] Add noqa --- dagrt/function_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index dcc51e0..75d9ebc 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -363,7 +363,7 @@ class IsNaN(Function): return (Boolean(),) -class Array_(Function): +class Array_(Function): # noqa """``array(n)`` returns an empty array with *n* entries in it. *n* must be an integer. """ -- GitLab From de965ba313442a22a9cfad724686bf326f276ee0 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 11 Jan 2020 21:17:42 -0600 Subject: [PATCH 05/11] Naming consistency --- dagrt/function_registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 75d9ebc..237dece 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -43,7 +43,7 @@ functions and customized function call code. .. data:: base_function_registry The default function registry, containing all the built-in functions (see -:ref:`builtins`). +:ref:`built-ins`). Registering new functions ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -51,7 +51,7 @@ Registering new functions .. autofunction:: register_ode_rhs .. autofunction:: register_function -.. _builtins: +.. _built-ins: Built-ins ^^^^^^^^^ -- GitLab From da79174008699f0528d1ebee0f5eb78f7fd3c50f Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 12 Jan 2020 21:51:10 +0100 Subject: [PATCH 06/11] Apply suggestion to dagrt/function_registry.py --- dagrt/function_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 237dece..6cd694f 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -34,7 +34,7 @@ NoneType = type(None) __doc__ = """ The function registry is used by targets to register external -functions and customized function call code. +functions and invoke user-specified code, including but not limited ODE right-hand sides. .. autoclass:: Function .. autoclass:: FunctionRegistry -- GitLab From 69e2e934d314a89299e04e7770459d281184233a Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 12 Jan 2020 21:51:15 +0100 Subject: [PATCH 07/11] Apply suggestion to dagrt/function_registry.py --- dagrt/function_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 6cd694f..4b75cc5 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -33,7 +33,7 @@ from dagrt.codegen.data import ( NoneType = type(None) __doc__ = """ -The function registry is used by targets to register external +The function registry is used by targets to resolve external functions and invoke user-specified code, including but not limited ODE right-hand sides. .. autoclass:: Function -- GitLab From ada3152781cc4e8d112aa688be8d6b660dbddbdd Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 12 Jan 2020 14:53:33 -0600 Subject: [PATCH 08/11] Fix line length --- dagrt/function_registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 4b75cc5..dae4216 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -33,8 +33,8 @@ from dagrt.codegen.data import ( NoneType = type(None) __doc__ = """ -The function registry is used by targets to resolve external -functions and invoke user-specified code, including but not limited ODE right-hand sides. +The function registry is used by targets to resolve external functions and +invoke user-specified code, including but not limited ODE right-hand sides. .. autoclass:: Function .. autoclass:: FunctionRegistry -- GitLab From 3b2f0624394e0e4bf92c272990156b6cc5f8131e Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 12 Jan 2020 15:39:27 -0600 Subject: [PATCH 09/11] Document register_ode_rhs --- dagrt/function_registry.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index dae4216..8ef82ba 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -643,6 +643,31 @@ def register_ode_rhs( output_type_id, identifier=None, input_type_ids=None, input_names=None): """Register a function as an ODE right-hand side. + + Functions registered through this call have the following characteristics. + First, there is a single return value of the user type whose type identifier + is *output_type_id*. Second, the function has as its first argument a scalar + named *t*. Last, the remaining argument list to the function consists of + user type values. + + For example, considering the ODE :math:`y' = f(t, y)`, the following call + registers a right-hand side function with name *f* and user type *y*:: + + freg = register_ode_rhs(freg, UserType("y"), identifier="f") + + :arg function_registry: the base function registry + :arg output_type_id: a string, the user type ID returned by the call. + :arg identifier: the full name of the function. If not provided, defaults + to * + output_type_id*. + :arg input_type_ids: a tuple of strings, the identifiers of the user types + which are the arguments to the right-hand side function. An automatically + added *t* argument occurs before these arguments. If not provided, + defaults to *(output_type_id,)*. + :arg input_names: a tuple of strings, the names of the inputs. If not provided, + defaults to *input_type_ids*. + + :returns: a new :class:`FunctionRegistry` + """ if identifier is None: identifier = ""+output_type_id -- GitLab From 06925a445f4a2f96332cea323de36de09ad3436a Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 12 Jan 2020 15:42:20 -0600 Subject: [PATCH 10/11] Fix a typo --- dagrt/function_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 8ef82ba..4cc045f 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -653,7 +653,7 @@ def register_ode_rhs( For example, considering the ODE :math:`y' = f(t, y)`, the following call registers a right-hand side function with name *f* and user type *y*:: - freg = register_ode_rhs(freg, UserType("y"), identifier="f") + freg = register_ode_rhs(freg, "y", identifier="f") :arg function_registry: the base function registry :arg output_type_id: a string, the user type ID returned by the call. -- GitLab From 65b1604c082de1c53e34c8c48af11e10e1b24ae0 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 12 Jan 2020 15:47:09 -0600 Subject: [PATCH 11/11] Fix another typo --- dagrt/function_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/function_registry.py b/dagrt/function_registry.py index 4cc045f..e084e7c 100644 --- a/dagrt/function_registry.py +++ b/dagrt/function_registry.py @@ -34,7 +34,7 @@ NoneType = type(None) __doc__ = """ The function registry is used by targets to resolve external functions and -invoke user-specified code, including but not limited ODE right-hand sides. +invoke user-specified code, including but not limited to ODE right-hand sides. .. autoclass:: Function .. autoclass:: FunctionRegistry -- GitLab