Fuzzy Logic Operations

One core feature of the provided libraries are the operations defined in hybrid_learning.fuzzy_logic. These allow to

  • define logical formulas as truth functions

  • define logics as a collection of such logical operations

  • parse logical formulas to and from string infix representation for a given logic

Modelling Approach

A logic in mathematics is a collection of logical symbols that may be used to formulate formulas. Symbols are variables and constants, functions, and logical operations, i.e. logical connectives (e.g. AND \(\wedge\), OR \(\vee\), NOT \(\neg\)), quantifiers (e.g. \(\exists\), \(\forall\)), and logical predicates (e.g. GreaterThan \(>\)).

Basic Ideas

The core data exchange format between operations is a dictionary (the annotations). This holds the groundings of variables and constants (i.e. mapping their names to values), and outputs of previous operations, e.g. truth values. This common storage for domain values and truth values enables easy reuse of prior operation outputs. Operations (functions, logical operations) are then callables that accept such an annotations dictionary, and add their output as further key-value pair. Operations are defined by specifying the keys of values that should be used from the annotations dictionary.

>>> from hybrid_learning.fuzzy_logic.tnorm_connectives.boolean import AND
>>> grounding = {'a': True, 'b': False}
>>> operation = AND("a", "b")
>>> operation(grounding)
{'a': True, 'b': False, 'a&&b': 0}

Also, for efficient computation, a family of (truth) values may be stored as a vector under one key. Operations will then take care of broadcasting.

>>> import numpy as np
>>> grounding = {'a': np.array([True, False]), 'b': True}
>>> AND("a", "b")(grounding)
{'a': array([ True, False]), 'b': True, 'a&&b': array([1, 0])}

An operation may also output a vector of values. Mathematically, this then is a family of operations with each operation producing one of the vector entries as output. This allows to handle a DNN that produces a mask tensor as output as a family of operations.

Base Classes

In this framework, logical formulas and theories are modelled using the base classes Merge and Logic as follows.

Variables and Constants

Variables are represented by string keys. They may be grounded (have a value assigned) by adding the key-value pair to the annotations dictionary. Constants are treated like grounded variables.

Functions and Logical Operations

These are instances of Merge. Provided base classes and implementations of typical logical operations are:

Formulas

Fromulas are computational trees of operations. As placeholder keys, Merge operations may use string keys of annotations values, or directly specify a child operation that should produce the value to use.

>>> from hybrid_learning.fuzzy_logic.tnorm_connectives.boolean import AND, OR
>>> operation = AND("a", OR("b", "c"), keep_keys=['b||c'])
>>> operation({'a': True, 'b': False, 'c': True})
{'a': True, 'b': False, 'c': True, 'b||c': True, '(b||c)&&a': 1}
>>> operation({'a': True, 'b': False, 'c': True})
{'a': True, 'b': False, 'c': True, 'b||c': True, '(b||c)&&a': 1}

The children operations are applied to the grounding dictionary before the parent operation, adding their outputs. In case the output of the root operation is a truth value, such a computational tree of operations constitutes a logical formula.

Logics

A logic is here defined by a collection of operation builders for building formulas. To allow parsing of infix notation, these are associated with an operator precedence (their order in the collection). The base class for this is Logic, and some logics with standard connectives are defined in tnorm_connectives. For building a standard logic including some predicates one can use get_logic(). To derive custom operation builders from Merge types, one can use a MergeBuilder.

Parsing

A logic allows parsing of infix notation string representations of formulas using a FormulaParser. Calling str on the resulting formula returns an equivalent parsable string representation of the formula.

Variadic Operations

At the heart of a merge operation lies the operation() function that receives the domain or truth values referenced by the operation settings, and returns the actual operation output. The class method variadic_() will return a callable that accepts a list of values (or a dict of values, ignoring the keys), and directly return the output of the operation. For further details and examples see variadic_().

Parsing Formulas

For parsing a string specification of a formula into an operation object, one requires a list of operation builders, e.g. operation classes or MergeBuilder instances, that are ordered by precedence (higher index = higher precedence). Instances of the default FormulaParser can be called on an infix notation of a formula and will return the parsed object. The symbols for representing the operations in the infix formula are assumed to be the SYMB attributes of the operation builders. The str to the formula object is the inverse to this default parsing operation.

>>> from hybrid_learning.fuzzy_logic import FormulaParser
>>> from hybrid_learning.fuzzy_logic.tnorm_connectives.boolean import AND, OR
>>> formula_spec: str = "a&&b||c&&d"
>>> FormulaParser([AND, OR])(formula_spec)
AND('a', OR('b', 'c'), 'd')
>>> FormulaParser([OR, AND])(formula_spec)
OR(AND('a', 'b'), AND('c', 'd'))
>>> str(FormulaParser([AND, OR])(formula_spec))
'(b||c)&&a&&d'

Defining Operator Builders and Logics

A logic is a sequence of operator builders for feeding a parser. Besides acting as a collection, the Logic base class offers some convenience methods for easy parsing and operation building. See the Logic documentation and the parser() methods for details.

Operator builders must be a callable that returns an operation, provide a SYMB attribute and a variadic_ function, which is fulfilled, e.g., by a Merge subclass or an MergeBuilder object. Using MergeBuilder, one can specify additional keyword arguments for the init call to a Merge class, e.g., change the default SYMB. A merge builder can be created using the standard init, or using the class method with_() of a Merge class.

>>> from hybrid_learning.fuzzy_logic import MergeBuilder
>>> from hybrid_learning.fuzzy_logic.quantifiers import ALL
>>> ALL('mask', dim=(-2, -1), symb='AllPixels')
ALL('mask', dim=(-2, -1), symb='AllPixels')
>>> builder = MergeBuilder(ALL, symb='AllPixels', additional_args={'dim': (-2, -1)})
>>> builder('mask')
ALL('mask', dim=(-2, -1), symb='AllPixels')
>>> # Using functional interface integrated with Merge classes
>>> builder = ALL.with_(dim=(-2, -1)).symb_('AllPixels')
>>> builder('mask')
ALL('mask', dim=(-2, -1), symb='AllPixels')

The builders also support variadic instance generation:

>>> ALL.variadic_(dim=(-2, -1), symb='AllPixels')
ALL(_variadic=True, dim=(-2, -1), symb='AllPixels')
>>> ALL.with_(dim=(-2, -1)).symb_('AllPixels').variadic_()
ALL(_variadic=True, dim=(-2, -1), symb='AllPixels')

Such operator builders can now be used to enrich one of the standard logics, as used in the standard logics builder get_logic():

>>> from hybrid_learning.fuzzy_logic import Logic, logic_by_name
>>> logic: Logic = logic_by_name('product')
>>> logic
ProductLogic()
>>> logic.operators
[...IMPLIEDBY..., ...IMPLIES..., ...AND..., ...OR..., ...NOT...]
>>> logic.insert(-1, ALL.with_(dim=(-2, -1)).symb_('AllPixels'))
>>> logic.operators
[..., ...OR..., MergeBuilder(hybrid_learning.fuzzy_logic.quantifiers.ALL, symb='AllPixels', additional_args={'dim': (-2, -1)}), ...NOT...]
>>> logic.parser()('AllPixels mask || b')
OR(ALL('mask', dim=(-2, -1), symb='AllPixels'), 'b')