fuzzy-logic-search: Query Documents with Fuzzy Logic

November 30, 2025

fuzzy-logic-search (fls) brings fuzzy logic to document querying. Unlike traditional Boolean search that returns binary relevant/not-relevant results, fls produces a degree-of-membership score in [0, 1], indicating how well each document matches your query.

Boolean search is rigid: a document either matches or it does not. If you search for “python AND machine-learning,” you get a binary split. A document about Python ML that never uses the exact term “machine-learning” gets zero, same as a document about medieval pottery.

Fuzzy logic captures the gradation that Boolean search throws away.

from fuzzy_logic_search.fuzzy_query import FuzzyQuery
from fuzzy_logic_search.fuzzy_set import FuzzySet

# Construct a query
query = FuzzyQuery("(and python machine-learning)")

# Or use Python operators
q1 = FuzzyQuery("python")
q2 = FuzzyQuery("machine-learning")
query = q1 & q2  # Equivalent to (and python machine-learning)

Query Language

Queries use a Lisp-like syntax that maps to an AST:

; Simple conjunction
(and cat dog)

; With negation
(and cat dog (not fish))

; With fuzzy modifiers
(very (and cat dog))

; Complex nested query
(or (and python ml) (very (not java)))

Or construct directly with Python:

# Using operators
query = FuzzyQuery("cat") & FuzzyQuery("dog") & ~FuzzyQuery("fish")

# Using AST directly
query = FuzzyQuery(['and', 'cat', 'dog', ['not', 'fish']])

I went with S-expressions for the query language because they map directly to the AST. No parsing ambiguity, trivial to serialize, and anyone who has written a Lisp evaluator can understand the implementation in about ten minutes.

Fuzzy Modifiers

Linguistic hedges transform membership values:

# "Very" squares the membership (emphasizes strong matches)
very_query = FuzzyQuery("python").very()
# 0.9 -> 0.81, 0.5 -> 0.25

# "Somewhat" takes square root (broadens tolerance)
somewhat_query = FuzzyQuery("python").somewhat()
# 0.9 -> 0.95, 0.25 -> 0.5

# "Extremely" cubes the membership
extremely_query = FuzzyQuery("python").extremely()

# "Slightly" takes 10th root
slightly_query = FuzzyQuery("python").slightly()

These come from Zadeh’s original fuzzy logic work. “Very” is concentration (squaring), “somewhat” is dilation (square root). They are mathematically clean and semantically intuitive: “very python” means “only documents that are strongly about Python.”

Evaluating Queries

Evaluate queries against a document corpus:

# Documents as lists of terms
docs = [
    ["python", "machine-learning", "tensorflow"],
    ["java", "spring", "microservices"],
    ["python", "web", "flask"],
    ["machine-learning", "neural-networks", "pytorch"]
]

# Evaluate query
query = FuzzyQuery("python") & FuzzyQuery("machine-learning")
result = query.evaluate(docs)  # Returns FuzzySet

# result.memberships = [1.0, 0.0, 0.0, 0.0]
# Only first document has both terms

Custom Membership Functions

The default membership is crisp (term present or not), but you can provide custom functions for more nuanced matching:

Read More