From 810e2c13a163da49a959b5dd249b0617e5ade1f6 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 14 Jul 2020 16:09:59 -0500 Subject: [PATCH 1/8] New AssignImplicit interface --- dagrt/language.py | 166 +++++++++++++++++++++++++++++++--------------- dagrt/version.py | 2 +- 2 files changed, 113 insertions(+), 55 deletions(-) diff --git a/dagrt/language.py b/dagrt/language.py index b85031e..fb9f823 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -294,23 +294,25 @@ class AssignImplicit(AssignmentBase): """ .. attribute:: assignees - A tuple of strings. The names of the variables to assign with the - results of solving for `solve_variables` + A tuple of strings, the names of the variables that are assigned by this + instruction. The assignees represent solutions to a single + equation or a system of (implicit) equations. - .. attribute:: solve_variables + .. attribute:: input_expressions - A tuple of strings, the names of the variables being solved for + A dictionary mapping strings to (symbolic) expressions that are + evaluated as the inputs to an equation solver. The expressions in this + dictionary are tracked for dependency and transformation purposes. - .. attribute:: expressions + For ease of organization only, the values in this dictionary are allowed + to contain nested tuples of expressions, though tuples are not + otherwise generally supported in :mod:`dagrt` expressions. - A tuple of expressions, which represent the left hand sides of a - system of (possibly nonlinear) equations. The solver will attempt - to find a simultaneous root of the system. + .. attribute:: params - .. attribute:: other_params - - A dictionary used to pass extra arguments to the solver, for instance - a starting guess + A dictionary mapping strings to (non-symbolic expression) parameters + that describe the system of equations solved by the implementation of + this instruction. .. attribute:: solver_id @@ -318,16 +320,20 @@ class AssignImplicit(AssignmentBase): system. This identifier is intended to match information about solvers which is supplied by the user. + The contents of this dictionaries *input_expressions* and *params* are + deliberately under-specified and the details are expected to be documented + by the scheme designer. + """ - def __init__(self, assignees, solve_variables, expressions, other_params, - solver_id, **kwargs): - Statement.__init__(self, assignees=assignees, - solve_variables=solve_variables, - expressions=expressions, - other_params=other_params, - solver_id=solver_id, - **kwargs) + def __init__(self, assignees, input_expressions, params, solver_id, + **kwargs): + Statement.__init__(self, + assignees=assignees, + input_expressions=input_expressions, + params=params, + solver_id=solver_id, + **kwargs) exec_method = six.moves.intern("exec_AssignImplicit") @@ -336,19 +342,13 @@ class AssignImplicit(AssignmentBase): def get_read_variables(self): # Variables can be read by: - # 1. expressions (except for those in solve_variables) - # 2. values in other_params - # 3. condition - from itertools import chain - - def flatten(iter_arg): - return chain(*list(iter_arg)) + # 1. values in input_expressions + # 2. superclass attributes (i.e., condition) variables = super(AssignImplicit, self).get_read_variables() - variables |= set(flatten(get_variables(expr) for expr in self.expressions)) - variables -= set(self.solve_variables) - variables |= set(flatten(get_variables(expr) for expr - in self.other_params.values())) + variables |= set(var + for expr in self.input_expressions.values() + for var in get_variables(expr)) return variables def map_expressions(self, mapper, include_lhs=True): @@ -361,26 +361,52 @@ class AssignImplicit(AssignmentBase): else: assignees = self.assignees + input_expressions = {key: mapper(val) + for key, val in input_expressions.items()} + return (super(AssignImplicit, self) .map_expressions(mapper, include_lhs=include_lhs) .copy( assignees=assignees, - expressions=mapper(self.expressions))) + input_expressions=input_expressions)) def __str__(self): + def stringify_tuple(tuple_maybe): + # This differs from str() in that the elements of the tuple are + # stringified with str() rather than repr(). This makes the + # stringified output of nested tuples of symbolic expressions look + # nicer. + if not isinstance(tuple_maybe, tuple): + return str(tuple_maybe) + result = ["("] + for elem in tuple_maybe: + if len(result) >= 2: + result.append(", ") + result.append(stringify_tuple(elem)) + if len(result) == 2: + result.append(",)") + else: + result.append(")") + return "".join(result) + lines = [] + + def format_params(dict_, stringifier): + nonlocal lines + for key, val in dict_.items(): + lines.append(" %s = %s" % (key, stringifier(val))) + lines.append("AssignImplicit") - lines.append("solver_id = " + str(self.solver_id)) - for assignee_index, assignee in enumerate(self.assignees): - lines.append(assignee + " <- " + self.solve_variables[assignee_index]) - lines.append("where") - for expression in self.expressions: - lines.append(" " + str(expression) + " = 0") - if self.other_params: - lines.append("with parameters") - for param_name, param_value in self.other_params.items(): - lines.append(param_name + ": " + str(param_value)) - lines.append(self._condition_printing_suffix()) + lines.append("assignees = %s" % ", ".join(self.assignees)) + lines.append("solver_id = %s" % self.solver_id) + if self.input_expressions: + lines.append("with input expressions:") + format_params(self.input_expressions, stringify_tuple) + if self.params: + lines.append("with params:") + format_params(self.params, str) + if self.condition is not True: + lines.append(self._condition_printing_suffix()) return "\n".join(lines) @@ -808,7 +834,7 @@ class CodeBuilder(object): **Language support:** .. automethod:: assign - .. automethod:: assign_implicit + .. automethod:: assign_implicit_1 .. automethod:: yield_state .. automethod:: fail_step .. automethod:: raise_ @@ -1096,18 +1122,50 @@ class CodeBuilder(object): __call__ = assign - def assign_implicit_1(self, assignee, solve_component, expression, guess, - solver_id=None): - """Special case of AssignImplicit when there is 1 component to solve for.""" - self.assign_implicit( - (assignee.name,), (solve_component.name,), (expression,), - {"guess": guess}, solver_id) + def assign_implicit(self, + assignees, input_expressions=None, params=None, solver_id=None): + """Generate code for an implicit assignment. See :class:`AssignImplicit`. + + *assignees* may be a variable, a variable name, or a tuple of either. + """ + assignees_tuple = [] + + if input_expressions is None: + input_expressions = {} + + if params is None: + params = {} + + from pymbolic.primitives import Variable + + if isinstance(assignees, str): + from pymbolic import parse + assignees = parse(assignees) + + for assignee in assignees: + if isinstance(assignee, str): + assignees_tuple.append(assignee) + elif isinstance(assignee, Variable): + assignees_tuple.append(assignee.name) + else: + raise ValueError( + "could not determine assignee name: %s" % assignee) - def assign_implicit(self, assignees, solve_components, expressions, - other_params, solver_id): self._add_statement(AssignImplicit( - assignees, solve_components, - expressions, other_params, solver_id)) + assignees=tuple(assignees_tuple), + input_expressions=input_expressions, + params=params, + solver_id=solver_id)) + + def assign_implicit_1(self, + assignee, input_expressions=None, params=None, solver_id=None): + """Special case of :meth:`CodeBuilder.assign_implicit` for a single assignee. + """ + return self.assign_implicit( + assignees=(assignee,), + input_expressions=input_expressions, + params=params, + solver_id=solver_id) def yield_state(self, expression, component_id, time, time_id): """Yield a value.""" diff --git a/dagrt/version.py b/dagrt/version.py index 5cea885..82ea6fb 100644 --- a/dagrt/version.py +++ b/dagrt/version.py @@ -1,2 +1,2 @@ -VERSION = (2020, 1) +VERSION = (2020, 2) VERSION_TEXT = ".".join(str(i) for i in VERSION) -- GitLab From 772bfdb7d48d7013562eea285caad34d4272d46d Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 14 Jul 2020 16:16:08 -0500 Subject: [PATCH 2/8] flake8 fix --- dagrt/language.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/language.py b/dagrt/language.py index fb9f823..d109278 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -362,7 +362,7 @@ class AssignImplicit(AssignmentBase): assignees = self.assignees input_expressions = {key: mapper(val) - for key, val in input_expressions.items()} + for key, val in self.input_expressions.items()} return (super(AssignImplicit, self) .map_expressions(mapper, include_lhs=include_lhs) -- GitLab From d15e8172e6c7641a46cc2a7164870349f5ad9416 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 22 Jul 2020 16:38:45 -0500 Subject: [PATCH 3/8] Generate documentation for CodeBuilder.assign_implicit --- dagrt/language.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dagrt/language.py b/dagrt/language.py index d109278..f32110a 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -834,6 +834,7 @@ class CodeBuilder(object): **Language support:** .. automethod:: assign + .. automethod:: assign_implicit .. automethod:: assign_implicit_1 .. automethod:: yield_state .. automethod:: fail_step -- GitLab From a5256719e45b91c752721b73774b4894ab41682b Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 22 Jul 2020 17:10:29 -0500 Subject: [PATCH 4/8] Improve AssignImplicit documentation --- dagrt/language.py | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/dagrt/language.py b/dagrt/language.py index f32110a..965720a 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -301,12 +301,12 @@ class AssignImplicit(AssignmentBase): .. attribute:: input_expressions A dictionary mapping strings to (symbolic) expressions that are - evaluated as the inputs to an equation solver. The expressions in this - dictionary are tracked for dependency and transformation purposes. + evaluated (at runtime) as the inputs to an equation solver. The expressions + in this dictionary are tracked for dependency and transformation purposes. - For ease of organization only, the values in this dictionary are allowed - to contain nested tuples of expressions, though tuples are not - otherwise generally supported in :mod:`dagrt` expressions. + To help organize structured data such as a Butcher tableau, the values in + this dictionary are allowed to contain nested tuples of expressions, though + tuples are not otherwise generally supported in :mod:`dagrt` expressions. .. attribute:: params @@ -322,7 +322,33 @@ class AssignImplicit(AssignmentBase): The contents of this dictionaries *input_expressions* and *params* are deliberately under-specified and the details are expected to be documented - by the scheme designer. + by the scheme designer. It should contain enough information to be able to + reconstruct the system of equations to be solved. + + As an example, consider a system consisting of a single implicit equation + arising from the solution of the ODE :math:`y' = f(t, y)` using an + Adams-Bashforth method + + .. math:: + + y_{n+1} = y_n - 1/2 h f(t_n, y_n) + 3/2 h f(t_{n+1}, y_{n+1}), + + where :math:`y_{n+1}` is the solved-for variable. + + There are many ways to describe this system in an :class:`AssignImplicit` + instruction, but the following interface is a possibility. ``input_expressions`` + could contain keys *state*, *times*, and *coeffs*, where + + - the value corresponding to *state* is a symbolic expression that evaluates + to :math:`y_n`, + - the value corresponding to *times* is a tuple of symbolic expressions + evaluating to :math:`t_{n}` and :math:`t_{n+1}`, + - the value corresponding to *coeffs* is a tuple of symbolic expressions + evaluating to :math:`-1/2 h` and :math:`3/2h`. + + ``params`` could contain the key *component_id*, whose value associates the + right-hand side function :math:`f` to the solution component (useful in the + case of multiple components). """ @@ -835,7 +861,6 @@ class CodeBuilder(object): .. automethod:: assign .. automethod:: assign_implicit - .. automethod:: assign_implicit_1 .. automethod:: yield_state .. automethod:: fail_step .. automethod:: raise_ -- GitLab From 80df54507c8f432982ef8937e2ebf44649384a56 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 22 Jul 2020 17:11:47 -0500 Subject: [PATCH 5/8] markup consistency --- dagrt/language.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/language.py b/dagrt/language.py index 965720a..35de76d 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -320,7 +320,7 @@ class AssignImplicit(AssignmentBase): system. This identifier is intended to match information about solvers which is supplied by the user. - The contents of this dictionaries *input_expressions* and *params* are + The contents of this dictionaries ``input_expressions`` and ``params`` are deliberately under-specified and the details are expected to be documented by the scheme designer. It should contain enough information to be able to reconstruct the system of equations to be solved. -- GitLab From 73981e18a5b0535ee3568a0d2e93ee91dbe40379 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 22 Jul 2020 17:12:10 -0500 Subject: [PATCH 6/8] pronoun agreement --- dagrt/language.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/language.py b/dagrt/language.py index 35de76d..31cd6df 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -322,7 +322,7 @@ class AssignImplicit(AssignmentBase): The contents of this dictionaries ``input_expressions`` and ``params`` are deliberately under-specified and the details are expected to be documented - by the scheme designer. It should contain enough information to be able to + by the scheme designer. They should contain enough information to be able to reconstruct the system of equations to be solved. As an example, consider a system consisting of a single implicit equation -- GitLab From 67be2c73dc890f3b61d6ee11703572174019f6c0 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 22 Jul 2020 17:13:04 -0500 Subject: [PATCH 7/8] Improve doc clarity --- dagrt/language.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/language.py b/dagrt/language.py index 31cd6df..93bc49c 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -348,7 +348,7 @@ class AssignImplicit(AssignmentBase): ``params`` could contain the key *component_id*, whose value associates the right-hand side function :math:`f` to the solution component (useful in the - case of multiple components). + case where the method being generated contains different right-hand sides). """ -- GitLab From cb276db125549a632ec0f65de65801a320023912 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 23 Jul 2020 00:06:25 -0500 Subject: [PATCH 8/8] Grammar fix --- dagrt/language.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dagrt/language.py b/dagrt/language.py index 93bc49c..c1257f4 100644 --- a/dagrt/language.py +++ b/dagrt/language.py @@ -320,7 +320,7 @@ class AssignImplicit(AssignmentBase): system. This identifier is intended to match information about solvers which is supplied by the user. - The contents of this dictionaries ``input_expressions`` and ``params`` are + The contents of the dictionaries ``input_expressions`` and ``params`` are deliberately under-specified and the details are expected to be documented by the scheme designer. They should contain enough information to be able to reconstruct the system of equations to be solved. -- GitLab