Skip to content

Fluent API Guide

Master FuzzyInfer's beautiful, Pythonic interface for elegant inference pipelines.

Introduction

The Fluent API makes fuzzy inference feel natural in Python:

from fuzzy_infer import fuzzy

# Chain operations elegantly
result = (
    fuzzy()
    .fact("is-bird", ["robin"], 0.9)
    .rule(when="is-bird ?x", then="can-fly ?x 0.85")
    .infer()
    .query("can-fly")
)

Creating Knowledge Bases

Basic Creation

from fuzzy_infer import fuzzy, FluentKB

# Using the helper function
kb = fuzzy()

# Or explicitly
kb = FluentKB()

# With initial facts
kb = fuzzy(
    ("is-mammal", ["dog"]),
    ("is-mammal", ["cat"], 0.9)
)

Method Chaining

Every method returns self for chaining:

kb = (
    fuzzy()
    .fact("temperature", ["sensor1", 25.5])
    .fact("humidity", ["sensor1", 0.65])
    .rule(
        when=("temperature", "?s", "?t"),
        then=("hot", "?s"),
        where=lambda b: b["?t"] > 30
    )
    .infer()
)

Adding Facts

Single Facts

# Tuple notation
kb.fact("is-bird", ["robin"], 0.9)

# Positional arguments
kb.fact("is-bird", ["eagle"])  # degree=1.0 by default

# Keyword arguments
kb.fact(predicate="can-fly", args=["robin"], degree=0.85)

Multiple Facts

# Using facts() method
kb.facts(
    ("is-mammal", ["dog"]),
    ("is-mammal", ["cat"], 0.9),
    ("has-fur", ["dog"], 1.0),
    ("has-fur", ["cat"], 0.95)
)

# From a list
animal_facts = [
    ("is-bird", ["robin"], 0.9),
    ("is-bird", ["eagle"], 1.0)
]
kb.facts(*animal_facts)

Creating Rules

Basic Rules

# Simple when-then
kb.rule(
    when=("is-mammal", "?x"),
    then=("is-warm-blooded", "?x")
)

# With degree modification
kb.rule(
    when=("is-bird", "?x"),
    then=("can-fly", "?x", 0.85)
)

# Named rules
kb.rule(
    when=("is-penguin", "?x"),
    then=("can-fly", "?x", 0.0),
    name="penguin-cannot-fly"
)

Complex Conditions

# Multiple AND conditions
kb.rule(
    when=[
        ("has-wings", "?x"),
        ("has-feathers", "?x"),
        ("lays-eggs", "?x")
    ],
    then=("is-bird", "?x", 0.95)
)

# OR conditions
kb.rule(
    when_any=[
        ("has-emergency", "?x"),
        ("priority-high", "?x")
    ],
    then=("process-immediately", "?x")
)

# Mixed AND/OR
kb.rule(
    when=[("sensor", "?s", "?value")],
    when_any=[
        ("threshold-exceeded", "?s"),
        ("manual-override", "?s")
    ],
    then=("trigger-alert", "?s")
)

Constraints and Filters

# Using where clause
kb.rule(
    when=("age", "?person", "?age"),
    then=("is-adult", "?person"),
    where=lambda bindings: bindings["?age"] >= 18
)

# Degree constraints
kb.rule(
    when=("confidence", "?prediction", degree=">0.8"),
    then=("accept", "?prediction")
)

# Complex filtering
kb.rule(
    when=[
        ("temperature", "?sensor", "?temp"),
        ("humidity", "?sensor", "?humid")
    ],
    then=("uncomfortable", "?sensor"),
    where=lambda b: b["?temp"] > 30 and b["?humid"] > 0.7
)

Query DSL

Basic Queries

# Query all facts of a predicate
all_birds = kb.query("is-bird")

# Query specific arguments
is_robin_bird = kb.query("is-bird", ["robin"])

# Pattern matching
flying_things = kb.query("can-fly", ["?x"])

Chained Queries

# Get results and filter
mammals = (
    kb.query("is-mammal")
    .with_min_degree(0.8)
    .sorted_by_degree()
    .limit(5)
)

# Transform results
names = kb.query("is-animal", ["?x"]).args(0)  # Get first arg of each

Advanced Queries

# Multiple patterns
results = kb.where(
    ("parent", "?x", "?y"),
    ("parent", "?y", "?z")
).select("?x", "?z")  # Get grandparents

# With filters
adults = kb.where(
    ("person", "?name"),
    ("age", "?name", "?age"),
    filter=lambda b: b["?age"] >= 18
)

Sessions and Context Managers

Basic Session

from fuzzy_infer import FuzzySession

with FuzzySession() as session:
    session.fact("is-bird", ["robin"])
    session.rule(
        when="is-bird ?x",
        then="can-fly ?x"
    )
    session.infer()
    results = session.query("can-fly")

Session Features

with FuzzySession(auto_infer=True) as session:
    # Auto-infer after each rule
    session.fact("temperature", ["room", 25])
    session.rule(
        when="temperature ?place ?t",
        then="comfortable ?place",
        where=lambda b: 20 <= b["?t"] <= 26
    )
    # Inference runs automatically
    print(session.query("comfortable"))

Functional Style

Lambda Rules

# Using lambdas for complex logic
kb.rule(lambda kb:
    kb["is-mammal"]["?x"] and kb["is-large"]["?x"]
    >> kb.add("is-megafauna", "?x")
)

# Conditional logic
kb.rule(lambda kb, bindings:
    kb.add("category", bindings["?x"],
           "large" if bindings["?size"] > 100 else "small")
    if kb.matches("animal", bindings["?x"])
    else None
)

Pipeline Operators

from fuzzy_infer import Pipeline

# Create inference pipeline
pipeline = (
    Pipeline()
    .add_facts(initial_facts)
    .add_rules(classification_rules)
    .transform(normalize_degrees)
    .filter(lambda f: f.degree > 0.5)
    .infer()
)

results = pipeline.execute()

Operators and Shortcuts

Operator Overloading

# Using >> for then
kb.rule(
    kb["is-bird"]["?x"] >> kb.add("can-fly", "?x", 0.9)
)

# Using & for AND
kb.rule(
    (kb["has-wings"]["?x"] & kb["has-feathers"]["?x"])
    >> kb.add("is-bird", "?x")
)

# Using | for OR
kb.rule(
    (kb["emergency"]["?x"] | kb["urgent"]["?x"])
    >> kb.add("priority-high", "?x")
)

String Shortcuts

# Parse rules from strings
kb.rule("is-mammal ?x -> warm-blooded ?x")
kb.rule("is-bird ?x & can-swim ?x -> is-waterfowl ?x 0.9")

# Batch rules
kb.rules("""
    is-human ?x -> is-mortal ?x
    is-greek ?x & is-human ?x -> is-philosopher ?x 0.8
    parent ?x ?y & parent ?y ?z -> grandparent ?x ?z
""")

Fuzzy Expressions

Building Expressions

from fuzzy_infer import FuzzyExpr

# Create fuzzy expressions
expr = FuzzyExpr("temperature", ">", 30)

# Use in rules
kb.rule(
    when=expr.match("?sensor"),
    then=("overheating", "?sensor")
)

# Combine expressions
hot = FuzzyExpr("temperature", ">", 30)
humid = FuzzyExpr("humidity", ">", 0.7)
uncomfortable = hot & humid

kb.rule(
    when=uncomfortable.match("?place"),
    then=("need-ac", "?place")
)

Degree Expressions

# Arithmetic on degrees
kb.rule(
    when=("confidence", "?x", degree="?c"),
    then=("double-check", "?x", degree="1.0 - ?c")
)

# Fuzzy hedges
kb.rule(
    when=("tall", "?x", degree="?d"),
    then=[
        ("very-tall", "?x", degree="?d ** 2"),    # Very
        ("somewhat-tall", "?x", degree="?d ** 0.5"), # Somewhat
        ("extremely-tall", "?x", degree="?d ** 3")  # Extremely
    ]
)

Streaming Support

Async Streaming

from fuzzy_infer import stream_session

async def process_sensor_stream():
    async with stream_session() as stream:
        # Add rules
        stream.rule(
            when="sensor-reading ?id ?value",
            then="alert ?id",
            where=lambda b: b["?value"] > 100
        )

        # Process stream
        async for reading in sensor_stream:
            await stream.add_fact(reading)
            alerts = await stream.query("alert")
            if alerts:
                await notify(alerts)

Batch Processing

# Process in batches for efficiency
def process_large_dataset(facts, batch_size=1000):
    kb = fuzzy()
    kb.add_rules(rules)

    for i in range(0, len(facts), batch_size):
        batch = facts[i:i+batch_size]
        kb.facts(*batch)
        kb.infer()

        # Process results incrementally
        yield kb.query_new()  # Get only new facts
        kb.clear_new()  # Clear new facts marker

Error Handling

Validation

# Automatic validation
try:
    kb.fact("invalid", "not-a-list")  # Args must be a list
except ValueError as e:
    print(f"Invalid fact: {e}")

# Custom validation
kb.rule(
    when="data ?x ?value",
    then="valid ?x",
    where=lambda b: isinstance(b["?value"], (int, float)),
    on_error="skip"  # Skip invalid bindings
)

Safe Operations

# Safe chaining with defaults
result = (
    kb.query("might-not-exist")
    .or_default([])  # Return empty list if no results
    .first_or_none()  # Get first or None
)

# Conditional execution
kb.if_exists("precondition").then_rule(
    when="dependent ?x",
    then="result ?x"
)

Best Practices

1. Use Descriptive Names

# Good
kb.rule(
    when="has-fever ?patient",
    then="needs-medication ?patient",
    name="fever-treatment"
)

# Bad
kb.rule(when="f ?x", then="m ?x", name="rule1")
# Group facts by domain
with kb.domain("medical"):
    kb.facts(
        ("patient", ["john"]),
        ("symptom", ["john", "fever"]),
        ("symptom", ["john", "cough"])
    )
    kb.rule(
        when=["symptom ?p fever", "symptom ?p cough"],
        then="possible-flu ?p"
    )

3. Use Type Hints

from typing import List
from fuzzy_infer import FluentKB, Fact

def classify_animals(kb: FluentKB, animals: List[str]) -> List[Fact]:
    for animal in animals:
        kb.fact("animal", [animal])

    kb.rule(
        when="animal ?x",
        then="needs-classification ?x"
    )

    return kb.infer().query("needs-classification")

Next Steps