Source code for boolforge.boolean_function.core

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np

from .. import utils
    
from .analysis import BooleanFunctionAnalysisMixin
from .canalization import BooleanFunctionCanalizationMixin
from .collective_canalization import BooleanFunctionCollectiveCanalizationMixin
from .conversions import BooleanFunctionConversionsMixin
from .interoperability import BooleanFunctionInteroperabilityMixin
from .parsing import f_from_expression

[docs] class BooleanFunction( BooleanFunctionAnalysisMixin, BooleanFunctionCanalizationMixin, BooleanFunctionCollectiveCanalizationMixin, BooleanFunctionConversionsMixin, BooleanFunctionInteroperabilityMixin, ): """ A Boolean function. This class represents a Boolean function :math:`f : \\{0,1\\}^n \\to \\{0,1\\}` and stores its truth table together with variable names and optional metadata. Parameters ---------- f : list[int] | np.ndarray | str Either: - a truth table of length ``2**n`` representing the outputs of a Boolean function with ``n`` inputs, or - a Boolean expression string that can be evaluated. Expression strings are parsed using ``boolean_function.parsing.f_from_expression``. name : str, optional Name of the node regulated by the Boolean function. Default is ``""``. variables : list[str] | np.ndarray | None, optional Names of the input variables, given in order. Must have length ``n``. If ``None`` (default), variables are named ``x0, ..., x_{n-1}``. Attributes ---------- f : np.ndarray NumPy array of dtype ``uint8`` and length ``2**n`` containing only the values 0 and 1, representing the truth table of the Boolean function. n : int Number of input variables. variables : np.ndarray One-dimensional NumPy array of length ``n`` containing variable names. name : str Name of the node regulated by the Boolean function. properties : dict Dictionary for dynamically computed properties of the Boolean function (e.g., canalizing structure, effective inputs, robustness measures). """ __slots__ = ['f','n','variables','name','properties'] def __init__( self, f : list[int] | np.ndarray | str, variables : list[str] | np.ndarray | None = None, name : str = "" ): """ Initialize a Boolean function. Parameters ---------- f : list[int] or np.ndarray or str Truth table of the Boolean function, given as a list or array of length ``2**n``, or a Boolean expression as a string. variables : list[str] or np.ndarray[str], optional Names of the input variables. Must have length ``n`` if provided. If None, default variable names x0, x1, ... are assigned. name : str, optional Optional name of the Boolean function. Notes ----- - The number of inputs ``n`` is inferred from the length of ``f``. """ self.name = name if isinstance(f, str): f, self.variables = f_from_expression(f) else: if not isinstance(f, (list, np.ndarray)): raise ValueError("f must be a list, numpy array or interpretable string") if not len(f) > 0: raise ValueError("f cannot be empty") _n = int(np.log2(len(f))) if not abs(np.log2(len(f)) - _n) < 1e-9: raise ValueError("f must be of size 2^n, n >= 0") if variables is None: self.variables = np.array([f"x{i}" for i in range(_n)]) else: self.variables = np.asarray(variables, dtype=str) if self.variables.ndim != 1: raise ValueError("variables must be a 1D array of strings") if len(self.variables) != _n: raise ValueError( f"variables must have length {_n}, got {len(self.variables)}" ) self.n = len(self.variables) self.f = np.array(f, dtype=np.uint8) if not np.all((self.f == 0) | (self.f == 1)): raise ValueError("f must contain only the values 0 and 1.") self.properties = {} @classmethod def _from_f_unchecked( cls, f : list[int] | np.ndarray, *, variables : list[str] | np.ndarray | None = None, name : str ="" ) -> "BooleanFunction": """ Construct a BooleanFunction directly from a truth table *without* validating invariants. This internal constructor bypasses all safety checks and therefore assumes that the input truth table already satisfies all BooleanFunction invariants. In particular, it assumes that: - ``f`` has length ``2**n`` for some integer ``n``, - all entries of ``f`` are in ``{0,1}``, - ``f`` is already in a NumPy-compatible array-like format. This method exists for **performance-critical internal code paths** such as random Boolean function generation, bulk construction, or Numba-accelerated routines, where invariant checks would be prohibitively expensive and correctness is guaranteed by construction. **Warning** ------- This method must *never* be called on untrusted input. """ obj = cls.__new__(cls) # Core data obj.f = np.asarray(f, dtype=np.uint8) obj.n = int(np.log2(len(obj.f))) # Metadata (match __init__ invariants) obj.name = name if variables is None: obj.variables = np.array([f"x{i}" for i in range(obj.n)]) else: obj.variables = np.asarray(variables) obj.properties = {} return obj ## Magic methods:
[docs] def __str__(self): """ Return a human-readable string representation of the Boolean function. This method returns the underlying truth table as a NumPy array. """ return f"{self.f}"
[docs] def __repr__(self): """ Return an unambiguous string representation of the BooleanFunction. For small functions (``n < 5``), the full truth table is shown. For larger functions, only the number of inputs is displayed to avoid excessive output. """ name = f"name='{self.name}', " if self.name else "" if self.n < 5: return f"{type(self).__name__}({name}f={self.f.tolist()})" else: return f"{type(self).__name__}({name}n={self.n}, bias={self.bias:.3f})"
def __len__(self): return 2**self.n def __getitem__(self, index): try: return int(self.f[index]) except TypeError: return self.f[index] def __setitem__(self, index, value): self.f[index] = value
[docs] def __mul__(self, value): """ Element-wise Boolean multiplication (logical AND). This method implements logical AND between Boolean functions or between a Boolean function and a scalar value in ``{0,1}``. Parameters ---------- value : BooleanFunction or int BooleanFunction of the same size or an integer value (0 or 1). Returns ------- BooleanFunction Result of element-wise Boolean multiplication. """ if isinstance(value, int): if value not in (0, 1): raise ValueError("Integer multiplier must be 0 or 1.") return self.__class__(self.f * value) elif isinstance(value, BooleanFunction): return self.__class__(self.f * value.f) else: raise TypeError( f"Unsupported operand type(s) for *: " f"'BooleanFunction' and '{type(value).__name__}'" )
[docs] def __rmul__(self, value): """ Right-hand element-wise Boolean multiplication (logical AND). This method enables expressions of the form ``value * BooleanFunction`` where ``value`` is an integer in ``{0,1}``. Parameters ---------- value : int Integer value (0 or 1). Returns ------- BooleanFunction Result of element-wise Boolean multiplication. """ return self.__mul__(value)
[docs] def __and__(self, value): """ Element-wise logical AND. This method implements element-wise logical AND between Boolean functions or between a Boolean function and a scalar value in ``{0,1}``. Parameters ---------- value : BooleanFunction or int BooleanFunction of the same size or an integer value (0 or 1). Returns ------- BooleanFunction Result of element-wise logical AND. """ if isinstance(value, int): if value not in (0, 1): raise ValueError("Integer must be 0 or 1.") return self.__class__(self.f & value) elif isinstance(value, BooleanFunction): return self.__class__(self.f & value.f) else: raise TypeError( f"Unsupported operand type(s) for &: " f"'BooleanFunction' and '{type(value).__name__}'" )
[docs] def __or__(self, value): """ Element-wise logical OR. This method implements element-wise logical OR between Boolean functions or between a Boolean function and a scalar value in ``{0,1}``. Parameters ---------- value : BooleanFunction or int BooleanFunction of the same size or an integer value (0 or 1). Returns ------- BooleanFunction Result of element-wise logical OR. """ if isinstance(value, int): if value not in (0, 1): raise ValueError("Integer must be 0 or 1.") return self.__class__(self.f | value) elif isinstance(value, BooleanFunction): return self.__class__(self.f | value.f) else: raise TypeError( f"Unsupported operand type(s) for |: " f"'BooleanFunction' and '{type(value).__name__}'" )
[docs] def __xor__(self, value): """ Element-wise logical XOR. This method implements element-wise logical XOR between Boolean functions or between a Boolean function and a scalar value in ``{0,1}``. Parameters ---------- value : BooleanFunction or int BooleanFunction of the same size or an integer value (0 or 1). Returns ------- BooleanFunction Result of element-wise logical XOR. """ if isinstance(value, int): if value not in (0, 1): raise ValueError("Integer must be 0 or 1.") return self.__class__(self.f ^ value) elif isinstance(value, BooleanFunction): return self.__class__(self.f ^ value.f) else: raise TypeError( f"Unsupported operand type(s) for ^: " f"'BooleanFunction' and '{type(value).__name__}'" )
[docs] def __invert__(self): """ Element-wise logical negation. This method computes the logical NOT of the Boolean function by flipping all truth table entries. Returns ------- BooleanFunction Result of element-wise logical negation. """ return self.__class__(1 - self.f)
[docs] def __call__(self, values: list[int] | tuple[int, ...] | np.ndarray): """ Evaluate the Boolean function on a given input vector. This method makes BooleanFunction instances callable and returns the output value for a specified binary input configuration. Parameters ---------- values : list[int] | tuple[int, ...] | np.ndarray Sequence of binary values (0 or 1) of length ``n``, where ``n`` is the number of input variables of the Boolean function. Returns ------- int Output value of the Boolean function (0 or 1) for the specified input. Raises ------ ValueError If the input length does not match ``n`` or if non-binary values are provided. Examples -------- >>> f = BooleanFunction("x1 | (x2 & x3)") >>> f([1, 0, 1]) 1 >>> f([0, 1, 0]) 0 """ if not len(values) == self.n: raise ValueError(f"The argument must be of length {self.n}.") if not set(values) <= {0, 1}: raise ValueError("Binary values required.") return self.f[utils.bin2dec(values)].item()
[docs] def summary(self, compute_all: bool = False, *, as_dict: bool = False): """ Return a concise summary of the Boolean function. The summary includes basic structural and statistical properties of the Boolean function and, optionally, additional properties that may require nontrivial computation. Parameters ---------- compute_all : bool, optional If ``True``, additional properties are computed and included in the summary. These computations may be expensive. If ``False`` (default), only already available properties are included. as_dict : bool, optional If ``True``, return the summary as a dictionary. If ``False`` (default), return a formatted string. Returns ------- str or dict Summary of the Boolean function, either as a formatted string or as a dictionary depending on the value of ``as_dict``. """ core_summary = { "Number of variables": self.n, "Hamming Weight": self.hamming_weight, "Bias": self.bias, "Absolute bias": self.absolute_bias, "Variables": self.variables.tolist(), } special_formatting = { "Absolute bias" : ".3f", "Bias" : ".3f", "Average sensitivity" : ".3f" } summary = core_summary.copy() if compute_all: activities = self.get_activities() avg_sensitivity = self.get_average_sensitivity() summary['Activities'] = [f"{x:.3f}" for x in activities] summary['Average sensitivity'] = avg_sensitivity self.get_type_of_inputs() self.get_layer_structure() summary.update(self.properties) if as_dict: return summary title = "BooleanFunction" if self.name: title += f" ({self.name})" lines = [title, "-" * len(title)] for key, value in summary.items(): if key not in special_formatting: lines.append(f"{key+':':27}{value}") else: lines.append(f"{key+':':27}{value:{special_formatting[key]}}") return "\n".join(lines)