# -*- coding: utf-8 -*-
"""Signature computation for forms."""
# Copyright (C) 2012-2016 Martin Sandve Alnæs
#
# This file is part of UFL (https://www.fenicsproject.org)
#
# SPDX-License-Identifier: LGPL-3.0-or-later
import hashlib
from ufl.classes import (Label,
Index, MultiIndex,
Coefficient, Argument,
GeometricQuantity, ConstantValue, Constant,
ExprList, ExprMapping)
from ufl.log import error
from ufl.corealg.traversal import traverse_unique_terminals, unique_post_traversal
from ufl.algorithms.domain_analysis import canonicalize_metadata
[docs]def compute_multiindex_hashdata(expr, index_numbering):
data = []
for i in expr:
if isinstance(i, Index):
j = index_numbering.get(i)
if j is None:
# Use negative ints for Index
j = -(len(index_numbering) + 1)
index_numbering[i] = j
data.append(j)
else:
# Use nonnegative ints for FixedIndex
data.append(int(i))
return tuple(data)
[docs]def compute_terminal_hashdata(expressions, renumbering):
if not isinstance(expressions, list):
expressions = [expressions]
assert renumbering is not None
# Extract a unique numbering of free indices, as well as form
# arguments, and just take repr of the rest of the terminals while
# we're iterating over them
terminal_hashdata = {}
labels = {}
index_numbering = {}
for expression in expressions:
for expr in traverse_unique_terminals(expression):
if isinstance(expr, MultiIndex):
# Indices need a canonical numbering for a stable
# signature, thus this algorithm
data = compute_multiindex_hashdata(expr, index_numbering)
elif isinstance(expr, ConstantValue):
data = expr._ufl_signature_data_(renumbering)
elif isinstance(expr, Coefficient):
data = expr._ufl_signature_data_(renumbering)
elif isinstance(expr, Constant):
data = expr._ufl_signature_data_(renumbering)
elif isinstance(expr, Argument):
data = expr._ufl_signature_data_(renumbering)
elif isinstance(expr, GeometricQuantity):
data = expr._ufl_signature_data_(renumbering)
elif isinstance(expr, Label):
# Numbering labels as we visit them # TODO: Include in
# renumbering
data = labels.get(expr)
if data is None:
data = "L%d" % len(labels)
labels[expr] = data
elif isinstance(expr, ExprList):
# Not really a terminal but can have 0 operands...
data = "[]"
elif isinstance(expr, ExprMapping):
# Not really a terminal but can have 0 operands...
data = "{}"
else:
error("Unknown terminal type %s" % type(expr))
terminal_hashdata[expr] = data
return terminal_hashdata
[docs]def compute_expression_hashdata(expression, terminal_hashdata) -> bytes:
cache = {}
for expr in unique_post_traversal(expression):
# Uniquely traverse tree and hash each node
# E.g. (a + b*c) is hashed as hash([+, hash(a), hash([*, hash(b), hash(c)])])
# Traversal uses post pattern, so children hashes are cached
if expr._ufl_is_terminal_:
data = [terminal_hashdata[expr]]
else:
data = [expr._ufl_typecode_]
for op in expr.ufl_operands:
data += [cache[op]]
cache[expr] = hashlib.sha512(str(data).encode("utf-8")).digest()
return cache[expression]
[docs]def compute_expression_signature(expr, renumbering): # FIXME: Fix callers
# FIXME: Rewrite in terms of compute_form_signature?
# Build hashdata for all terminals first
terminal_hashdata = compute_terminal_hashdata([expr], renumbering)
# Build hashdata for full expression
expression_hashdata = compute_expression_hashdata(expr, terminal_hashdata)
# Pass it through a seriously overkill hashing algorithm
# (should we use sha1 instead?)
return expression_hashdata.hex()