Source code for polyfemos.parser.functionparser

# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# This file is part of Polyfemos.
#
# Polyfemos is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or any later version.
#
# Polyfemos is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License and
# GNU General Public License along with Polyfemos. If not, see
# <https://www.gnu.org/licenses/>.'
#
# Author: Henrik Jänkävaara
# -----------------------------------------------------------------------------
"""
Functions to parse filepaths from strings following the internal function
syntax

:copyright:
    2019, University of Oulu, Sodankyla Geophysical Observatory
:license:
    GNU Lesser General Public License v3.0 or later
    (https://spdx.org/licenses/LGPL-3.0-or-later.html)
"""
import ast
import operator


[docs]def compose(op, f1, f2): """ A function to compose functions :type op: func :param op: A two argument function :type f1,f2: float, int, bool, func :param f1,f2: value or function, given function should take one argument, if value given, it will be changed in to one-argument function :rtype: func :return: A one argument function """ # make 'arg' callable if it is not wrap = lambda arg: arg if callable(arg) else lambda x: arg return lambda x: op(wrap(f1)(x), wrap(f2)(x))
# aliases are used for defining multicharacter operations # boolean aliases are just for convenience _aliases = [ ["<=", "{"], [">=", "}"], ["!=", "!"], ["==", "="], ["**", "^"], ["False", "F"], ["True", "T"], ["NaN", "F"], ] # possible operations with 2 arguments _operations = { "^": operator.pow, "*": operator.mul, "/": operator.truediv, "+": operator.add, "-": operator.sub, "<": operator.lt, "{": operator.le, ">": operator.gt, "}": operator.ge, "=": operator.eq, "!": operator.ne, "&": operator.and_, "|": operator.or_, "r": round, } # boolean values and identity function to include variable _vals = { "X": lambda x: x, "F": lambda x: False, "T": lambda x: True, } _reserved = set(_operations) | set(_vals)
[docs]def function_from_str(functionstr): """ Creates a simple function from given string. The ``functionstr`` may or may not be inside parentheses, the ``functionstr`` may not contain any other brackets (except curly brackets intead of ``<=`` and ``>=`` operations if wanted). Available operations in execution order, Order of execution of same operations is from left to right. Note that all of the operations take two arguments. +----------+-------+----------------------------+ |Operation | Alias | Description | +==========+=======+============================+ | ``**`` | ``^`` | power | +----------+-------+----------------------------+ | ``*`` | | multiplication | +----------+-------+----------------------------+ | ``/`` | | division | +----------+-------+----------------------------+ | ``+`` | | addition | +----------+-------+----------------------------+ | ``-`` | | substracion | +----------+-------+----------------------------+ | ``<`` | | lesser than | +----------+-------+----------------------------+ | ``<=`` | ``{`` | lesser or equal | +----------+-------+----------------------------+ | ``>`` | | greater than | +----------+-------+----------------------------+ | ``>=`` | ``}`` | greater or equal | +----------+-------+----------------------------+ | ``==`` | ``=`` | equal | +----------+-------+----------------------------+ | ``!=`` | ``!`` | not equal | +----------+-------+----------------------------+ | ``&`` | | and | +----------+-------+----------------------------+ | ``|`` | | or | +----------+-------+----------------------------+ | ``r`` | | round, <num>r<decimals> | +----------+-------+----------------------------+ Available values +-------------+-------+---------------------------------------------------+ | Value | Alias | Description | +=============+=======+===================================================+ | ``X`` | | The variable/argument in the resulting function | +-------------+-------+---------------------------------------------------+ | ``False`` | ``F`` | boolean False | +-------------+-------+---------------------------------------------------+ | ``True`` | ``T`` | boolean False | +-------------+-------+---------------------------------------------------+ Example ``functionstr`` values .. code-block:: text (False) (X<4.0|X>7|X==5) (X<4.0|X>7) (X<=4.0|X>=7) (X*0.001r3) (X*10./27.+5.r3) (X<=-1) :type functionstr: str :param functionstr: :rtype: func :return: A one-argument function which returns either boolean or numeral depending on the used operations """ if functionstr[0] != "(" or functionstr[-1] != ")": # Should the parentheses be forced? pass functionstr = functionstr.strip("()") # replace aliases for alias in _aliases: functionstr = functionstr.replace(*alias) # distinguish operations from other values for k in _operations.keys(): functionstr = functionstr.replace(k, " {} ".format(k)) # distinguish unary operation '-' from the subtraction operation for k in _operations.keys(): functionstr = functionstr.replace( " {} - ".format(k), " {} -".format(k)) # create list containing operations, values and numerals functionstr = [s for s in functionstr.split() if s] # evaluate numerals functionstr = [ s if s in _reserved else ast.literal_eval(s) for s in functionstr] # replace values with their respective functions functionstr = [_vals[s] if s in _vals else s for s in functionstr] for k, v in _operations.items(): while k in functionstr: i = functionstr.index(k) # Apply arguments to the operation and insert the # resulting function in place of the operation functionstr[i] = compose(v, functionstr[i - 1], functionstr[i + 1]) # Remove arguments from the list functionstr[i - 1] = None functionstr[i + 1] = None functionstr = [s for s in functionstr if s is not None] func = functionstr[0] func = func if callable(func) else None return func
if __name__ == "__main__": fstrs = [ "(False)", "(X<4.0|X>7|X==5)", "(X<4.0|X>7)", "(X<=4.0|X>=7)", "(X*0.001r3)", "(X*10./27.+5.r3)", "(X<=-1)", ] for fstr in fstrs: f = function_from_str(fstr) print() print(fstr) for i in range(14): a = i - 3 print(a, f(a)) print(f(float('nan')))