36 '''This module contains a transformation that replaces a PSyIR
37 assignment node with its adjoint form.
40 from __future__
import absolute_import
44 Literal, UnaryOperation, IntrinsicCall
57 '''Implements a transformation to translate a Tangent-Linear
58 assignment to its Adjoint form.
61 def apply(self, node, options=None):
62 '''Apply the Assignment transformation to the specified node. The node
63 must be a valid tangent-linear assignment. The assignment is
64 replaced with its adjoint version.
66 :param node: an Assignment node.
67 :type node: :py:class:`psyclone.psyir.nodes.Assignment`
68 :param options: a dictionary with options for transformations.
69 :type options: Optional[Dict[str, Any]]
76 node.rhs, [BinaryOperation.Operator.ADD,
77 BinaryOperation.Operator.SUB])
80 sym_maths = SymbolicMaths.get()
81 for rhs_term
in rhs_terms:
90 new_rhs_term = rhs_term.copy()
91 for ref
in new_rhs_term.walk(Reference):
97 if node.is_array_assignment
and isinstance(ref, ArrayMixin):
104 if node.lhs.symbol
is ref.symbol:
107 if sym_maths.equal(ref, node.lhs):
110 ref.replace_with(node.lhs.copy())
112 new_rhs_term = node.lhs.copy()
117 rhs_operator = BinaryOperation.Operator.ADD
119 candidate = rhs_term.parent
120 while not isinstance(candidate, Assignment):
121 if (isinstance(candidate, BinaryOperation)
and
122 candidate.operator == BinaryOperation.Operator.SUB
and
123 candidate.children[1]
is previous):
128 if rhs_operator == BinaryOperation.Operator.SUB:
129 rhs_operator = BinaryOperation.Operator.ADD
131 rhs_operator = BinaryOperation.Operator.SUB
133 candidate = candidate.parent
146 deferred_inc.append((new_rhs_term, rhs_operator))
149 rhs = BinaryOperation.create(
150 rhs_operator, active_var.copy(), new_rhs_term)
151 assignment = Assignment.create(active_var.copy(), rhs)
152 node.parent.children.insert(node.position, assignment)
154 if (len(deferred_inc) == 1
and
155 isinstance(deferred_inc[0][0], Reference)):
160 rhs, _ = deferred_inc.pop(0)
161 for term, operator
in deferred_inc:
162 rhs = BinaryOperation.create(operator, rhs, term)
163 assignment = Assignment.create(node.lhs.copy(), rhs)
164 node.parent.children.insert(node.position, assignment)
168 assignment = Assignment.create(
169 node.lhs.copy(), Literal(
"0.0", REAL_TYPE))
170 node.parent.children.insert(node.position, assignment)
175 def _array_ranges_match(self, assign, active_variable):
177 If the supplied assignment is to an array range and the supplied
178 active variable is the entity being assigned to then this routine
179 checks that the array ranges of the LHS and the supplied reference
180 match. If they do not then an exception is raised.
182 :param assign: the assignment that we are checking.
183 :type assign: :py:class:`psyclone.psyir.nodes.Assignment`
184 :param active_variable: an active variable that appears on the \
185 LHS and RHS of the supplied assignment.
186 :type active_variable: :py:class:`psyclone.psyir.nodes.Reference`
188 :raises TangentLinearError: if the supplied assignment is to a \
189 symbol with an array range but the same symbol occurs on the \
190 RHS without an array range.
191 :raises NotImplementedError: if the array ranges on the LHS and \
192 RHS of the assignment for the supplied variable do not match.
197 if not (assign.is_array_assignment
and active_variable.symbol
is
201 if not isinstance(active_variable, ArrayMixin):
203 f
"Assignment is to an array range but found a "
204 f
"reference to the LHS variable "
205 f
"'{assign.lhs.symbol.name}' without array notation"
206 f
" on the RHS: '{assign.debug_string()}'")
208 sym_maths = SymbolicMaths.get()
210 for pos, idx
in enumerate(active_variable.indices):
211 lhs_idx = assign.lhs.indices[pos]
215 if not (type(idx)
is type(lhs_idx)
and
216 sym_maths.equal(idx.start,
218 sym_maths.equal(idx.stop,
220 sym_maths.equal(idx.step,
222 raise NotImplementedError(
223 f
"Different sections of the same active array "
224 f
"'{assign.lhs.symbol.name}' are "
225 f
"accessed on the LHS and RHS of an assignment: "
226 f
"'{assign.debug_string()}'. This is not supported.")
229 '''Perform various checks to ensure that it is valid to apply the
230 AssignmentTrans transformation to the supplied PSyIR Node.
232 :param node: the node that is being checked.
233 :type node: :py:class:`psyclone.psyir.nodes.Assignment`
234 :param options: a dictionary with options for transformations.
235 :type options: Optional[Dict[str, Any]]
237 :raises TransformationError: if the node argument is not an \
239 :raises TangentLinearError: if the assignment does not conform \
240 to the required tangent-linear structure.
244 if not isinstance(node, Assignment):
245 raise TransformationError(
246 f
"Node argument in assignment transformation should be a "
247 f
"PSyIR Assignment, but found '{type(node).__name__}'.")
251 assignment_active_var_names = [
252 var.name
for var
in assign.walk(Reference)
254 if not assignment_active_var_names:
263 f
"Assignment node '{assign.debug_string()}' has the following "
264 f
"active variables on its RHS '{assignment_active_var_names}' "
265 f
"but its LHS '{assign.lhs.name}' is not an active variable.")
269 assign.rhs, [BinaryOperation.Operator.ADD,
270 BinaryOperation.Operator.SUB])
276 if (len(rhs_terms) == 1
and isinstance(rhs_terms[0], Literal)
and
277 float(rhs_terms[0].value) == 0.0):
282 for rhs_term
in rhs_terms:
289 lu_bound_ops = [IntrinsicCall.Intrinsic.LBOUND,
290 IntrinsicCall.Intrinsic.UBOUND]
291 for ref
in rhs_term.walk(Reference):
293 not (isinstance(ref.parent, IntrinsicCall)
and
294 ref.parent.intrinsic
in lu_bound_ops)):
295 active_vars.append(ref)
300 f
"Each non-zero term on the RHS of the assignment "
301 f
"'{assign.debug_string()}' must have an active variable "
302 f
"but '{rhs_term.debug_string()}' does not.")
304 if len(active_vars) > 1:
307 f
"Each term on the RHS of the assignment "
308 f
"'{assign.debug_string()}' must not have more than one "
309 f
"active variable but '{rhs_term.debug_string()}' has "
310 f
"{len(active_vars)}.")
312 if (isinstance(rhs_term, Reference)
and rhs_term.symbol
323 if (isinstance(rhs_term, UnaryOperation)
and
325 UnaryOperation.Operator.MINUS):
326 rhs_term = rhs_term.children[0]
330 rhs_term, [BinaryOperation.Operator.MUL,
331 BinaryOperation.Operator.DIV])
335 for expr_term
in expr_terms:
336 check_term = expr_term
337 if (isinstance(expr_term, UnaryOperation)
and
338 expr_term.operator
in [UnaryOperation.Operator.PLUS,
339 UnaryOperation.Operator.MINUS]):
340 check_term = expr_term.children[0]
341 if (isinstance(check_term, Reference)
and
343 active_variable = check_term
347 f
"Each term on the RHS of the assignment "
348 f
"'{assign.debug_string()}' must be linear with respect "
349 f
"to the active variable, but found "
350 f
"'{rhs_term.debug_string()}'.")
355 candidate = active_variable
356 parent = candidate.parent
357 while not isinstance(parent, Assignment):
361 if (isinstance(parent, BinaryOperation)
and
362 parent.operator == BinaryOperation.Operator.DIV
and
363 parent.children[1]
is candidate):
366 f
"In tangent-linear code an active variable cannot "
367 f
"appear as a denominator but "
368 f
"'{rhs_term.debug_string()}' was found in "
369 f
"'{assign.debug_string()}'.")
372 parent = candidate.parent
380 def _split_nodes(node, binary_operator_list):
381 '''Utility to split an expression into a series of sub-expressions
382 separated by one of the binary operators specified in
383 binary_operator_list.
385 :param node: the node containing the expression to split.
386 :type node: :py:class:`psyclone.psyir.nodes.DataNode`
387 :param binary_operator_list: list of binary operators.
388 :type binary_operator_list: list of
389 :py:class:`psyclone.psyir.nodes.BinaryOperations.Operator`
391 :returns: a list of sub-expressions.
392 :rtype: list of :py:class:`psyclone.psyir.nodes.DataNode`
395 if (isinstance(node, BinaryOperation))
and \
396 (node.operator
in binary_operator_list):
397 lhs_node_list = AssignmentTrans._split_nodes(
398 node.children[0], binary_operator_list)
399 rhs_node_list = AssignmentTrans._split_nodes(
400 node.children[1], binary_operator_list)
401 return lhs_node_list + rhs_node_list
405 return "Convert a tangent-linear PSyIR Assignment to its adjoint form"
410 :returns: the name of the transformation as a string.
414 return type(self).__name__
420 __all__ = [
"AssignmentTrans"]