Source code for ufl.formatting.ufl2unicode

# coding: utf-8

import numbers

import ufl
from ufl.log import error
from ufl.corealg.multifunction import MultiFunction
from ufl.corealg.map_dag import map_expr_dag
from ufl.core.multiindex import Index, FixedIndex
from ufl.form import Form
from ufl.algorithms import compute_form_data


[docs]class PrecedenceRules(MultiFunction): "An enum-like class for C operator precedence levels." def __init__(self): MultiFunction.__init__(self)
[docs] def highest(self, o): return 0
terminal = highest list_tensor = highest component_tensor = highest
[docs] def restricted(self, o): return 5
cell_avg = restricted facet_avg = restricted
[docs] def call(self, o): return 10
indexed = call min_value = call max_value = call math_function = call bessel_function = call
[docs] def power(self, o): return 12
[docs] def mathop(self, o): return 15
derivative = mathop trace = mathop deviatoric = mathop cofactor = mathop skew = mathop sym = mathop
[docs] def not_condition(self, o): return 20
[docs] def product(self, o): return 30
division = product # mod = product dot = product inner = product outer = product cross = product
[docs] def add(self, o): return 40
# sub = add index_sum = add
[docs] def lt(self, o): return 50
le = lt gt = lt ge = lt
[docs] def eq(self, o): return 60
ne = eq
[docs] def and_condition(self, o): return 70
[docs] def or_condition(self, o): return 71
[docs] def conditional(self, o): return 72
[docs] def lowest(self, o): return 80
operator = lowest
_precrules = PrecedenceRules()
[docs]def precedence(expr): return _precrules(expr)
try: import colorama has_colorama = True except ImportError: has_colorama = False
[docs]class UC: "An enum-like class for unicode characters." # Letters in this alphabet have contiguous code point numbers bold_math_a = u"𝐚" bold_math_A = u"𝐀" thin_space = u"\u2009" superscript_plus = u'⁺' superscript_minus = u'⁻' superscript_equals = u'⁼' superscript_left_paren = u'⁽' superscript_right_paren = u'⁾' superscript_digits = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"] subscript_plus = u'₊' subscript_minus = u'₋' subscript_equals = u'₌' subscript_left_paren = u'₍' subscript_right_paren = u'₎' subscript_digits = ["₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"] sqrt = u'√' transpose = u'ᵀ' integral = u'∫' integral_double = u'∬' integral_triple = u'∭' integral_contour = u'∮' integral_surface = u'∯' integral_volume = u'∰' sum = u'∑' division_slash = '∕' partial = u'∂' epsilon = u'ε' omega = u'ω' Omega = u'Ω' gamma = u'γ' Gamma = u'Γ' nabla = u'∇' for_all = u'∀' dot = u'⋅' cross_product = u'⨯' circled_times = u'⊗' nary_product = u'∏' ne = u'≠' lt = u'<' le = u'≤' gt = u'>' ge = u'≥' logical_and = u'∧' logical_or = u'∨' logical_not = u'¬' element_of = u'∈' not_element_of = u'∉' left_white_square_bracket = u'⟦' right_white_squared_bracket = u'⟧' left_angled_bracket = u'⟨' right_angled_bracket = u'⟩' left_double_angled_bracket = u'⟪' right_double_angled_bracket = u'⟫' combining_right_arrow_above = '\u20D7' combining_overline = '\u0305'
[docs]def bolden_letter(c): if ord("A") <= ord(c) <= ord("Z"): c = chr(ord(c) - ord(u"A") + ord(UC.bold_math_A)) elif ord("a") <= ord(c) <= ord("z"): c = chr(ord(c) - ord(u"a") + ord(UC.bold_math_a)) return c
[docs]def superscript_digit(digit): return UC.superscript_digits[ord(digit) - ord("0")]
[docs]def subscript_digit(digit): return UC.subscript_digits[ord(digit) - ord("0")]
[docs]def bolden_string(s): return u"".join(bolden_letter(c) for c in s)
[docs]def overline_string(f): return u"".join("%s%s" % (c, UC.combining_overline) for c in f)
[docs]def subscript_number(number): assert isinstance(number, int) prefix = UC.subscript_minus if number < 0 else '' number = str(number) return prefix + ''.join(subscript_digit(c) for c in str(number))
[docs]def superscript_number(number): assert isinstance(number, int) prefix = UC.superscript_minus if number < 0 else '' number = str(number) return prefix + ''.join(superscript_digit(c) for c in str(number))
[docs]def opfont(opname): return bolden_string(opname)
[docs]def measure_font(dx): return bolden_string(dx)
integral_by_dim = { 3: UC.integral_triple, 2: UC.integral_double, 1: UC.integral, } integral_type_to_codim = { "cell": 0, "exterior_facet": 1, "interior_facet": 1, "vertex": "tdim", "point": "tdim", "custom": 0, "overlap": 0, "interface": 1, "cutcell": 0, } integral_symbols = { "cell": UC.integral_volume, "exterior_facet": UC.integral_surface, "interior_facet": UC.integral_surface, "vertex": UC.integral, "point": UC.integral, "custom": UC.integral, "overlap": UC.integral, "interface": UC.integral, "cutcell": UC.integral, } integral_postfixes = { "cell": "", "exterior_facet": "ext", "interior_facet": "int", "vertex": "vertex", "point": "point", "custom": "custom", "overlap": "overlap", "interface": "interface", "cutcell": "cutcell", }
[docs]def get_integral_symbol(integral_type, domain, subdomain_id): tdim = domain.topological_dimension() codim = integral_type_to_codim[integral_type] itgdim = tdim - codim # ipost = integral_postfixes[integral_type] istr = integral_by_dim[itgdim] # TODO: Render domain description if isinstance(subdomain_id, numbers.Integral): istr += subscript_number(int(subdomain_id)) elif subdomain_id == "everywhere": pass elif subdomain_id == "otherwise": istr += "[rest of domain]" elif isinstance(subdomain_id, tuple): istr += ",".join([subscript_number(int(i)) for i in subdomain_id]) dxstr = ufl.measure.integral_type_to_measure_name[integral_type] dxstr = measure_font(dxstr) return istr, dxstr
[docs]def par(s): return "(%s)" % s
[docs]def prec(expr): return 0 # FIXME
# return precedence[expr._ufl_class_]
[docs]def is_int(s): try: int(s) return True except ValueError: return False
[docs]def format_index(ii): if isinstance(ii, FixedIndex): s = "%d" % ii._value elif isinstance(ii, Index): s = "i%s" % subscript_number(ii._count) else: error("Invalid index type %s." % type(ii)) return s
[docs]def ufl2unicode(expression): "Generate Unicode string for a UFL expression or form." if isinstance(expression, Form): form_data = compute_form_data(expression) preprocessed_form = form_data.preprocessed_form return form2unicode(preprocessed_form, form_data) else: return expression2unicode(expression)
[docs]def expression2unicode(expression, argument_names=None, coefficient_names=None): rules = Expression2UnicodeHandler(argument_names, coefficient_names) return map_expr_dag(rules, expression)
[docs]def form2unicode(form, formdata): # formname = formdata.name argument_names = None coefficient_names = None # Define form as sum of integrals lines = [] integrals = form.integrals() for itg in integrals: integrand_string = expression2unicode( itg.integrand(), argument_names, coefficient_names) istr, dxstr = get_integral_symbol(itg.integral_type(), itg.ufl_domain(), itg.subdomain_id()) line = "%s %s %s" % (istr, integrand_string, dxstr) lines.append(line) return '\n + '.join(lines)
[docs]def binop(expr, a, b, op, sep=" "): eprec = precedence(expr) op0, op1 = expr.ufl_operands aprec = precedence(op0) bprec = precedence(op1) # Assuming left-to-right evaluation, therefore >= and > here: if aprec >= eprec: a = par(a) if bprec > eprec: b = par(b) return sep.join((a, op, b))
[docs]def mathop(expr, arg, opname): eprec = precedence(expr) aprec = precedence(expr.ufl_operands[0]) op = opfont(opname) if aprec > eprec: arg = par(arg) sep = "" else: sep = UC.thin_space return "%s%s%s" % (op, sep, arg)
[docs]class Expression2UnicodeHandler(MultiFunction): def __init__(self, argument_names=None, coefficient_names=None, colorama_bold=False): MultiFunction.__init__(self) self.argument_names = argument_names self.coefficient_names = coefficient_names self.colorama_bold = colorama_bold and has_colorama # --- Terminal objects ---
[docs] def scalar_value(self, o): if o.ufl_shape and self.colorama_bold: return "%s%s%s" % (colorama.Style.BRIGHT, o._value, colorama.Style.RESET_ALL) return "%s" % o._value
[docs] def zero(self, o): if o.ufl_shape and self.colorama_bold: if len(o.ufl_shape) == 1: return "0%s" % UC.combining_right_arrow_above return "%s0%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL) return "0"
[docs] def identity(self, o): if self.colorama_bold: return "%sI%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL) return "I"
[docs] def permutation_symbol(self, o): if self.colorama_bold: return "%s%s%s" % (colorama.Style.BRIGHT, UC.epsilon, colorama.Style.RESET_ALL) return UC.epsilon
[docs] def facet_normal(self, o): return "n%s" % UC.combining_right_arrow_above
[docs] def spatial_coordinate(self, o): return "x%s" % UC.combining_right_arrow_above
[docs] def argument(self, o): # Using ^ for argument numbering and _ for indexing since # indexing is more common than exponentiation if self.argument_names is None: i = o.number() bfn = "v" if i == 0 else "u" if not o.ufl_shape: return bfn elif len(o.ufl_shape) == 1: return "%s%s" % (bfn, UC.combining_right_arrow_above) elif self.colorama_bold: return "%s%s%s" % (colorama.Style.BRIGHT, bfn, colorama.Style.RESET_ALL) else: return bfn return self.argument_names[(o.number(), o.part())]
[docs] def coefficient(self, o): # Using ^ for coefficient numbering and _ for indexing since # indexing is more common than exponentiation if self.coefficient_names is None: i = o.count() var = "w" if len(o.ufl_shape) == 1: var += UC.combining_right_arrow_above elif len(o.ufl_shape) > 1 and self.colorama_bold: var = "%s%s%s" % (colorama.Style.BRIGHT, var, colorama.Style.RESET_ALL) return "%s%s" % (var, superscript_number(i)) return self.coefficient_names[o.count()]
[docs] def constant(self, o): i = o.count() var = "c" if len(o.ufl_shape) == 1: var += UC.combining_right_arrow_above elif len(o.ufl_shape) > 1 and self.colorama_bold: var = "%s%s%s" % (colorama.Style.BRIGHT, var, colorama.Style.RESET_ALL) return "%s%s" % (var, superscript_number(i))
[docs] def multi_index(self, o): return ",".join(format_index(i) for i in o)
[docs] def label(self, o): return "l%s" % (subscript_number(o.count()),)
# --- Non-terminal objects ---
[docs] def variable(self, o, f, l): return "var(%s,%s)" % (f, l)
[docs] def index_sum(self, o, f, i): if 1: # prec(o.ufl_operands[0]) >? prec(o): f = par(f) return "%s[%s]%s" % (UC.sum, i, f)
[docs] def sum(self, o, a, b): return binop(o, a, b, "+")
[docs] def product(self, o, a, b): return binop(o, a, b, " ", sep="")
[docs] def division(self, o, a, b): if is_int(b): b = subscript_number(int(b)) if is_int(a): # Return as a fraction # NOTE: Maybe consider using fractional slash # with normal numbers if terminals can handle it a = superscript_number(int(a)) else: a = par(a) return "%s %s %s" % (a, UC.division_slash, b) return binop(o, a, b, UC.division_slash)
[docs] def abs(self, o, a): return "|%s|" % (a,)
[docs] def transposed(self, o, a): a = par(a) return "%s%s" % (a, UC.transpose)
[docs] def indexed(self, o, A, ii): op0, op1 = o.ufl_operands Aprec = precedence(op0) oprec = precedence(o) if Aprec > oprec: A = par(A) return "%s[%s]" % (A, ii)
[docs] def variable_derivative(self, o, f, v): f = par(f) v = par(v) nom = r"%s%s" % (UC.partial, f) denom = r"%s%s" % (UC.partial, v) return par(r"%s%s%s" % (nom, UC.division_slash, denom))
[docs] def coefficient_derivative(self, o, f, w, v, cd): f = par(f) w = par(w) nom = r"%s%s" % (UC.partial, f) denom = r"%s%s" % (UC.partial, w) return par(r"%s%s%s[%s]" % (nom, UC.division_slash, denom, v)) # TODO: Fix this syntax...
[docs] def grad(self, o, f): return mathop(o, f, "grad")
[docs] def div(self, o, f): return mathop(o, f, "div")
[docs] def nabla_grad(self, o, f): oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) return "%s%s%s" % (UC.nabla, UC.thin_space, f)
[docs] def nabla_div(self, o, f): oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.dot, UC.thin_space, f)
[docs] def curl(self, o, f): oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.cross_product, UC.thin_space, f)
[docs] def math_function(self, o, f): op = opfont(self._name) f = par(f) return "%s%s" % (op, f)
[docs] def sqrt(self, o, f): f = par(f) return "%s%s" % (UC.sqrt, f)
[docs] def exp(self, o, f): op = opfont("exp") f = par(f) return "%s%s" % (op, f)
[docs] def atan2(self, o, f1, f2): f1 = par(f1) f2 = par(f2) op = opfont("arctan2") return "%s(%s, %s)" % (op, f1, f2)
[docs] def bessel_j(self, o, nu, f): op = opfont("J") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f)
[docs] def bessel_y(self, o, nu, f): op = opfont("Y") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f)
[docs] def bessel_i(self, o, nu, f): op = opfont("I") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f)
[docs] def bessel_K(self, o, nu, f): op = opfont("K") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f)
[docs] def power(self, o, a, b): if is_int(b): b = superscript_number(int(b)) return binop(o, a, b, "", sep="") return binop(o, a, b, "^", sep="")
[docs] def outer(self, o, a, b): return binop(o, a, b, UC.circled_times)
[docs] def inner(self, o, a, b): return "%s%s, %s%s" % (UC.left_angled_bracket, a, b, UC.right_angled_bracket)
[docs] def dot(self, o, a, b): return binop(o, a, b, UC.dot)
[docs] def cross(self, o, a, b): return binop(o, a, b, UC.cross_product)
[docs] def determinant(self, o, A): return "|%s|" % (A,)
[docs] def inverse(self, o, A): A = par(A) return "%s%s" % (A, superscript_number(-1))
[docs] def trace(self, o, A): return mathop(o, A, "tr")
[docs] def deviatoric(self, o, A): return mathop(o, A, "dev")
[docs] def cofactor(self, o, A): return mathop(o, A, "cofac")
[docs] def skew(self, o, A): return mathop(o, A, "skew")
[docs] def sym(self, o, A): return mathop(o, A, "sym")
[docs] def conj(self, o, a): # Overbar is already taken for average, and there is no superscript asterix in unicode. return mathop(o, a, "conj")
[docs] def real(self, o, a): return mathop(o, a, "Re")
[docs] def imag(self, o, a): return mathop(o, a, "Im")
[docs] def list_tensor(self, o, *ops): l = ", ".join(ops) # noqa: E741 return "%s%s%s" % ("[", l, "]")
[docs] def component_tensor(self, o, A, ii): return "[%s %s %s]" % (A, UC.for_all, ii)
[docs] def positive_restricted(self, o, f): f = par(f) return "%s%s" % (f, UC.superscript_plus)
[docs] def negative_restricted(self, o, f): f = par(f) return "%s%s" % (f, UC.superscript_minus)
[docs] def cell_avg(self, o, f): f = overline_string(f) return f
[docs] def facet_avg(self, o, f): f = overline_string(f) return f
[docs] def eq(self, o, a, b): return binop(o, a, b, "=")
[docs] def ne(self, o, a, b): return binop(o, a, b, UC.ne)
[docs] def le(self, o, a, b): return binop(o, a, b, UC.le)
[docs] def ge(self, o, a, b): return binop(o, a, b, UC.ge)
[docs] def lt(self, o, a, b): return binop(o, a, b, UC.lt)
[docs] def gt(self, o, a, b): return binop(o, a, b, UC.gt)
[docs] def and_condition(self, o, a, b): return binop(o, a, b, UC.logical_and)
[docs] def or_condition(self, o, a, b): return binop(o, a, b, UC.logical_or)
[docs] def not_condition(self, o, a): a = par(a) return "%s%s" % (UC.logical_not, a)
[docs] def conditional(self, o, c, t, f): c = par(c) t = par(t) f = par(t) If = opfont("if") Else = opfont("else") l = " ".join((t, If, c, Else, f)) # noqa: E741 return l
[docs] def min_value(self, o, a, b): op = opfont("min") return "%s(%s, %s)" % (op, a, b)
[docs] def max_value(self, o, a, b): op = opfont("max") return "%s(%s, %s)" % (op, a, b)
[docs] def expr_list(self, o, *ops): items = ", ".join(ops) return "%s %s %s" % (UC.left_white_square_bracket, items, UC.right_white_squared_bracket)
[docs] def expr_mapping(self, o, *ops): items = ", ".join(ops) return "%s %s %s" % (UC.left_double_angled_bracket, items, UC.left_double_angled_bracket)
[docs] def expr(self, o): raise ValueError("Missing handler for type %s" % str(type(o)))