Source code for ufl.algorithms.formfiles

"""A collection of utility algorithms for handling UFL files."""

# Copyright (C) 2008-2016 Martin Sandve Alnæs
#
# This file is part of UFL (https://www.fenicsproject.org)
#
# SPDX-License-Identifier:    LGPL-3.0-or-later
#
# Modified by Anders Logg, 2008-2009.
# Modified by Marie E. Rognes, 2011.

import os
import re

from ufl.argument import Argument
from ufl.coefficient import Coefficient
from ufl.constant import Constant
from ufl.core.expr import Expr
from ufl.finiteelement import AbstractFiniteElement
from ufl.form import Form
from ufl.utils.sorting import sorted_by_key


class FileData:
    """File data."""

    def __init__(self):
        """Initialise."""
        self.elements = []
        self.coefficients = []
        self.expressions = []
        self.forms = []
        self.object_names = {}
        self.object_by_name = {}
        self.reserved_objects = {}

    def __bool__(self):
        """Convert to a bool."""
        return bool(
            self.elements
            or self.coefficients
            or self.forms
            or self.expressions
            or self.object_names
            or self.object_by_name
            or self.reserved_objects
        )

    __nonzero__ = __bool__


def read_lines_decoded(fn):
    """Read decoded lines of a UFL file."""
    r = re.compile(b".*coding: *([^ ]+)")

    def match(line):
        """Match."""
        return r.match(line, re.ASCII)

    # First read lines as bytes
    with open(fn, "rb") as f:
        lines = f.readlines()

    # Check for coding: in the first two lines
    for i in range(min(2, len(lines))):
        m = match(lines[i])
        if m:
            (encoding,) = m.groups()
            # Drop encoding line
            lines = lines[:i] + lines[i + 1 :]
            break
    else:
        # Default to utf-8 (works for ascii files
        # as well, default for python files in py3)
        encoding = "utf-8"

    # Decode all lines
    lines = [line.decode(encoding=encoding) for line in lines]
    return lines


[docs] def read_ufl_file(filename): """Read a UFL file.""" if not os.path.exists(filename): raise ValueError(f"File '{filename}' doesn't exist.") lines = read_lines_decoded(filename) code = "".join(lines) return code
def execute_ufl_code(uflcode): """Execute code.""" namespace = {} exec(uflcode, namespace) return namespace def interpret_ufl_namespace(namespace): """Take a namespace dict from an executed ufl file and convert it to a FileData object.""" # Object to hold all returned data ufd = FileData() # Extract object names for Form, Coefficient and AbstractFiniteElement objects # The use of id(obj) as key in object_names is necessary # because we need to distinguish between instances, # and not just between objects with different values. for name, value in sorted_by_key(namespace): # Store objects by reserved name OR instance id reserved_names = ("unknown",) # Currently only one reserved name if name in reserved_names: # Store objects with reserved names ufd.reserved_objects[name] = value # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value elif isinstance( value, AbstractFiniteElement | Coefficient | Constant | Argument | Form | Expr ): # Store instance <-> name mappings for important objects # without a reserved name ufd.object_names[id(value)] = name ufd.object_by_name[name] = value # Get list of exported forms forms = namespace.get("forms") if forms is None: # Get forms from object_by_name, which has already mapped # tuple->Form where needed def get_form(name): form = ufd.object_by_name.get(name) return form if isinstance(form, Form) else None a_form = get_form("a") L_form = get_form("L") M_form = get_form("M") forms = [a_form, L_form, M_form] # Add forms F and J if not "a" and "L" are used if a_form is None or L_form is None: F_form = get_form("F") J_form = get_form("J") forms += [F_form, J_form] # Remove Nones forms = [f for f in forms if isinstance(f, Form)] ufd.forms = forms # Validate types if not isinstance(ufd.forms, list | tuple): raise ValueError(f"Expecting 'forms' to be a list or tuple, not '{type(ufd.forms)}'.") if not all(isinstance(a, Form) for a in ufd.forms): raise ValueError("Expecting 'forms' to be a list of Form instances.") # Get list of exported elements elements = namespace.get("elements") if elements is None: elements = [ufd.object_by_name.get(name) for name in ("element",)] elements = [e for e in elements if e is not None] ufd.elements = elements # Validate types if not isinstance(ufd.elements, list | tuple): raise ValueError( f"Expecting 'elements' to be a list or tuple, not '{type(ufd.elements)}''." ) if not all(isinstance(e, AbstractFiniteElement) for e in ufd.elements): raise ValueError("Expecting 'elements' to be a list of AbstractFiniteElement instances.") # Get list of exported coefficients functions = [] ufd.coefficients = namespace.get("coefficients", functions) # Validate types if not isinstance(ufd.coefficients, list | tuple): raise ValueError( f"Expecting 'coefficients' to be a list or tuple, not '{type(ufd.coefficients)}'." ) if not all(isinstance(e, Coefficient) for e in ufd.coefficients): raise ValueError("Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types if not isinstance(ufd.expressions, list | tuple): raise ValueError( f"Expecting 'expressions' to be a list or tuple, not '{type(ufd.expressions)}'." ) if not all(isinstance(e[0], Expr) for e in ufd.expressions): raise ValueError("Expecting 'expressions' to be a list of Expr instances.") # Return file data return ufd
[docs] def load_ufl_file(filename): """Load a UFL file with elements, coefficients, expressions and forms.""" # Read code from file and execute it uflcode = read_ufl_file(filename) namespace = execute_ufl_code(uflcode) return interpret_ufl_namespace(namespace)
[docs] def load_forms(filename): """Return a list of all forms in a file.""" ufd = load_ufl_file(filename) return ufd.forms