Configurational expressions¶
QCA notation — A*~B + C, A*B -> Y — is parsed into a typed tree rather than
manipulated as text. Expressions are therefore comparable, simplifiable and
evaluable, and solutions come back as structured objects instead of strings.
Parsing¶
| Notation | Meaning | Alternatives |
|---|---|---|
* |
conjunction, minimum | |
+ |
disjunction, maximum | |
~A |
negation, 1 - A |
!A, -A |
-> |
implication (sufficiency claim) | => |
( ) |
grouping |
Condition names follow Python identifier rules, so both the uppercase single letters of the literature and longer descriptive names work.
Nothing is evaluated as code
Parsing is structural — a tokenizer and a recursive-descent parser. An
expression taken from a configuration file or from user input cannot execute
anything. There is no eval anywhere in this package.
Malformed input raises ExpressionSyntaxError, which points at the position:
>>> parse_expression("A * * B")
ExpressionSyntaxError: Expected a condition name, found '*'
A * * B
^
Precedence¶
Conjunction binds more tightly than disjunction, and negation more tightly
still, so A + B*C means A + (B*C). Parentheses override this, and the
printer re-inserts them wherever grouping would otherwise be lost:
>>> from setqca.expressions import format_expression
>>> format_expression(parse_expression("(A + B)*C"))
'(A+B)*C'
>>> format_expression(parse_expression("A + B*C"))
'A+B*C'
Parsing and printing round-trip: text → tree → text → tree gives back a
semantically identical tree, which is property-tested.
Evaluation¶
Fuzzy operators are the standard ones — minimum, maximum and 1 - x.
An implication has no membership of its own, because it is a relation between two sets rather than a set. Evaluate it as one:
claim = parse_expression("A*B -> Y")
fit = claim.evaluate_relation(data)
print(fit.consistency, fit.coverage, fit.pri)
Asking for the membership of an implication is an error rather than a silent guess.
Simplification¶
from setqca import simplify_expression
from setqca.expressions import format_expression
format_expression(simplify_expression("A + A*B")) # 'A'
Applied: associativity, commutativity, idempotence (A*A = A), double negation
(~~A = A) and absorption (A + A*B = A).
The complement laws do not hold
In Boolean algebra A*~A is empty and A+~A is the universe. Neither is
true for fuzzy sets. With A = 0.5, min(A, 1-A) = 0.5 and
max(A, 1-A) = 0.5 — a case can be half in a set and half in its negation
at the same time.
setqca therefore never simplifies those away:
This is the single most common way a Boolean-minded simplifier corrupts a fuzzy analysis. Every simplification here is verified to leave membership unchanged on real data.
Comparing expressions¶
Two expressions that differ only by ordering or nesting are equal after canonicalisation:
from setqca.expressions import equivalent
equivalent(parse_expression("A*B + C"), parse_expression("C + B*A")) # True
Note this is structural equivalence under the laws above, not semantic equivalence over all possible data. Deciding the latter for fuzzy sets is a different and much harder question.
Configurations¶
A Configuration is one corner of the property space — a state for every
condition — and converts to and from a minterm index:
from setqca.expressions import Configuration
config = Configuration.from_minterm(6, ("A", "B", "C"))
str(config) # 'A*B*~C'
config.minterm # 6
config.evaluate(data)
Minterm indices are big-endian over the condition order, matching the truth table and the minimiser.
setqca.expressions ¶
Typed configurational expressions: parsing, canonical form and evaluation.
Expressions are parsed structurally into a typed tree. Nothing in the input is ever evaluated as code, so an expression from a configuration file or a user prompt cannot execute anything.
Examples:
>>> import pandas as pd
>>> from setqca.expressions import evaluate_expression, parse_expression
>>> data = pd.DataFrame({"A": [0.9, 0.2], "B": [0.8, 0.7]})
>>> evaluate_expression("A*~B", data).round(2)
array([0.2, 0.2])
>>> str(parse_expression("A*B -> Y"))
'A*B -> Y'
Condition
dataclass
¶
Bases: SetExpression
Named calibrated condition drawn from a column of the data.
evaluate ¶
Return the calibrated membership column for this condition.
Source code in src/setqca/sets.py
Intersection
dataclass
¶
Intersection(operands: tuple[SetExpression, ...])
Bases: SetExpression
Fuzzy conjunction using the minimum t-norm.
evaluate ¶
Return the elementwise minimum across all operands.
Source code in src/setqca/sets.py
Negation
dataclass
¶
Negation(operand: SetExpression)
SetExpression ¶
Bases: ABC
Abstract fuzzy-set expression over calibrated conditions.
Expressions compose with the standard Python operators & (intersection,
minimum t-norm), | (union, maximum s-norm) and ~ (negation).
evaluate
abstractmethod
¶
Evaluate membership of the expression for every case.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Frame of calibrated condition memberships. |
required |
Returns:
| Type | Description |
|---|---|
FloatArray
|
Membership of each case in the expression. |
Source code in src/setqca/sets.py
Union
dataclass
¶
Union(operands: tuple[SetExpression, ...])
Bases: SetExpression
Fuzzy disjunction using the maximum s-norm.
evaluate ¶
Return the elementwise maximum across all operands.
Source code in src/setqca/sets.py
Configuration
dataclass
¶
One corner of the property space: a state for every condition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
states
|
tuple of (str, bool)
|
Condition name and whether it is present, in minterm order. |
required |
to_expression ¶
to_expression() -> SetExpression
Return the conjunction of literals describing this corner.
Source code in src/setqca/expressions/_ast.py
evaluate ¶
from_minterm
classmethod
¶
from_minterm(
minterm: int, conditions: tuple[str, ...]
) -> Configuration
Build a configuration from a big-endian minterm index.
Source code in src/setqca/expressions/_ast.py
Implication
dataclass
¶
Implication(
antecedent: SetExpression, consequent: SetExpression
)
A sufficiency claim antecedent -> consequent.
An implication has no membership of its own: it is a relation between two sets, evaluated as a set-theoretic subset relation rather than as a membership vector.
evaluate_relation ¶
evaluate_relation(data: DataFrame) -> SufficiencyFit
Return the parameters of fit for the claim against the data.
ExpressionSyntaxError ¶
Bases: ValueError
Raised when an expression cannot be tokenized or parsed.
The message carries the offending position so the caller can point at it.
Source code in src/setqca/expressions/_tokenizer.py
Token
dataclass
¶
Token(kind: TokenKind, text: str, position: int)
A lexical token and where it started in the source text.
TokenKind ¶
Bases: Enum
Lexical category of a token.
canonical ¶
canonical(node: SetExpression) -> SetExpression
Return a structurally canonical form of an expression.
Associativity, commutativity, idempotence and double negation are applied, so two expressions that differ only by those laws canonicalise to the same object and therefore compare equal.
The complement laws are deliberately not applied; see the module docstring.
Source code in src/setqca/expressions/_ast.py
equivalent ¶
equivalent(
left: SetExpression, right: SetExpression
) -> bool
Return whether two expressions are equal after simplification.
format_expression ¶
format_expression(node: SetExpression) -> str
Render a node, parenthesising only where grouping would otherwise be lost.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
SetExpression
|
Expression to render. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Standard QCA notation, for example |
Examples:
>>> from setqca import Condition
>>> a, b, c = Condition("A"), Condition("B"), Condition("C")
>>> format_expression((a | b) & c)
'(A+B)*C'
>>> format_expression(a | (b & c))
'A+B*C'
Source code in src/setqca/expressions/_ast.py
precedence ¶
precedence(node: SetExpression) -> int
simplify ¶
simplify(node: SetExpression) -> SetExpression
Simplify an expression using only laws valid for fuzzy sets.
Applies flattening, commutative ordering, idempotence, double negation and absorption. Never applies the complement laws, which are false for the minimum/maximum operators.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
SetExpression
|
Expression to simplify. |
required |
Returns:
| Type | Description |
|---|---|
SetExpression
|
A semantically identical expression, in canonical order. |
Examples:
>>> from setqca import Condition
>>> from setqca.expressions import format_expression, simplify
>>> a, b = Condition("A"), Condition("B")
>>> format_expression(simplify(a | (a & b)))
'A'
Source code in src/setqca/expressions/_ast.py
parse_expression ¶
parse_expression(
expression: str,
) -> SetExpression | Implication
Parse a configurational expression into a typed tree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expression
|
str
|
Standard QCA notation. |
required |
Returns:
| Type | Description |
|---|---|
SetExpression or Implication
|
An :class: |
Raises:
| Type | Description |
|---|---|
ExpressionSyntaxError
|
If the text is not a well-formed expression. The message includes the offending position. |
Examples:
>>> from setqca.expressions import parse_expression
>>> str(parse_expression("A*~B + C"))
'A*~B+C'
>>> str(parse_expression("A*B -> Y"))
'A*B -> Y'
Source code in src/setqca/expressions/_parser.py
parse_set_expression ¶
parse_set_expression(expression: str) -> SetExpression
Parse an expression that must not be an implication.
Use this when the caller needs a membership-valued expression and a relation would be a mistake rather than a variant.
Raises:
| Type | Description |
|---|---|
ExpressionSyntaxError
|
If the text is malformed, or is an implication. |
Source code in src/setqca/expressions/_parser.py
tokenize ¶
tokenize(expression: str) -> list[Token]
Split an expression into tokens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expression
|
str
|
Source text, for example |
required |
Returns:
| Type | Description |
|---|---|
list of Token
|
Tokens terminated by a single :attr: |
Raises:
| Type | Description |
|---|---|
ExpressionSyntaxError
|
If the text contains a character that cannot begin a token. |
Source code in src/setqca/expressions/_tokenizer.py
evaluate_expression ¶
evaluate_expression(
expression: str | SetExpression, data: DataFrame
) -> FloatArray
Evaluate an expression against calibrated data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expression
|
str or SetExpression
|
Expression text, or an already-parsed tree. |
required |
data
|
DataFrame
|
Calibrated condition memberships in |
required |
Returns:
| Type | Description |
|---|---|
FloatArray
|
Membership of every case in the expression. |
Raises:
| Type | Description |
|---|---|
ExpressionSyntaxError
|
If the text is malformed or is an implication, which has no membership
of its own. Use :meth: |
Source code in src/setqca/expressions/__init__.py
simplify_expression ¶
simplify_expression(
expression: str | SetExpression,
) -> SetExpression
Parse if needed, then simplify using only fuzzy-valid laws.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expression
|
str or SetExpression
|
Expression text, or an already-parsed tree. |
required |
Returns:
| Type | Description |
|---|---|
SetExpression
|
A semantically identical expression in canonical order. |